K-/S-Rate, CPU Speed, C Code, and Multithreading


#1

Hi there. I'm very new to both Axoloti and coding (hoping to learn on the Axoloti), so sorry if what I ask is either obvious or doesn't make sense.

Anyways, I've been perusing the forums here, and thought of a question I couldn't find the answer to. In a nutshell, I'm wondering how both the k-rate and s-rate Hz numbers relate to both the number of lines in a code block and the processor's clock speed.

So far, I've picked up that k-rate code is 3 kHz, s-rate is 48 kHz, and the CPU's clock speed is 168 MHz. For the k-/s-rate numbers, does this mean that the Axoloti board will execute the entire code.krate block for an object 3,000 times a second, and the entire code.srate block 48,000 times a second? Or will it only execute 1 line of code in these blocks 3,000/48,000 times a second? Or will it do something else? Basically, what is it that is being done at the 3/48 kHz rates?

As for the CPU's clock speed, since it's much higher than the k-/s-rate speeds I'm assuming that there is some multithreading going on, such that the processor is executing multiple k-/s-rate things at the same time. Is this right? And if so, how does the multithreading work at the broad brush strokes level? Is it that each object has its own thread? Or is it ad hoc what gets its own thread? Or is there something I as a developer will need to do to control this aspect of things? Or is it something else?

Any thoughts you could spare for me would be a huge help. Thanks!

P.S. Feel free to move this to another category if 'Developer' isn't the right one.


#2

I'd preface this by saying that I'm fairly new to the axoloti firmware, so if others have better information/insights please correct me.

I'm wondering how both the k-rate and s-rate Hz numbers relate to both the
number of lines in a code block and the processor's clock speed.

They don't...

So far, I've picked up that k-rate code is 3 kHz s-rate is 48 kHz

Right. The codec/sai is setup to playback 48000 samples per second.
Each sample is a 32 bit fixed point (q5.27) quantity.
There are 2 channels of audio.

Some signals in a synth are relatively slow moving, E.g envelopes.
We can save memory and cpu by generating them at a slower rate.
In axoloti the factor is x16, so the k-rate signals have 3000 values/second.

and the CPU's clock speed is 168 MHz.

Right. The CPU is actually rated for 180 MHz, but ST in their wisdom made it impossible to run the USB with this CPU speed.

For the k-/s-rate numbers, does this mean that the Axoloti board will execute
the entire code.krate block for an object 3,000 times a second, and the entire
code.srate block 48,000 times a second?

Yes. If you write an some object code in the GUI and look at what gets generated you will see the following:

void dsp(blah...) {
  your_krate_code();
  for(i = 0 i < BUFSIZE; i ++) {
    your_srate_code();
  }
}

ie - your krate code gets called once and the srate code gets called in a 16x loop.
Some of the objects don't bother with the srate code. They just do everything (including the srate work) in the krate code specification.

16 x 1/48000 = 1/3 ms of signal. So: an object generating an srate signal must be called 3000 times/second.

As for the CPU's clock speed, since it's much higher than the k-/s-rate speeds
I'm assuming that there is some multithreading going on

Yes, in so far as the firmware uses chibios and chibios supports multiple concurrent tasks/threads.

such that the processor is executing multiple k-/s-rate things at the same
time. Is this right?

No. One thread is responsible for generating the audio buffers and that is "ThreadDSP".

Basically it works as follows:

The codec/SAI needs to be fed at a rate of 48000 samples/second (x 2 channels) to generate clean audio.

The SAI is fed by DMA from memory. The DMA is arranged in double buffering mode. ie: when the SAI is reading from one buffer the CPU can be busy filling in the other buffer. When the SAI is done with one buffer the DMA will be switched to the other buffer, so the CPU has to be done with filling it in before this happens.

Every time the DMA switches buffers an interrupt gets generated. This interrupt wakes up ThreadDSP and tells it to get busy computing the sound and filling in the output buffer the SAI is not currently reading from.

To do this the code calls the patch you have created. See: PatchProcess in xpatch.cpp

That code hooks up all the objects you have created and ultimately computes the 16 samples (x2 channels) needed for the audio output.

When you return from your patch the ThreadDSP code will time how long it took and see how much you beat the deadline by. This gives you the CPU load number.

So- the bigger the patch, the more complex the objects, the greater the number of voices, the longer it will take the CPU to compute the sound. Every 1/3 ms the ThreadDSP code will wake up and will go full speed trying to get the buffer filled before the SAI needs it.

Hope this helps.


#3

@deadsy Thanks so much for that. I think it gives me a good handle on things. But to double check that I'm on the right page, the main outline is that Axoloti has two buffers, and at any time one of the buffers is being read from (and I assume output through the audio out?) while the other is being written to. ThreadDSP is the function that does the writing, and the way ThreadDSP does this is by calling the PatchProcess function from xpatch.cpp, which in turn calls my k- and s-rate code through the void dsp(...) function you sketched out above. Finally, ThreadDSP checks to see how much time it had left after it finishes writing to the buffer, and uses that to show me the CPU load. Is that on the right track?

Also, reading through your response made me think of a few extra questions. First, are there two buffers because Axoloti has a stereo out and needs one each for the left and right outs, or for some other reason?

Second, does the dsp(...) function get generated for each object in a patch, or for the entire patch?

Third, do you happen to know where I would find the (I assume .c or .cpp) files that include the dsp(...) function? All I've been able to find are the .axo/.axp files that (if I understand correctly) are used to generate the files I'm looking for now.

And last, do you know where I would find the xpatch.cpp file and the file(s) that have the ThreadDSP code? I've found an xpatch.h file under the axoloti-runtime/firmware folder, but this doesn't look to be the file you're talking about. And I haven't had any luck finding ThreadDSP.

Thanks again for the help!


#4

Is that on the right track?

Yes.

are there two buffers because Axoloti has a stereo out and needs one each for the left and right outs, or for some other reason?

well, there are actually 4 defined in ./axoloti/firmware/codec.c

int32_t buf[BUFSIZE*2] __attribute__ ((section (".sram2")));
int32_t buf2[BUFSIZE*2] __attribute__ ((section (".sram2")));
int32_t rbuf[BUFSIZE*2] __attribute__ ((section (".sram2")));
int32_t rbuf2[BUFSIZE*2] __attribute__ ((section (".sram2")));

buf/buf2 are for audio output (dac).
rbuf/rbuf2 are for audio input (adc).

For both input and output there are 2 buffers so they can operate in the aforementioned ping-pong fashion. While the codec is busy with one buffer the cpu is busy with the other buffer. That's a standard DSP technique and has nothing to do with stereo output.

You will notice a BUFSIZE*2 for the buffers. The x2 is for the left/right stereo channels. The CPU has to compute 2 channels worth of audio data.

does the dsp(...) function get generated for each object

For each object.

where I would find the (I assume .c or .cpp) files that include the dsp(...) function?

I find it in ./axoloti/build/xpatch.cpp
I'm building the code from source, so it may be different if you have a binary install- but you'll find the generated patch code somewhere amongst the axoloti files. (find | grep xpatch)

ThreadDSP

./axoloti/firmware/patch.c

static msg_t ThreadDSP(void *arg) {

#5

@deadsy Thanks so much for the follow up! I have a good overview of these things now, and a manageable number of files to start looking over for the details.