Skip to content

Smaller audio buffers config option #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
108 changes: 101 additions & 7 deletions CircularBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,113 @@ Modified from https://en.wikipedia.org/wiki/Circular_buffer
Mirroring version
On 18 April 2014, the simplified version on the Wikipedia page for power of 2 sized buffers
doesn't work - cbIsEmpty() returns true whether the buffer is full or empty.

April 2025: modified for different buffer sizes under the suggestion
of Meebleeps (https://github.com/sensorium/Mozzi/issues/281)
*/

#define MOZZI_BUFFER_SIZE 256 // do not expect to change and it to work.
// just here for forward compatibility if one day
// the buffer size might be editable

/** Circular buffer object. Has a fixed number of cells, set to 256.

/** Circular buffer object. Has a fixed number of cells, set by BUFFER_SIZE.
@tparam ITEM_TYPE the kind of data to store, eg. int, int8_t etc.
@tparam BUFFER_SIZE the size of the circular buffer
*/
template <class ITEM_TYPE>
template <class ITEM_TYPE, int16_t BUFFER_SIZE>
class CircularBuffer
{

public:
/** Constructor
*/
CircularBuffer(): start(0),end(0),s_msb(0),e_msb(0)
{
}

inline
bool isFull() {
return end == start && e_msb != s_msb;
}

inline
bool isEmpty() {
return end == start && e_msb == s_msb;
}

inline
void write(ITEM_TYPE in) {
items[end] = in;
//if (isFull()) cbIncrStart(); /* full, overwrite moves start pointer */
cbIncrEnd();
}

inline
ITEM_TYPE read() {
ITEM_TYPE out = items[start];
cbIncrStart();
return out;
}

inline
unsigned long count() {
return (num_buffers_read << COUNT_LSHIFT) + start;
}
inline
ITEM_TYPE * address() {
return items;
}

private:
ITEM_TYPE items[BUFFER_SIZE];
uint8_t start; /* index of oldest itement */
uint8_t end; /* index at which to write new itement */
uint8_t s_msb;
uint8_t e_msb;
unsigned long num_buffers_read;
static constexpr unsigned long COUNT_LSHIFT =
(BUFFER_SIZE == 256) ? 8 :
(BUFFER_SIZE == 128) ? 7 :
(BUFFER_SIZE == 64) ? 6 :
(BUFFER_SIZE == 32) ? 5 :
(BUFFER_SIZE == 16) ? 4 :
(BUFFER_SIZE == 8) ? 3 :
(BUFFER_SIZE == 4) ? 2 :
(BUFFER_SIZE == 2) ? 1 : 0;

inline
void cbIncrStart() {
start++;
if (start == BUFFER_SIZE)
{
start = 0;
s_msb ^= 1;
num_buffers_read++;
}
}

inline
void cbIncrEnd()
{
end++;
if (end == BUFFER_SIZE)
{
end = 0;
e_msb ^= 1;
}
}


};



/** Circular buffer object. Specialization for size of 256.
Note: Lot of duplication but C++ does not allow for specialization of the
function member only (partial specialization).
@tparam ITEM_TYPE the kind of data to store, eg. int, int8_t etc.
*/
template <class ITEM_TYPE>
class CircularBuffer<ITEM_TYPE, 256>
{
public:
/** Constructor
*/
Expand Down Expand Up @@ -68,7 +162,7 @@ class CircularBuffer
}

private:
ITEM_TYPE items[MOZZI_BUFFER_SIZE];
ITEM_TYPE items[256];
uint8_t start; /* index of oldest itement */
uint8_t end; /* index at which to write new itement */
uint8_t s_msb;
Expand All @@ -90,5 +184,5 @@ class CircularBuffer
end++;
if (end == 0) e_msb ^= 1;
}

};

17 changes: 17 additions & 0 deletions config/mozzi_config_documentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,23 @@
* */
#define MOZZI_AUDIO_PIN_1 FOR_DOXYGEN_ONLY

/** @ingroup config
* @def MOZZI_OUTPUT_BUFFER_SIZE
*
* @brief Audio buffer setting.
*
* For a lot of outputting modes, Mozzi is buffering the audio samples in order to be able to coop with varying loads on the processor.
* The bigger the buffer, the more able Mozzi will be to coop with big change of processor loads as the buffered values can compensate for that.
* At the same time, a bigger buffer produces a bigger latency as the time between when Mozzi produces the sample and the time it is actually outputted increases. For instance, for a long time Mozzi's buffer size was of a fixed size of 256. This produces a potential latency of 15.6 ms for a MOZZI_AUDIO_RATE of 16384, and half this value for a MOZZI_AUDIO_RATE of 32768.
* Depending on the application, this is usually not a problem but can lead to synchronisation issues in some cases (for instance when working with clocks).
* MOZZI_OUTPUT_BUFFER_SIZE can be reduced to smaller values with this config, leading to more accurate timings but potentially to glitches if the buffer runs low.
* Valid values are power of two from 256 downward (128, 64, …).
* Note that this might not have an effect in all modes/platforms combination as Mozzi is sometimes using an external buffer which is not always configurable.
*
* TODO: Throw a warning if config does not have an effect
*/
#define MOZZI_OUTPUT_BUFFER_SIZE FOR_DOXYGEN_ONLY


/***************************************** ADVANCED SETTTINGS -- External audio output ******************************************
*
Expand Down
4 changes: 2 additions & 2 deletions internal/MozziGuts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ inline void bufferAudioOutput(const AudioOutput f) {
++samples_written_to_buffer;
}
#else
CircularBuffer<AudioOutput> output_buffer; // fixed size 256
CircularBuffer<AudioOutput, MOZZI_OUTPUT_BUFFER_SIZE> output_buffer;
# define canBufferAudioOutput() (!output_buffer.isFull())
# define bufferAudioOutput(f) output_buffer.write(f)
static void CACHED_FUNCTION_ATTR defaultAudioOutput() {
Expand Down Expand Up @@ -150,7 +150,7 @@ uint16_t getAudioInput() { return audio_input; }

#if MOZZI_IS(MOZZI__LEGACY_AUDIO_INPUT_IMPL, 1)
// ring buffer for audio input
CircularBuffer<uint16_t> input_buffer; // fixed size 256
CircularBuffer<uint16_t, 256> input_buffer; // fixed size 256
#define audioInputAvailable() (!input_buffer.isEmpty())
#define readAudioInput() (input_buffer.read())
/** NOTE: Triggered at MOZZI_AUDIO_RATE via defaultAudioOutput(). In addition to the MOZZI_AUDIO_INPUT_PIN, at most one reading is taken for mozziAnalogRead(). */
Expand Down
4 changes: 2 additions & 2 deletions internal/MozziGuts_impl_ESP8266.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ uint16_t output_buffer_size = 0;

# if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
} // namespace MozziPrivate
# include <i2s.h>
# include <I2S.h>
namespace MozziPrivate {
inline bool canBufferAudioOutput() {
return (i2s_available() >= MOZZI_PDM_RESOLUTION);
Expand All @@ -60,7 +60,7 @@ inline void audioOutput(const AudioOutput f) {
}
# elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
} // namespace MozziPrivate
# include <i2s.h>
# include <I2S.h>
namespace MozziPrivate {
inline bool canBufferAudioOutput() {
return (i2s_available() >= MOZZI_PDM_RESOLUTION);
Expand Down
4 changes: 2 additions & 2 deletions internal/MozziGuts_impl_RENESAS.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ FspTimer timer;
#endif

#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
CircularBuffer<uint16_t> output_buffer;
CircularBuffer<uint16_t, MOZZI_OUTPUT_BUFFER_SIZE> output_buffer;
} // namespace MozziPrivate
#include "MozziGuts_impl_RENESAS_analog.hpp"
namespace MozziPrivate {
Expand Down Expand Up @@ -159,7 +159,7 @@ static void startAudio() {

// The following branches the DAC straight on Mozzi's circular buffer.
dtc_cfg.p_info->p_src = output_buffer.address();
dtc_cfg.p_info->length = MOZZI_BUFFER_SIZE;
dtc_cfg.p_info->length = MOZZI_OUTPUT_BUFFER_SIZE;
R_DTC_Reconfigure(&dtc_ctrl, dtc_cfg.p_info);
timer_dac.start();
#endif
Expand Down
3 changes: 3 additions & 0 deletions internal/config_checks_esp32.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
// All modes besides timed external bypass the output buffer!
#if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPUT_INTERNAL_DAC, MOZZI_OUTPUT_PWM)
# define BYPASS_MOZZI_OUTPUT_BUFFER true
#if (MOZZI_OUTPUT_BUFFER_SIZE != 256)
# warning MOZZI_OUTPUT_BUFFER_SIZE does not have an effect in this mode.
#endif
#endif

#define MOZZI__INTERNAL_ANALOG_READ_RESOLUTION 12
Expand Down
3 changes: 3 additions & 0 deletions internal/config_checks_esp8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_BITS, 16)
// esp. since i2s output already has output rate control -> no need for a
// separate output timer
#define BYPASS_MOZZI_OUTPUT_BUFFER true
#if (MOZZI_OUTPUT_BUFFER_SIZE != 256)
# warning MOZZI_OUTPUT_BUFFER_SIZE does not have an effect in this mode.
#endif
#endif

#define MOZZI__INTERNAL_ANALOG_READ_RESOLUTION 10
Expand Down
8 changes: 8 additions & 0 deletions internal/config_checks_generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
#define MOZZI_AUDIO_INPUT_PIN 0
#endif

#if not defined(MOZZI_OUTPUT_BUFFER_SIZE)
#define MOZZI_OUTPUT_BUFFER_SIZE 256
#endif

//MOZZI_PWM_RATE -> hardware specific
//MOZZI_AUDIO_PIN_1 -> hardware specific
//MOZZI_AUDIO_PIN_1_LOW -> hardware specific
Expand Down Expand Up @@ -121,6 +125,10 @@
/// Step 3: Apply various generic checks that make sense on more than one platform
MOZZI_CHECK_POW2(MOZZI_AUDIO_RATE)
MOZZI_CHECK_POW2(MOZZI_CONTROL_RATE)
MOZZI_CHECK_POW2(MOZZI_OUTPUT_BUFFER_SIZE)
#if (MOZZI_OUTPUT_BUFFER_SIZE > 256)
#error "Mozzi does not support buffer sizes greated than 256 at the moment"
#endif

#if MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_STANDARD) && MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE)
#error "MOZZI_AUDIO_INPUT depends on MOZZI_ANALOG_READ option"
Expand Down
5 changes: 4 additions & 1 deletion internal/config_checks_mbed.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,12 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE, MOZZI_AUDIO_INP
# endif
#endif

// All modes besides timed external bypass the output buffer!
// All modes besides timed external bypass the output buffer! In these modes, the buffer size is not configurable at the moment: throw an error if the user tries to change it.
#if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED)
# define BYPASS_MOZZI_OUTPUT_BUFFER true
# if (MOZZI_OUTPUT_BUFFER_SIZE != 256) // has been modified
# warning MOZZI_OUTPUT_BUFFER_SIZE does not have an effect in this mode.
# endif
#endif

// TODO: This value is correct for Arduino Giga and Arduino Portenta, but not necessarily everywhere else
Expand Down
14 changes: 12 additions & 2 deletions internal/config_checks_rp2040.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPU
# endif
# define BYPASS_MOZZI_OUTPUT_BUFFER true
# define MOZZI_RP2040_BUFFERS 8 // number of DMA buffers used
# define MOZZI_RP2040_BUFFER_SIZE 256 // total size of the buffer, in samples
# if !defined MOZZI_RP2040_BUFFER_SIZE
# define MOZZI_RP2040_BUFFER_SIZE MOZZI_OUTPUT_BUFFER_SIZE // total size of the buffer, in samples
# if (MOZZI_OUTPUT_BUFFER_SIZE < MOZZI_RP2040_BUFFERS)
# error MOZZI_OUTPUT_BUFFER_SIZE cannot be lower than 8 on this platform at the moment
# endif
# endif
#endif

#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
Expand All @@ -115,7 +120,12 @@ MOZZI_CHECK_SUPPORTED(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPU
MOZZI_CHECK_SUPPORTED(MOZZI_I2S_FORMAT, MOZZI_I2S_FORMAT_PLAIN, MOZZI_I2S_FORMAT_LSBJ)
# define BYPASS_MOZZI_OUTPUT_BUFFER true
# define MOZZI_RP2040_BUFFERS 8 // number of DMA buffers used
# define MOZZI_RP2040_BUFFER_SIZE 256 // total size of the buffer, in samples
# if !defined MOZZI_RP2040_BUFFER_SIZE
# define MOZZI_RP2040_BUFFER_SIZE MOZZI_OUTPUT_BUFFER_SIZE // total size of the buffer, in samples
# if (MOZZI_OUTPUT_BUFFER_SIZE < MOZZI_RP2040_BUFFERS)
# error MOZZI_OUTPUT_BUFFER_SIZE cannot be lower than 8 on this platform at the moment
# endif
# endif
#endif

#if !defined(MOZZI_ANALOG_READ)
Expand Down