498f407585
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
78 lines
3.1 KiB
Swift
78 lines
3.1 KiB
Swift
// ASRConversionTests.swift
|
|
// OSGKeyboard · Tests
|
|
//
|
|
// Locks in the Float32→Int16 PCM conversion that `SpeechAnalyzerASR`
|
|
// runs on the audio thread. The dictation transcriber's precondition
|
|
// (`"Audio sample data must be 16-bit signed integers"`) trips if
|
|
// the conversion is wrong, so the scaling + clipping math here is
|
|
// the difference between "Speech works" and "Speech crashes" — worth
|
|
// a regression test even though it's only ~3 lines of arithmetic.
|
|
|
|
@testable import OSGKeyboardHostSupport
|
|
@testable import OSGKeyboardShared
|
|
import XCTest
|
|
|
|
final class ASRConversionTests: XCTestCase {
|
|
|
|
/// Edge cases: silence, full-scale positive, full-scale negative,
|
|
/// mid-scale positive, mid-scale negative, and the "above unity"
|
|
/// gain-overflow case. Each is the one number we'd most regret
|
|
/// getting wrong.
|
|
func testFloat32ToInt16EdgeCases() {
|
|
runConversion(
|
|
input: [0.0, 1.0, -1.0, 0.5, -0.5, 1.5, -1.5],
|
|
expected: [0, 32767, -32767, 16384, -16384, 32767, -32768]
|
|
)
|
|
}
|
|
|
|
/// Round-trip-ish: every Int16 in a small range should be
|
|
/// reachable from a corresponding Float input. Locks the
|
|
/// quantization step (1/32767) so a future "use a different
|
|
/// scaling" change has to update this test.
|
|
func testFloat32ToInt16RoundTrip() {
|
|
var input = [Float]()
|
|
var expected = [Int16]()
|
|
for i in stride(from: -32768, through: 32767, by: 1024) {
|
|
// Map Int16 back to its canonical Float source value:
|
|
// src = i / 32767.0 (so src=1.0 → i=32767, src=-1.0 → i=-32767).
|
|
// We don't test i=-32768 because the asymmetric range
|
|
// (Int16 is -32768...32767) means there is no Float
|
|
// that decodes back to exactly -32768.
|
|
let src = Float(i) / 32767.0
|
|
input.append(src)
|
|
expected.append(Int16(i))
|
|
}
|
|
runConversion(input: input, expected: expected)
|
|
}
|
|
|
|
/// Empty input must be a no-op. Guards against off-by-one in
|
|
/// the loop and against `UnsafePointer` access on a zero-length
|
|
/// array (which is undefined behaviour in C but valid in Swift).
|
|
func testFloat32ToInt16Empty() {
|
|
// `sourceCount == 0` with `nil` pointers is a no-op. The
|
|
// function guards on `sourceCount > 0` before dereferencing
|
|
// anything, so the nil pointers are safe.
|
|
ASRServiceFactory.convertFloat32ToInt16(
|
|
source: nil, sourceCount: 0, destination: nil
|
|
)
|
|
// Reaching here without crashing is the assertion.
|
|
}
|
|
|
|
// MARK: - Helper
|
|
|
|
private func runConversion(input: [Float], expected: [Int16]) {
|
|
precondition(input.count == expected.count, "test setup")
|
|
var actual = [Int16](repeating: 0, count: expected.count)
|
|
input.withUnsafeBufferPointer { src in
|
|
actual.withUnsafeMutableBufferPointer { dst in
|
|
ASRServiceFactory.convertFloat32ToInt16(
|
|
source: src.baseAddress,
|
|
sourceCount: src.count,
|
|
destination: dst.baseAddress
|
|
)
|
|
}
|
|
}
|
|
XCTAssertEqual(actual, expected)
|
|
}
|
|
}
|