diff --git a/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm b/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm index f140a90..6b255b0 100644 --- a/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm +++ b/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm @@ -54,6 +54,8 @@ void deinit() override { void reset() override { SoundpipeDSPBase::reset(); if (!isInitialized) return; + sp_mincer_destroy(&mincer); + sp_mincer_create(&mincer); sp_mincer_init(sp, mincer, ftbl, 2048); } diff --git a/Tests/SoundpipeAudioKitTests/Generator Tests/PhaseLockedVocoderTests.swift b/Tests/SoundpipeAudioKitTests/Generator Tests/PhaseLockedVocoderTests.swift index 0029929..b4fc592 100644 --- a/Tests/SoundpipeAudioKitTests/Generator Tests/PhaseLockedVocoderTests.swift +++ b/Tests/SoundpipeAudioKitTests/Generator Tests/PhaseLockedVocoderTests.swift @@ -44,4 +44,66 @@ class PhaseLockedVocoderTests: XCTestCase { testMD5(audio) } + + func testReset() { + let url = generateTestFile() + XCTAssertNotNil(url) + + guard let file = try? AVAudioFile(forReading: url) else { + XCTFail("Couldn't load test file") + return + } + + let engine = AudioEngine() + let vocoder = PhaseLockedVocoder(file: file) + engine.output = vocoder + + // Start the engine and render some audio + let audio = engine.startTest(totalDuration: 2.0) + vocoder.$position.ramp(to: 0.5, duration: 0.5) + audio.append(engine.render(duration: 1.0)) + + // Get initial memory usage + var info = mach_task_basic_info() + var count = mach_msg_type_number_t(MemoryLayout.size)/4 + var kerr: kern_return_t = withUnsafeMutablePointer(to: &info) { + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { + task_info(mach_task_self_, + task_flavor_t(MACH_TASK_BASIC_INFO), + $0, + &count) + } + } + XCTAssertEqual(kerr, KERN_SUCCESS) + let initialMemory = info.resident_size + + // Reset the vocoder multiple times to stress test memory management + for _ in 0 ... 1000 { + vocoder.reset() + } + + // Get final memory usage + kerr = withUnsafeMutablePointer(to: &info) { + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { + task_info(mach_task_self_, + task_flavor_t(MACH_TASK_BASIC_INFO), + $0, + &count) + } + } + XCTAssertEqual(kerr, KERN_SUCCESS) + let finalMemory = info.resident_size + + // Calculate memory growth + let memoryGrowth = finalMemory - initialMemory + XCTAssertLessThan(memoryGrowth, 1_000_000, "Memory leak detected: Memory grew by \(memoryGrowth) bytes") + + // Continue rendering after reset + vocoder.$position.ramp(to: 0, duration: 0.5) + audio.append(engine.render(duration: 1.0)) + + engine.stop() + + testMD5(audio) + } } diff --git a/Tests/SoundpipeAudioKitTests/ValidatedMD5s.swift b/Tests/SoundpipeAudioKitTests/ValidatedMD5s.swift index ec87e03..c88dad5 100644 --- a/Tests/SoundpipeAudioKitTests/ValidatedMD5s.swift +++ b/Tests/SoundpipeAudioKitTests/ValidatedMD5s.swift @@ -48,6 +48,7 @@ let validatedMD5s: [String: String] = [ "-[OscillatorAutomationTests testAutomationAfterDelayedConnection]": "93d96731b7ca3dc9bf1e4209bd0b65ec", "-[OscillatorAutomationTests testDelayedAutomation]": "640265895a27587289d65a29ce129804", "-[PhaseLockedVocoderTests testDefault]": "eb9fe2d8ee2e3b3d6527a4e139c2686e", + "-[PhaseLockedVocoderTests testReset]": "814d0574a9d98c76e2dd557050f55f37", "-[PitchTapTests testBasic]": "5b6ae6252df77df298996a7367a00a9e", "-[PluckedStringTests testDefault]": "3f13907e6e916b7a4bf6046a4cbf0764", "-[TalkboxTests testTalkbox]": "316ef6638793f5fb6ec43fae1919ccff",