Recommend SPI Flash chip for T4.1 polyphonic playback

ryanrs

Active member
I'd like to use a T4.1 with a 32/64/128 mbyte spi flash for polyphonic audio playback (10+ streams possible?)

The streams are mono 16-bit 24kHz (or similar mid-quality). Processing will probably be limited to gain and short delay for spatial positioning of the mono streams in the stereo output.

I'm seeing different supported spi flash chips on the Audio Lib page and on the T4.1 flash expansion page. Maybe this is because of differences between the Audio Adapter spi interface and the T4.1 quad bit i/o? I've also seen discussion of larger chips in discussion threads, but I'm not sure if these are officially supported.

My application does not use PSRAM.

I would like to use the spi flash pads on the back of the T4.1 with quad i/o and high clock speeds. Can the Audio Lib 'playFlashRaw' play files off the T4.1 serial flash?

Can anyone recommend specific 128/64/32 MB chips that are known to work well on the T4.1 with the Audio library in this way? Larger is better, but not more than 128MB. Preferably in-stock at digikey or mouser.

Thanks!
 
I’m currently working on streaming off the T4.1 SD card, using internal heap or PSRAM as a buffer. So far I’m comfortably achieving 16 channels at 16-bit 44kHz from standard mono WAV files, off pretty much any card I throw at it. One card has tested really badly for write speed, but that’s probably not relevant to your use case. This would suggest that most SPI Flash should work, though I don’t have direct experience.

My code isn’t ready for prime time yet, but I could push it to GitHub as a work in progress if you wanted to give it a try out.
 
OK, if I can get 10-20 24 kHz voices directly of an SD card, then I won't use spi flash at all.

What blocksize do you read from the SD card? How much buffering is needed? It would be nice if I could run it out of the T4.1 internal ram instead of psram.
 
I've made the buffer size configurable at run-time, per object. The code reads half of the buffer at a time, when there's space available, and I'd strongly recommend making the buffer a multiple of 1024 bytes, so a half buffer is a multiple of 512 bytes or an SD card / filesystem sector. Recommendation based on not having tried anything else...

With a decent SD card (SanDisk Extreme 64Gb) I can get away with each track having a 4k buffer, in my test case of 16 tracks that's using about 64k of heap. It takes 9ms to do a full set of buffer re-fills, and that happens every 23ms, so I'd be reluctant to use less buffer than that. With a rubbish SD card I can't even get away with 8k buffers. Bumping them to 64k buffers (*16 = 1Mb) in PSRAM it's maybe workable, with normal operation taking 64ms to re-fill every 372ms; however, after a while some files seem to behave badly, and the re-fill time goes to about 190ms. I've not yet tried deliberately fragmenting the files!

If you want to try it out, you can find my additions at https://github.com/h4yn0nnym0u5e/Audio/tree/feature/buffered-SD. No documentation as yet, but:
  • additional files are AudioBuffer.cpp and .h, play_wav_buffered.cpp and .h, and oscope.h
  • put these in your Audio library folder, or clone the whole repo to your libraries folder
  • #include "play_wav_buffered.h" in your application
  • design as usual using AudioPlaySdWav objects
  • in your application, change the AudioPlaySdWav objects to be AudioPlayWAVbuffered
  • before you use an object, you MUST set the buffer, e.g. using track1.createBuffer(4096,AudioBuffer::inHeap);
  • every (used) object must have a buffer, but you only need to set it up once, maybe in setup()
  • you can then play files from SD, e.g. using track1.playSD("noisy.wav");
  • .pause() pauses playing, .play() with no parameters resumes from pause, .stop() stops playing
  • .cueSD() "starts" playing in paused mode, useful if you're trying to start multiple files simultaneously, because .play() pre-loads the buffer which can take a few milliseconds to execute

EDIT: note, only 44.1kHz files supported at the moment!
SECOND EDIT: this uses EventResponder to load the buffer, so your application must call yield() frequently; call it direct, use delay(), or allow loop() to exit
 
From a purely hardware perspective, NOR flash chips are probably the best storage media. Access latency is very low and always a fixed number of SPI clocks, which gives you the best capability to play many audio clips simultaneously. The main problem with NOR flash is relatively small storage. As far as I know, Winbond W25Q01JV is the largest supported today, which gives you 128 Mbyte. The WSON8 package can be (somewhat awkwardly) soldered to the pads on the bottom of Teensy 4.1 or the audio shield. I believe W25Q128JV at only 16 Mbyte is the largest available in SOIC8 package which is easier to solder.

SD cards have a wide range of command latency, which can also vary quite a lot in the same card depending on whatever internal media management the card might be doing at the moment you need access to stored data. Most newer cards have a "A1" or "A2" rating which give some assurance of lower latency, though still nowhere near as good as NOR flash. Of course you get massive storage size at low prices, no soldering is needed, and you can put it into a normal SD card reader or slot on your laptop to write the files.

The software side, I'm a bit embarrassed to admit, is not nearly as good as it should be. I'm really hoping to catch a break from semiconductor shortages and really work on this. Briefly, there are 2 main issues on the software to consider. 1: Contiguous storage on the media is better, because nothing else needs to be read to find the location of data during playback. 2: Buffering in the player can allow you to play more sounds simultaneously and still tolerate latency (either from the hardware or a filesystem reading metadata).

With SD cards, contiguous file storage is available with FAT64 (aka exfat) but not FAT32 or FAT16, so you'll want to use a card larger than 32GB. Creating files this way is only available from the SdFat API, but if using SD.h you can access the underlying SdFat functions. See the SdFat_Usage example. With NOR flash, your choices are the slow but fully featured LittleFS filesystem, or the old & limited but fast SerialFlash library.
 
The sheer convenience and size of SD cards is why I'm pursuing that route, though there's definitely a way to go yet. Even before I start attacking recording, my (externally generated) suite of test WAV files is 44 files totalling 495Mb, so I really didn't want to take the time to fight either "slow but fully featured" or "old & limited but fast" libraries.

An interesting observation is that buffering SD card reads into PSRAM seems reasonably low-impact, though de-buffering in the audio engine does cause a speed hit. Using PSRAM allows suitably huge buffers which should help with the latency issues; in a test environment I'm hoping to get to playing 16 and recording 4 16bit / 44.1kHz mono files without glitching, and while still allowing time for other processing. Without PSRAM you'd be more constrained, of course, but if you're going to solder something on, then the PSRAM may give you a more versatile platform.

More than happy to take on comments and suggestions for priorities, given that all I'm really attacking is your issue#2 (buffering).
 
Back
Top