Hi there, I'm building an audio app for iOS and ran into a very weird bug when using multiple AVAudioUnitSampler instances to play different instruments at the same time. I narrowed down the repro to:
let sampler = AVAudioUnitSampler()
self.audioEngine.attach(sampler)
self.audioEngine.connect(sampler, to: self.audioEngine.mainMixerNode, format: nil)
try! sampler.loadInstrument(at: Bundle.main.url(forResource: "SteinwayPiano-v1", withExtension: "aupreset")!)
let sampler2 = AVAudioUnitSampler()
self.audioEngine.attach(sampler2)
self.audioEngine.connect(sampler2, to: self.audioEngine.mainMixerNode, format: nil)
try! sampler2.loadInstrument(at: Bundle.main.url(forResource: "Rhodes-v1", withExtension: "aupreset")!)
I get the following error on the console (real device and simulator):
2022-07-02 21:27:28.329147+0200 soundboard[23592:612358] [default] ExtAudioFile.cpp:193 about to throw -42: open audio file
2022-07-02 21:27:28.329394+0200 soundboard[23592:612358] [default] FileSample.cpp:52 about to throw -42: FileSample::LoadFromURL: ExtAudioFileOpenURL
2022-07-02 21:27:28.330206+0200 soundboard[23592:612358] SampleManager.cpp:434 Failed to load sample 'Sounds/Rhodes/A_050__D3_4-ST.wav -- file:///Users/deermichel/Library/Developer/CoreSimulator/Devices/79ACB2AD-5155-4798-8E96-649964CB274E/data/Containers/Bundle/Application/A10C7802-3F4B-445D-A390-B6A84D8071EE/soundboard.app/': error -42
2022-07-02 21:27:28.330414+0200 soundboard[23592:612358] [default] SampleManager.cpp:435 about to throw -42: Failed to load sample
Sometimes the app crashes after that, but most importantly, sampler2 won't work.
Now, if I only create one of the samplers, it works as expected. Also, if both samplers reference to the same aupreset, it works. Only if I try to load different samples, I end up in undefined behavior.
Adding a audioEngine.detach(sampler)
before creating sampler2 doesn't solve the issue either - it will fail loading the samples with the same error. However, when deferring removal of sampler and creation of sampler2 a little bit, it magically starts working (though that's not what I want because I need both samplers simultaneously):
let sampler = AVAudioUnitSampler()
self.audioEngine.attach(sampler)
self.audioEngine.connect(sampler, to: self.audioEngine.mainMixerNode, format: nil)
try! sampler.loadInstrument(at: Bundle.main.url(forResource: "SteinwayPiano-v1", withExtension: "aupreset")!)
DispatchQueue.main.async {
self.audioEngine.detach(sampler)
let sampler2 = AVAudioUnitSampler()
self.audioEngine.attach(sampler2)
self.audioEngine.connect(sampler2, to: self.audioEngine.mainMixerNode, format: nil)
try! sampler2.loadInstrument(at: Bundle.main.url(forResource: "Rhodes-v1", withExtension: "aupreset")!)
}
My samples are linked to the bundle as a folder alias - and I have a feeling that there is some exclusive lock... however I don't have the source code to debug the errors on the console further. Any help is appreciated, have a good one :)
Ok, it turned out that is was not a bug in AVAudioUnitSampler - running the same code on macOS gave some additional hints...
unable to obtain configuration from file:///Library/Preferences/com.apple.ViewBridge.plist due to Error Domain=NSCocoaErrorDomain Code=256 "(null)" UserInfo={NSFilePath=/Library/Preferences/com.apple.ViewBridge.plist, NSUnderlyingError=0x600003a9e610 {Error Domain=NSPOSIXErrorDomain Code=24 "Too many open files"}}
...with the second sampler, the app exceeded the soft limit for currently opened files per process (=256). The crashes afterwards were caused by failing file handles in other parts of the app. A workaround can be found at https://stackoverflow.com/a/62074374/1993349 ... done.