Skip to content

Commit

Permalink
Add method to obtain precise audio sample rate
Browse files Browse the repository at this point in the history
Actual LRClk I2S sample rate is not precisely
equal to the "ideal" sample rate due to
peripheral clock division requirements.

This adds a method to return a float
representing the "actual" hardware
audio sample rate for more precise DSP
  • Loading branch information
ndonald2 committed May 16, 2024
1 parent f7727ed commit 11b5b84
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 42 deletions.
13 changes: 12 additions & 1 deletion src/hid/audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class AudioHandle::Impl
// Interface
AudioHandle::Result Init(const AudioHandle::Config config, SaiHandle sai);
AudioHandle::Result
Init(const AudioHandle::Config config, SaiHandle sai1, SaiHandle sai2);
Init(const AudioHandle::Config config, SaiHandle sai1, SaiHandle sai2);
AudioHandle::Result DeInit();
AudioHandle::Result Start(AudioHandle::AudioCallback callback);
AudioHandle::Result Start(AudioHandle::InterleavingAudioCallback callback);
Expand Down Expand Up @@ -60,6 +60,12 @@ class AudioHandle::Impl

float GetSampleRate() { return sai1_.GetSampleRate(); }

float GetPreciseSampleRate(uint32_t sai_idx)
{
return sai_idx == 0 ? sai1_.GetPreciseSampleRate()
: sai2_.GetPreciseSampleRate();
}

AudioHandle::Result SetPostGain(float val)
{
if(val <= 0.f)
Expand Down Expand Up @@ -525,6 +531,11 @@ float AudioHandle::GetSampleRate()
return pimpl_->GetSampleRate();
}

float AudioHandle::GetPreciseSampleRate(uint32_t sai_idx)
{
return pimpl_->GetPreciseSampleRate(sai_idx);
}

AudioHandle::Result
AudioHandle::SetSampleRate(SaiHandle::Config::SampleRate samplerate)
{
Expand Down
33 changes: 19 additions & 14 deletions src/hid/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
namespace daisy
{
/** @brief Audio Engine Handle
* @ingroup audio
* @ingroup audio
* @details This class allows for higher level access to an audio engine.
* If you're using a SOM like the DaisySeed or DaisyPatchSM (or any
* If you're using a SOM like the DaisySeed or DaisyPatchSM (or any
* board that includes one of those objects) then the intialization
* is already taken care of.
* If you're setting up your own custom hardware, or need to make changes
Expand Down Expand Up @@ -49,17 +49,17 @@ class AudioHandle
};

/** Non-Interleaving input buffer
* Buffer arranged by float[chn][sample]
* Buffer arranged by float[chn][sample]
* const so that the user can't modify the input
*/
typedef const float* const* InputBuffer;

/** Non-Interleaving output buffer
* Arranged by float[chn][sample]
* Arranged by float[chn][sample]
*/
typedef float** OutputBuffer;

/** Type for a Non-Interleaving audio callback
/** Type for a Non-Interleaving audio callback
* Non-Interleaving audio callbacks in daisy will be of this type
*/
typedef void (*AudioCallback)(InputBuffer in,
Expand All @@ -72,12 +72,12 @@ class AudioHandle
*/
typedef const float* InterleavingInputBuffer;

/** Interleaving Output buffer
/** Interleaving Output buffer
** audio is prepared as { L0, R0, L1, R1, . . . LN, RN }
*/
typedef float* InterleavingOutputBuffer;

/** Interleaving Audio Callback
/** Interleaving Audio Callback
* Interleaving audio callbacks in daisy must be of this type
*/
typedef void (*InterleavingAudioCallback)(InterleavingInputBuffer in,
Expand All @@ -102,7 +102,7 @@ class AudioHandle
/** Returns the Global Configuration struct for the Audio */
const Config& GetConfig() const;

/** Returns the number of channels of audio.
/** Returns the number of channels of audio.
**
** When using a single SAI this returns 2, when using two SAI it returns 4
** If no SAI is initialized this returns 0
Expand All @@ -111,20 +111,25 @@ class AudioHandle
*/
size_t GetChannels() const;

/** Returns the Samplerate as a float */
/** Returns the (idealized) Samplerate as a float */
float GetSampleRate();

/** Returns the actual, hardware-precise Samplerate as a float
* @param sai_idx The index (0 or 1) of the SAI to use as the source of the sample rate.
*/
float GetPreciseSampleRate(uint32_t sai_idx = 0);

/** Sets the samplerate, and reinitializes the sai as needed. */
Result SetSampleRate(SaiHandle::Config::SampleRate samplerate);

/** Sets the block size after initialization, and updates the internal configuration struct.
** Get BlockSize and other details via the GetConfig
** Get BlockSize and other details via the GetConfig
*/
Result SetBlockSize(size_t size);

/** Sets the amount of gain adjustment to perform before and after callback.
** useful if the hardware has additional headroom, and the nominal value shouldn't be 1.0
**
** useful if the hardware has additional headroom, and the nominal value shouldn't be 1.0
**
** \param val Gain adjustment amount. The hardware will clip at the reciprical of this value. */
Result SetPostGain(float val);

Expand All @@ -139,8 +144,8 @@ class AudioHandle
/** Starts the Audio using the non-interleaving callback. */
Result Start(AudioCallback callback);

/** Starts the Audio using the interleaving callback.
** For now only two channels are supported via this method.
/** Starts the Audio using the interleaving callback.
** For now only two channels are supported via this method.
*/
Result Start(InterleavingAudioCallback callback);

Expand Down
45 changes: 33 additions & 12 deletions src/per/sai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SaiHandle::Impl

// Utility functions
float GetSampleRate();
float GetPreciseSampleRate() const;
size_t GetBlockSize();
float GetBlockRate();

Expand All @@ -43,6 +44,9 @@ class SaiHandle::Impl
/** Offset stored for weird inter-SAI stuff.*/
size_t dma_offset;

/** Precise sample rate based on peripheral clock division */
float sr_precise;

/** Callback that dispatches user callback from Cplt and HalfCplt DMA Callbacks */
void InternalCallback(size_t offset);

Expand Down Expand Up @@ -201,6 +205,14 @@ SaiHandle::Result SaiHandle::Impl::Init(const SaiHandle::Config& config)
return Result::ERR;
}

uint32_t per_freq = (sai_idx == 0)
? HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI1)
: HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI2);
uint32_t mck_div = sai_a_handle_.Init.Mckdiv;

// Actual LRClk sample rate is subject to integer peripheral clock division
sr_precise = static_cast<float>(per_freq) / (mck_div * 256.0f);

return Result::OK;
}

Expand Down Expand Up @@ -230,10 +242,10 @@ void SaiHandle::Impl::InitDma(PeripheralBlock block)
hsai = &sai_a_handle_;
hdma = &sai_a_dma_handle_;
dir = config_.a_dir == Config::Direction::RECEIVE
? DMA_PERIPH_TO_MEMORY
: DMA_MEMORY_TO_PERIPH;
req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_A
: DMA_REQUEST_SAI2_A;
? DMA_PERIPH_TO_MEMORY
: DMA_MEMORY_TO_PERIPH;
req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_A
: DMA_REQUEST_SAI2_A;

if(sai_idx == int(Config::Peripheral::SAI_1))
hdma->Instance = DMA1_Stream0;
Expand All @@ -245,10 +257,10 @@ void SaiHandle::Impl::InitDma(PeripheralBlock block)
hsai = &sai_b_handle_;
hdma = &sai_b_dma_handle_;
dir = config_.b_dir == Config::Direction::RECEIVE
? DMA_PERIPH_TO_MEMORY
: DMA_MEMORY_TO_PERIPH;
req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_B
: DMA_REQUEST_SAI2_B;
? DMA_PERIPH_TO_MEMORY
: DMA_MEMORY_TO_PERIPH;
req = sai_idx == int(Config::Peripheral::SAI_1) ? DMA_REQUEST_SAI1_B
: DMA_REQUEST_SAI2_B;

if(sai_idx == int(Config::Peripheral::SAI_1))
hdma->Instance = DMA1_Stream1;
Expand Down Expand Up @@ -349,6 +361,10 @@ float SaiHandle::Impl::GetSampleRate()
default: return 48000.f;
}
}
float SaiHandle::Impl::GetPreciseSampleRate() const
{
return sr_precise;
}
size_t SaiHandle::Impl::GetBlockSize()
{
// Buffer handled in halves, 2 samples per frame (1 per channel)
Expand All @@ -365,10 +381,10 @@ void SaiHandle::Impl::InitPins()
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_TypeDef* port;
dsy_gpio_pin* cfg[] = {&config_.pin_config.fs,
&config_.pin_config.mclk,
&config_.pin_config.sck,
&config_.pin_config.sa,
&config_.pin_config.sb};
&config_.pin_config.mclk,
&config_.pin_config.sck,
&config_.pin_config.sa,
&config_.pin_config.sb};
// Special Case checks
dsy_gpio_pin sck_af_pin = {DSY_GPIOA, 2};
is_master = (config_.a_sync == Config::Sync::MASTER
Expand Down Expand Up @@ -570,6 +586,11 @@ float SaiHandle::GetSampleRate()
return pimpl_->GetSampleRate();
}

float SaiHandle::GetPreciseSampleRate()
{
return pimpl_->GetPreciseSampleRate();
}

size_t SaiHandle::GetBlockSize()
{
return pimpl_->GetBlockSize();
Expand Down
35 changes: 20 additions & 15 deletions src/per/sai.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@

namespace daisy
{
/**
/**
* Support for I2S Audio Protocol with different bit-depth, samplerate options
* Allows for master or slave, as well as freedom of selecting direction,
* Allows for master or slave, as well as freedom of selecting direction,
* and other behavior for each peripheral, and block.
*
*
* DMA Transfer commands must use buffers located within non-cached memory or use cache maintenance
* To declare an unitialized global element in the DMA memory section:
* int32_t DSY_DMA_BUFFER_SECTOR my_buffer[96];
*
* Callback functions will be called once per half of the buffer. In the above example,
* Callback functions will be called once per half of the buffer. In the above example,
* the callback function would be called once for every 48 samples.
*
*
* Use SAI Handle like this:
*
*
* SaiHandle::Config sai_config;
* sai_config.periph = SaiHandle::Config::Peripheral::SAI_1;
* sai_config.sr = SaiHandle::Config::SampleRate::SAI_48KHZ;
Expand Down Expand Up @@ -70,7 +70,7 @@ class SaiHandle
SAI_32BIT,
};

/** Specifies whether a particular block is the master or the slave
/** Specifies whether a particular block is the master or the slave
** If both are set to slave, no MCLK signal will be used, and it is
** expected that the codec will have its own xtal.
*/
Expand Down Expand Up @@ -118,16 +118,16 @@ class SaiHandle
/** Returns the current configuration */
const Config& GetConfig() const;

/** Callback Function to be called when DMA transfer is complete and half complete.
/** Callback Function to be called when DMA transfer is complete and half complete.
** This callback is prepared however the data is transmitted/received from the device.
** For example, using an AK4556 the data will be interleaved 24bit MSB Justified
**
**
** The hid/audio class will be allow for type conversions, de-interleaving, etc.
*/
typedef void (*CallbackFunctionPtr)(int32_t* in, int32_t* out, size_t size);

/** Starts Rx and Tx in Circular Buffer Mode
** The callback will be called when half of the buffer is ready,
/** Starts Rx and Tx in Circular Buffer Mode
** The callback will be called when half of the buffer is ready,
** and will handle size/2 samples per callback.
*/
Result StartDma(int32_t* buffer_rx,
Expand All @@ -138,15 +138,20 @@ class SaiHandle
/** Stops the DMA stream for the SAI blocks in use. */
Result StopDma();

/** Returns the samplerate based on the current configuration */
/** Returns the (idealized) samplerate based on the current configuration */
float GetSampleRate();

/** Returns the number of samples per audio block
/** Returns the exact samplerate based on the current configuration.
* This is only valid after successful initialization.
*/
float GetPreciseSampleRate();

/** Returns the number of samples per audio block
** Calculated as Buffer Size / 2 / number of channels */
size_t GetBlockSize();

/** Returns the Block Rate of the current stream based on the size
** of the buffer passed in, and the current samplerate.
/** Returns the Block Rate of the current stream based on the size
** of the buffer passed in, and the current samplerate.
*/
float GetBlockRate();

Expand Down

0 comments on commit 11b5b84

Please sign in to comment.