import XCTest
@testable import Physarum
final class AudioTransientTests: XCTestCase {
private func tone(freq: Double, amp: Float, sr: Double, n: Int) -> [Float] {
(0..<n).map { amp * Float(sin(2.0 * Double.pi * freq * Double($0) / sr)) }
}
/// The transient path is independent of the balance adaptation; keeping it off here
/// means these tests observe the onset detector alone.
private func rawTuning() -> AudioTuning {
var t = AudioTuning()
t.adaptiveBalance = false
return t
}
func testSteadyToneTransientStaysLow_OnsetSpikes() {
let sr = 48000.0, n = 1024
let a = AudioAnalyzer(fftSize: n, smoothing: 0)
var last = AudioBands.zero
for _ in 0..<60 { last = a.analyze(samples: tone(freq: 120, amp: 0.5, sr: sr, n: n), sampleRate: sr, tuning: rawTuning()) }
XCTAssertLessThan(last.transientBass, 0.15, "steady tone should idle low")
let onset = a.analyze(samples: tone(freq: 120, amp: 1.0, sr: sr, n: n), sampleRate: sr, tuning: rawTuning())
XCTAssertGreaterThan(onset.transientBass, last.transientBass + 0.2, "onset should spike transient")
}
func testSilenceTransientIsZero() {
let sr = 48000.0, n = 1024
let a = AudioAnalyzer(fftSize: n, smoothing: 0)
var b = AudioBands.zero
for _ in 0..<10 { b = a.analyze(samples: [Float](repeating: 0, count: n), sampleRate: sr, tuning: rawTuning()) }
XCTAssertLessThan(b.transientBass, 0.05)
XCTAssertLessThan(b.transientMid, 0.05)
XCTAssertLessThan(b.transientHigh, 0.05)
}
}