Having all but given up on getting USB audio to work reliably, I'm looking now at taking the stereo 16-bit I2S stream from the Raspberry Pi 4's 40-pin GPIO header.
Unfortunately, that signal stops when not playing anything (some people have made a daemon that constantly plays silence, but I'm not sure that I want to: it seems like an excellent opportunity to poorly document how to set up another one), and varies to match the sample rate of the material being played at the moment. And it's jittery anyway, because the standard audio rates don't have a nice relationship to that system clock.
So I probably don't want to slave my DSP chain to that.
If my input is that unreliable, then I'd much rather use a fixed independent rate for my DSP chain - 48kHz only - and resample the input as needed to match that. There's this Q&A about a fixed ratio between the two rates, but I don't think I can guarantee that.
I've thought of two ways so far, to resample a variable, unknown at design time, input rate to a fixed internal one:
Run a general-purpose lowpass at X times my fixed rate (with the coefficients of course calculated for that rate, which is different from the rest of the DSP chain), and just have its high-rate output available for the rest of the DSP chain to grab as needed. (implicit decimation?) The input is the new sample if we just got one, zero otherwise, and it has no idea about the I2S stream itself. It's always running, and either sees a new sample this time or it doesn't.
This (as it appears to me) makes a dynamic resampler that takes a wide range of possible input rates, even irregular ones, without reconfiguring. It even handles a stopped input, becoming actively silent until the input restarts.
- Opportunities to simplify or speed up? Most of its input samples are zero (but don't know which ones), most of its output samples aren't used (do know which of those), and it's running at a much higher rate than anything else...or at least, something is running faster than everything else, just to know when the new sample arrived. The actual lowpass code could be called at the normal rate and only produce one output sample, but use an X-sample buffer as its input instead of just one. (its coefficients still need to be calculated for the X-higher rate though)
So far, I've used a bunch of 1st-order IIR lowpasses in various SAR ADCs' interrupt handlers (algebraic rearrangement of an exponential average, using integer bit shifts instead of multiplication/division), and I think I know enough about a biquad to choose a seemingly attractive form and write it (and copypasta the coefficient calculators), but I haven't actually used one of those in a project yet. Likewise for state-variable.
I've looked at IIR more because of its similarity to the analog world. I understand the theory behind FIR, but the analog comparisons just don't work as well. I've read (and completely agree) that a FIR lowpass can be heavily optimized for resampling, because it doesn't need to calculate every output value for the sole purpose of feeding it back into the algorithm like IIR does; it only needs to calculate the output values that are actually used downstream, and no more. But, this logic skips a critical step: yes, IIR must calculate every output sample, even if most of them are only used internally, but there's so little going on in the first place, compared to a similar-response FIR, that it may still come out ahead, even after the FIR is optimized. I've never seen that answered, always jumped over to say that "FIR is better for resampling," which I'm not convinced yet is always true. But I certainly could be convinced!
This is very much a learning experience!
Run it through a D/A/D conversion. Let an off-the-shelf DAC figure it out, and then pick that up with an ADC right next to it on the same custom PCB.
I'd rather do it all digitally if I can, since I have another project in the idea phase that needs to receive 8 channels or so from a PC of some kind. A single stereo pair might be manageable with this method, but 4 of them seems like a bit much! I'd like to copy/paste the code from this project into that one if I can.
That other project will definitely need something better, but for this one, a stereo-to-2.1 converter with system processing and amps included, it seemed to me like it might fit on a $4 Raspberry Pi Pico. (datasheet) So that's what I'm running on so far.
Dual M0+, so it doesn't have a hardware FPU, but it does have a hand-optimized floating-point library in ROM. 133MHz.
Why can't I just use an off-the-shelf chip and library like Analog Devices' ADAU series and SigmaStudio?
I certainly could for the immediate project - stereo to 2.1 - but I also want to learn how to write my own DSP code, because I think I have to for the other one. The other one has a budget of maybe 20 samples total at 96kHz, analog to analog. (separate from the PC connection) That is, measured by an external stereo ADC with one channel probing the analog input and the other at the analog output, so it includes the converters' group delay as well. Meanwhile, all the libraries I've seen so far have used a bigger buffer than that, not counting the converters.