Hi folks
I've been working on an improved (I think) set of objects for playing and recording WAV files from filesystems, principally SD card. You can find the preliminary effort at https://github.com/h4yn0nnym0u5e/Aud...re/buffered-SD.
The main design goals were:
- play / record multiple files simultaneously, without glitching
- support for multi-channel files (1, 2, 4, 6 and 8 channels)
- removing filesystem accesses from the audio interrupt
My own testing so far has been minimal - I've got as far as successfully playing back 16 mono files while mixing them and recording to 2 stereo files, using a reasonably fresh SanDisk Extreme 64Gb SDXC card, and still having some CPU time left to do other stuff with. Clearly more testing is needed, it's highly likely that there are bugs, I need to do some demos and more documentation, and there may well be features that it would be useful to add. It would be great if those of you who have a use for such a beast would give it a go and report back on how well (or badly...) it went, to help me decide how best to spend my time on this ... or, indeed, whether I should just shelve the whole thing because no-one needs it!
The objects work pretty much as you might expect: I've updated the Audio Design Tool to allow you to add them to your sketches, and they include the usual info in the right-hand pane on the various functions they provide.
Technical stuff
The absolutely key function, which is dissimilar to the ancestral AudioPlaySdWav object, is createBuffer(). If you don't call this for every object then you'll almost certainly get poor results! It allocates buffer memory to allow filesystem reads to be a sensible size. This not only reduces the read frequency (and hence CPU overhead), but also means that if there is the occasional delay there is some chance the buffers will have enough data that the audio won't glitch. The buffer can be allocated either in heap or in PSRAM; the latter doesn't have as much impact as you might think, because transfers between SD and PSRAM seem to be about as fast as between SD and fast RAM. For the above test I allocated 64k for each playback buffer, and 128k for each record buffer, using a total of 1.25MB of the 8MB PSRAM.
The other thing to be aware of is that filesystem transfers happen outside the audio interrupt updates, within the context of your application: this is done using the EventResponder library, which means that your application must call yield() reliably often. You can do this by allowing loop() to exit, or by explicitly calling yield() in your application, or by using delay() (if you must...). There may be other library calls which yield(), but I wouldn't want to rely on them myself. "Reliably often" is hard to quantify, but the bigger your buffers the less often you need to do it. Buffer refills trigger when they're half empty, so a 64k buffer will be refilled when it gets below 32k; if you're using it for a mono WAV file that will be every 371ms; with 16 files playing, a refill will be needed on average every 23ms. I have made some effort to ensure buffer refills are staggered in time, so there's a fighting chance your application won't stutter because 16 reads taking 5ms each have all triggered at once.
For playback the buffers are pre-loaded when the playSD() function is called; because this can take some time, if synchronous starting of multiple files is important, you may wish to use cueSD() to prepare them all, followed by play() to set them going.
Roadmap
I'd really like to get feedback on:
- bugs and misfeatures
- simple demo sketches of cool stuff
- missing features
- bits you don't understand
Things I'm vaguely considering include:
- demos and documentation
- adding hooks to allow use of file formats other than 44kHz 16-bit PCM
- instrumentation so we can e.g. check performance, buffer margins etc.
- looping / starting playback in the middle of a file