|
| 1 | +// Regression guard for https://github.com/AudioKit/SoundpipeAudioKit/issues/37 |
| 2 | +// (tenseness=1 reportedly silencing the generator). The bug no longer repros |
| 3 | +// in offline render; these tests assert the boundary values still produce audio. |
| 4 | + |
| 5 | +import AudioKit |
| 6 | +import AVFoundation |
| 7 | +import SoundpipeAudioKit |
| 8 | +import XCTest |
| 9 | + |
| 10 | +class VocalTractTenseness1Tests: XCTestCase { |
| 11 | + func testTensenessZeroToOneSnap() { |
| 12 | + assertAudible(setup: { $0.tenseness = 0.0 }, transition: { $0.tenseness = 1.0 }) |
| 13 | + } |
| 14 | + |
| 15 | + func testTensenessZeroToOneRamp() { |
| 16 | + assertAudible(setup: { $0.tenseness = 0.0 }, |
| 17 | + transition: { $0.$tenseness.ramp(to: 1.0, duration: 0.1) }) |
| 18 | + } |
| 19 | + |
| 20 | + func testTensenessStartsAtOne() { |
| 21 | + assertAudible(setup: { $0.tenseness = 1.0 }, transition: { _ in }) |
| 22 | + } |
| 23 | + |
| 24 | + func testTensenessOneToZero() { |
| 25 | + assertAudible(setup: { $0.tenseness = 1.0 }, transition: { $0.tenseness = 0.0 }) |
| 26 | + } |
| 27 | + |
| 28 | + private func assertAudible(setup: (VocalTract) -> Void, |
| 29 | + transition: (VocalTract) -> Void, |
| 30 | + file: StaticString = #filePath, |
| 31 | + line: UInt = #line) { |
| 32 | + let engine = AudioEngine() |
| 33 | + let vocalTract = VocalTract() |
| 34 | + setup(vocalTract) |
| 35 | + engine.output = vocalTract |
| 36 | + |
| 37 | + let audio = engine.startTest(totalDuration: 2.0) |
| 38 | + vocalTract.start() |
| 39 | + audio.append(engine.render(duration: 1.0)) |
| 40 | + transition(vocalTract) |
| 41 | + let after = engine.render(duration: 1.0) |
| 42 | + audio.append(after) |
| 43 | + engine.stop() |
| 44 | + |
| 45 | + XCTAssertGreaterThan(rms(of: after), 1e-5, "near-silent output", file: file, line: line) |
| 46 | + XCTAssertFalse(hasNonFinite(after), "NaN/Inf in output", file: file, line: line) |
| 47 | + } |
| 48 | + |
| 49 | + private func rms(of buffer: AVAudioPCMBuffer) -> Float { |
| 50 | + guard let data = buffer.floatChannelData else { return 0 } |
| 51 | + let frames = Int(buffer.frameLength) |
| 52 | + let channels = Int(buffer.format.channelCount) |
| 53 | + var sumSq: Double = 0 |
| 54 | + for ch in 0 ..< channels { |
| 55 | + for i in 0 ..< frames { |
| 56 | + let s = Double(data[ch][i]) |
| 57 | + if s.isFinite { sumSq += s * s } |
| 58 | + } |
| 59 | + } |
| 60 | + let n = Double(frames * channels) |
| 61 | + return n > 0 ? Float((sumSq / n).squareRoot()) : 0 |
| 62 | + } |
| 63 | + |
| 64 | + private func hasNonFinite(_ buffer: AVAudioPCMBuffer) -> Bool { |
| 65 | + guard let data = buffer.floatChannelData else { return false } |
| 66 | + let frames = Int(buffer.frameLength) |
| 67 | + for ch in 0 ..< Int(buffer.format.channelCount) { |
| 68 | + for i in 0 ..< frames where !data[ch][i].isFinite { return true } |
| 69 | + } |
| 70 | + return false |
| 71 | + } |
| 72 | +} |
0 commit comments