Skip to content
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