Understanding DSP code, specifically FillBuffer()

#1

Hi,

I’ve been poking around the code posted on github, mainly concentrating on the clouds module hoping to learn a few things.

Things are going ok, there is one sticking point however, I can not figure out how FillBuffer() is called, sorry if this is a really dumb question, but any help understanding this would be appreciated.

#2

FillBuffer is passed as an argument to the codec initialization routine. In drivers/codec.cc you’ll see that it is invoked in Codec::Fill, which is itself invoked whenever 50% of a DMA transfer on the I2S peripheral is completed.

#3

Thanks for the quick reply!

Ok, I’ll look into this, think that’s enough to get me started.

Thanks again.

#4

Apologies for necrobumping but I’ve been digging into some of this myself and this thread seemed like a good place to ask. FillBuffer() makes sense, but what I’m struggling with is making sense of the various buffers used (taking Clouds as an example).

Specifically, I’m having trouble drawing the connections between the workspace buffers allocated by GranularProcessor and the DMA buffers initialized by Codec — my understanding is that they are different buffers but at some point the contents of the workspace buffers will need to be transferred to the DMA tx/rx buffers and sent out to the codec. Where does this actually happen?

#5

The workspace buffers in GranularProcessor are for recording audio, they don’t have anything to do with reading from/writing to the codec.

In short:

  • The DMA controller handles transfers from/to the codec, and this happens in the following buffers: Codec:: tx_dma_buffer_ and ```Codex:: rx_dma_buffer_````.
  • Whenever we start transmitting/receiving one half of the buffer, we call whatever function is pointed at FillBufferCallback and give it a pointer to the other half of the buffer, so that it is processed. That’s classic double buffering. Practically, FillBufferCallback points to clouds.cc's FillBuffer.
  • FillBuffer does a couple of things (handling the VU-meter, reading CVs), then passes these pointers to GranularProcessor::Process for the actual cloudsing.
  • At this point, the samples go through a fairly involved list of steps (stereo to mono mixing, mixing with the feedback signal, possibly some downsampling when the quality setting is set to half-sample-rate, granularization, diffusion, pitch-shifting, filtering, reverb). But we don’t really care about the original DMA buffers – we immediately read from them and convert the data to floats; and in the end whatever floats we have are converted back to integers and stuffed into the DMA buffers.

If the “buffer” you’re concerned with is the granularization buffer, look into GranularProcessor::ProcessGranular. You’ll see that the input samples are first recorded in the granular buffer (buffer_16_[i].WriteFade), and then, depending on the playback mode, various parts of this buffer are read in complicated ways (typically player_.Play(buffer_16_, parameters_, &output[0].l, size);).

2 Likes