Download this file
| 1 | import CallsKit |
| 2 | import Foundation |
| 3 | |
| 4 | /// The harness's microphone: a voiced, syllable-shaped tone over steady |
| 5 | /// noise, so the far side receives audio with speech-like structure and |
| 6 | /// the capture processing has something to work on. Real speech it is |
| 7 | /// not; the fixture test in CallsKitTests is where suppression is judged. |
| 8 | final class SyntheticVoice: AudioInputSource, @unchecked Sendable { |
| 9 | private var t = 0.0 |
| 10 | private var seed: UInt32 = 0x1234_5678 |
| 11 | |
| 12 | func render(_ samples: UnsafeMutablePointer<Float>, count: Int, sampleRate: Double) { |
| 13 | for i in 0..<count { |
| 14 | // Four syllables a second, a 140 Hz voice with two harmonics. |
| 15 | let envelope = max(0, sin(2 * .pi * 2 * t)) |
| 16 | let voice = |
| 17 | envelope |
| 18 | * (sin(2 * .pi * 140 * t) + 0.5 * sin(2 * .pi * 280 * t) + 0.25 * sin(2 * .pi * 420 * t)) |
| 19 | seed = seed &* 1_664_525 &+ 1_013_904_223 |
| 20 | let noise = Double(Int32(bitPattern: seed)) / Double(Int32.max) |
| 21 | samples[i] = Float(0.2 * voice + 0.02 * noise) |
| 22 | t += 1 / sampleRate |
| 23 | } |
| 24 | } |
| 25 | } |
| 26 | |