• Hardware
  • 24-bit audio input/output @ 48kHz?

Hi,
The current Bela design has 16-bit input/output @ 44.1 kHz https://github.com/BelaPlatform/Bela/wiki/Hardware%20Explained.
Are there any limitation in the BeagleBone for this? Could the cape be redesigned to get 24-bit input/output @ 48 kHz?
I would appreciate if I can have an idea of what changes would I have to make to the hardware/software to
achieve this. Any pointers or related projects would also be most welcome.

Regards,
Murray

Hi,
on-board audio codec is capable of 96kHz/24bit.
The reason why the clock is 44.1kHz is because digital I/O and analog I/O are also clocked by the audio codec and these tasks take time to complete and if the audio sampling rate was higher there would not be enough time for them to run. If you were to disable analog and digital I/O you could probably easily get to 48kHz without further modifications.

The reason why the bit-depth in use is 16bit is because the noise floor on the board would make the additional 8bits pretty much useless. Also, operating on 16bit values simplifies the code and requires less memory (the PRU, which handles the I/O, has access to 12kB of RAM only), allowing larger blocksizes. Similarly, another good reason for 44.1kHz is that the """"upgrade"""" to 48kHz would probably go unnoticed given the noise floor and it would just require more computations.

All of this said, if you add this in render.cpp

#include <I2c_Codec.h>
extern I2c_Codec *gAudioCodec;

and then in the render() function you add

    static bool init = false;
    if(init == false){
        init = true;
		gAudioCodec->setPllD(5277);
		gAudioCodec->setPllP(7);
               gAudioCodec->setAudioSamplingRate(48000);
		printf("sampling rate: %f\n",gAudioCodec->getAudioSamplingRate());
    }

there you should have your codec running at 48kHz.
To understand what this does, have a look at the comments in core/I2c_Codec.cpp

Note that this does not change the value returned by context->audioSamplingRate

Thank you very much for your answer and explanation giuliomoro.

a month later

Hi,
Im also interested in this 🙂

I had a quick look at setAudioSamplingRate, and it appears to calculate and call setPllD (via setPllK), so seems not required. could PllP not be similarly calculated? (or in other works how did you determine it should be 7 for 48k, vs 8 for 44k?)

also could this not be set in setup()?

Im wondering, if perhaps the the codec could be initialised just after setup time, from the audioSamplingRate context, which you could set...

i.e. we'd have something like

setup() {
context->setAudioSamplingRate(48000);
}

Im not after analog/digital io for now, but do think its possible to do this at a different clock rate?
you already appear to set the rate for analog sampling as a division of SR due to number of IOs no?
... so id be happy to reduce this rate, to say SR/4 if using higher SR.

you could set all the values manually. setAudioSamplingRate is a convenience method that allows fine adjustments without having to recompute the PLL dividers/multipliers by hand every time.

If you were to do this

setup() {
  context->setAudioSamplingRate(48000); 
}

then it would be overwritten when the actual codec initialization takes place. Refactoring the core code would probably allow it, but I do not see the point, given that this hack is not supported.

Not sure what you mean by

Im not after analog/digital io for now, but do think its possible to do this at a different clock rate?
you already appear to set the rate for analog sampling as a division of SR due to number of IOs no?
... so id be happy to reduce this rate, to say SR/4 if using higher SR.

context->analogSampleRate is just a scaled version of context->audioSampleRate, but, again, if you change the clock with the trick above, this is going to affect the analog sample rate, without this being reflected in the context.

Refactoring the core code would probably allow it, but I do not see the point, given that this hack is not supported.

yeah, I was hoping that perhaps it could be considered as a supported option... if it was not a difficult thing to do.

in my case, Im using some c++ code that assumes 48k, which to change to 44k would be difficult - so I'm using this.
the alternative would be to downsample, but I don't think thats trivial either ... and presumably going to burn a few cpu cycles.

I am actually quite surprised that it currently runs at 48kHz, I remember pretty clearly that 12 months ago I could not push it past about 45.5kHz. Maybe some of the recent modifications to the PRU code saved some nanoseconds in the loop? As we are planning to go back to this shortly and stuff more operations in the PRU loop, which would probably make it impossible to run it at 48kHz, I think we should leave it as it is.

Although it would make sense to allow to use LOWER sampling rates in a user-friendly way ...

Also, note that calling

    static bool init = false;
    if(init == false){
        init = true;
		gAudioCodec->setPllD(5277);
		gAudioCodec->setPllP(7);
                gAudioCodec->setAudioSamplingRate(48000);
		printf("sampling rate: %f\n",gAudioCodec->getAudioSamplingRate());
    }

in render() WILL cause two mode switches in the audio thread (but only the first time render() is executed and the I2C calls are performed, so this is probably not a big deal).

Im a bit confused why this is so problematic for Bela...
Axoloti runs on an STM32F429, so M4 180mhz at 48kHz without issue, so why does a BeagleBone Black have issues given its an A8 running at 1Ghz. both are using block sizes of 16, by defaults, so at 48khz thats 3000/second...

Because of this:
https://github.com/BelaPlatform/Bela/blob/master/pru_rtaudio.p

The code between lines 922 and 1094 needs to run in half sampling period (that is 11.34us at 44.1kHz). The code in there performs audio I/O, digital I/O and analog I/O. Most instructions take 5ns, except those that involve memory , e.g.:

    //it takes 190ns to go through the next instruction
    LBBO r2, r2, 0, 4

Additionally, the PRU has to stop waiting for the audio codec on the McASP and the ADC and DAC on the SPI bus.
e.g.: usually ADC_WAIT_FOR_FINISH usually waits for 1.14us.
There are 2x ADC_WAIT_FOR_FINISH and 2x DAC_WAIT_FOR_FINISH in each loop and these quickly add up to fill all the time available, so much that when we added the digitals (which make quite a lot of use of memory I/O), we had to add them in between waiting for ADC_WAIT_FOR_FINISH, instead of a busy wait.

To be clear, this isn't a fundamental limitation of Bela per se, it's a limitation of how the PRU code is organised. There are a number of optimisations which could be made to the PRU code, for example using local interrupts rather than polling the MCASP and SPI peripherals, or moving the analog and digital IO onto the other PRU to give the audio loop more time.

The problem is really the amount of testing which would be required to ensure any new PRU code had the same hard real-time performance as the current implementation.

6 months later

We are looking to develop a Prosumer product with the Bela platform and need 24 bit performance. Our final hardware design and board layout will yield S/N of 118db+, which warrants 24 bit performance. We would like to use the bela Kernal and tools in order to accelerate our development time. Is there a way to enable 24 bit in the Kernal and tools or would that require deep level customizations?

17 days later

I doubt they'd be interested in helping competitors.