This makes me wonder. Is this the right approach? Is AudioFormatReader::read safe to call on the audio callback?
Will the creation of the reader load and decode the whole data, so read only copies bytes?
Or will the creation of the reader load the file, and then read does both decoding and copying? (and is this decoding realtime-safe?)?
Or will read actually do file I/O as well as decoding and copying? (in which case it’s definitely not realtime-safe).
The documentation isn’t really clear on any of this.
Am I better off to not calling read in the callback at all, but just up-front copy all the data from the reader into my own buffer holding the whole sample, discard the reader, and then in the audio callback simply just copy the needed chunk of data from my own buffer into the host buffer?
This brings me to the next question: what is the purpose of the MemoryMappedAudioFormatReader? Memory-mapped file I/O is faster than regular file I/O, but memory-mapped access is certainly not a lock-free/realtime safe operation, so it sounds like this class wouldn’t offer any advantage for my use case. I imagine it would be useful in some kind of disk-streaming scenario where it would feed a FIFO of audio chunks on a File I/O thread that then would be picked up by the audio thread. But I’m not doing stuff like this now, I just want to implement a simple sampler with JUCE
Can anyone here shed more light on all this? Cheers
No, none of those classes are intended to be realtime-safe! They’re really just file i/o streams that give you samples instead of bytes. But good point that we should probably make the docs clearer about that, we do often see beginners using them on the audio thread. (Actually, maybe an assertion if you try to do that would be more helpful…)
And yes, MemoryMappedAudioFormatReader is just faster, that’s all. In the tracktion engine we do currently use memory-mapping to implement pre-caching so that we can use them on the audio thread (using other threads to pre-emptively poke bytes that are coming up to force the OS to map those bits into memory) but although this is probably optimally efficient in terms of overall performance, it does make it impossible to detect when there are underruns, so we’ll probably refactor it at some point into a more manual system of pre-caching.
Certainly the most bulletproof way to do it is by just reading the whole thing into memory. A less guaranteed but good-enough-in-normal-situations approach is to just use a BufferingAudioReader.
Thanks! I’ll read the whole thing into memory then.
Very interesting! What makes you think that after “poking bytes” there is actually a guarantee that your memory-mapped access will succeed? You can poke all you want, I think there is still a non-zero chance that your memory access will result in a page fault and the OS has to go back to the harddisk for that chunk of data. So I don’t think you can do this kind of memory access on the audio thread. Am I missing something?
This is a tangent, but very interesting also! How do I do such an assertion? I imagine it would be very useful for example to put such an assertion into a custom allocator to catch any unintentional memory allocations on the audio thread. That would be awesome actually. I wasn’t aware this is possible in JUCE? How?
Well, in practice it works very well indeed and is incredibly fast. But yes, like I said it has drawbacks. On the one hand it takes advantage of decades of OS optimisations in paging data into memory and predicting read patterns, which makes it very very efficient. On the other hand you’re at the mercy of the OS doing something weird, and when it does you can’t really detect what went wrong. So yeah, swings and roundabouts…
I thought we had a system for this but couldn’t find it… @t0m ?
But it’s pretty easy - add a thread-local flag to indicate we’re on a real time thread and then check it with an assertion in sensitive bits of code. Obviously not very good for a release build but a handy debugging trick
Well, the audio thread would obviously need to set and read the thread-local variable flag, and I’ve no idea what kind of crazy code the compiler will generate for that, so probably not a great thing to have in a release build.