From fe17896b118541e3db5f4bd8982b2d07f3285a05 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 19:16:13 +0200 Subject: [PATCH 01/18] Initial changes to CircularBuffer for smaller buffer sizes Typo --- CircularBuffer.h | 62 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/CircularBuffer.h b/CircularBuffer.h index e8c164a5b..d6983c465 100644 --- a/CircularBuffer.h +++ b/CircularBuffer.h @@ -14,11 +14,36 @@ 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 +// TODO: remove this define from here, put a default value in config +#define MOZZI_BUFFER_SIZE 256 + +// This is to get the correct cound for audioticks() +#if (MOZZI_BUFFER_SIZE == 256) +#define COUNT_LSHIFT 8 +#elif (MOZZI_BUFFER_SIZE == 128) +#define COUNT_LSHIFT 7 +#elif (MOZZI_BUFFER_SIZE == 64) +#define COUNT_LSHIFT 6 +#elif (MOZZI_BUFFER_SIZE == 32) +#define COUNT_LSHIFT 5 +#elif (MOZZI_BUFFER_SIZE == 16) +#define COUNT_LSHIFT 4 +#elif (MOZZI_BUFFER_SIZE == 8) +#define COUNT_LSHIFT 3 +#elif (MOZZI_BUFFER_SIZE == 4) +#define COUNT_LSHIFT 2 +#elif (MOZZI_BUFFER_SIZE == 2) +#define COUNT_LSHIFT 1 +#elif (MOZZI_BUFFER_SIZE == 1) +#define COUNT_LSHIFT 0 +#endif + + /** Circular buffer object. Has a fixed number of cells, set to 256. @tparam ITEM_TYPE the kind of data to store, eg. int, int8_t etc. @@ -60,7 +85,7 @@ class CircularBuffer inline unsigned long count() { - return (num_buffers_read << 8) + start; + return (num_buffers_read << COUNT_LSHIFT) + start; } inline ITEM_TYPE * address() { @@ -75,7 +100,7 @@ class CircularBuffer uint8_t e_msb; unsigned long num_buffers_read; - +#if (CIRCULAR_BUFFER_SIZE == 256) inline void cbIncrStart() { start++; @@ -90,5 +115,32 @@ class CircularBuffer end++; if (end == 0) e_msb ^= 1; } +#else // if circular buffer length is != 256, use less efficient version for to manage start/end index buffer index + inline + void cbIncrStart() { + start++; + if (start == CIRCULAR_BUFFER_SIZE) + { + start = 0; + s_msb ^= 1; + num_buffers_read++; + } + } + + inline + void cbIncrEnd() + { + end++; + if (end == CIRCULAR_BUFFER_SIZE) + { + end = 0; + e_msb ^= 1; + } + } +#endif + }; + + +#undef COUNT_LSHIFT // avoid macro spil From 98e9cc77026729065362d8003758c64767c6f95b Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 19:16:36 +0200 Subject: [PATCH 02/18] Started documentation of MOZZI_BUFFER_SIZE --- config/mozzi_config_documentation.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/config/mozzi_config_documentation.h b/config/mozzi_config_documentation.h index fc5b1c9f0..a7172db9d 100644 --- a/config/mozzi_config_documentation.h +++ b/config/mozzi_config_documentation.h @@ -249,6 +249,23 @@ * */ #define MOZZI_AUDIO_PIN_1 FOR_DOXYGEN_ONLY +/** @ingroup config + * @def MOZZI_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_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_BUFFER_SIZE FOR_DOXYGEN_ONLY + /***************************************** ADVANCED SETTTINGS -- External audio output ****************************************** * From 1c469114c51f3b20e4b42d74b692a16bf52ef248 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 19:39:04 +0200 Subject: [PATCH 03/18] Changed MOZZI_BUFFER_SIZE to MOZZI_OUTPUT_BUFFER_SIZE --- CircularBuffer.h | 30 ++++++++++++++--------------- config/mozzi_config_documentation.h | 6 +++--- internal/MozziGuts_impl_RENESAS.hpp | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CircularBuffer.h b/CircularBuffer.h index d6983c465..0fd0d2aba 100644 --- a/CircularBuffer.h +++ b/CircularBuffer.h @@ -20,26 +20,26 @@ of Meebleeps (https://github.com/sensorium/Mozzi/issues/281) */ // TODO: remove this define from here, put a default value in config -#define MOZZI_BUFFER_SIZE 256 +#define MOZZI_OUTPUT_BUFFER_SIZE 256 -// This is to get the correct cound for audioticks() -#if (MOZZI_BUFFER_SIZE == 256) +// This is to get the correct cound for audioticks() (there might be a smarter way...) +#if (MOZZI_OUTPUT_BUFFER_SIZE == 256) #define COUNT_LSHIFT 8 -#elif (MOZZI_BUFFER_SIZE == 128) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 128) #define COUNT_LSHIFT 7 -#elif (MOZZI_BUFFER_SIZE == 64) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 64) #define COUNT_LSHIFT 6 -#elif (MOZZI_BUFFER_SIZE == 32) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 32) #define COUNT_LSHIFT 5 -#elif (MOZZI_BUFFER_SIZE == 16) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 16) #define COUNT_LSHIFT 4 -#elif (MOZZI_BUFFER_SIZE == 8) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 8) #define COUNT_LSHIFT 3 -#elif (MOZZI_BUFFER_SIZE == 4) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 4) #define COUNT_LSHIFT 2 -#elif (MOZZI_BUFFER_SIZE == 2) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 2) #define COUNT_LSHIFT 1 -#elif (MOZZI_BUFFER_SIZE == 1) +#elif (MOZZI_OUTPUT_BUFFER_SIZE == 1) #define COUNT_LSHIFT 0 #endif @@ -93,14 +93,14 @@ class CircularBuffer } private: - ITEM_TYPE items[MOZZI_BUFFER_SIZE]; + ITEM_TYPE items[MOZZI_OUTPUT_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; -#if (CIRCULAR_BUFFER_SIZE == 256) +#if (MOZZI_OUTPUT_BUFFER_SIZE == 256) inline void cbIncrStart() { start++; @@ -119,7 +119,7 @@ class CircularBuffer inline void cbIncrStart() { start++; - if (start == CIRCULAR_BUFFER_SIZE) + if (start == MOZZI_OUTPUT_BUFFER_SIZE) { start = 0; s_msb ^= 1; @@ -131,7 +131,7 @@ class CircularBuffer void cbIncrEnd() { end++; - if (end == CIRCULAR_BUFFER_SIZE) + if (end == MOZZI_OUTPUT_BUFFER_SIZE) { end = 0; e_msb ^= 1; diff --git a/config/mozzi_config_documentation.h b/config/mozzi_config_documentation.h index a7172db9d..e2d5a9706 100644 --- a/config/mozzi_config_documentation.h +++ b/config/mozzi_config_documentation.h @@ -250,7 +250,7 @@ #define MOZZI_AUDIO_PIN_1 FOR_DOXYGEN_ONLY /** @ingroup config - * @def MOZZI_BUFFER_SIZE + * @def MOZZI_OUTPUT_BUFFER_SIZE * * @brief Audio buffer setting. * @@ -258,13 +258,13 @@ * 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_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. + * 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_BUFFER_SIZE FOR_DOXYGEN_ONLY +#define MOZZI_OUTPUT_BUFFER_SIZE FOR_DOXYGEN_ONLY /***************************************** ADVANCED SETTTINGS -- External audio output ****************************************** diff --git a/internal/MozziGuts_impl_RENESAS.hpp b/internal/MozziGuts_impl_RENESAS.hpp index 7ba7afda4..12bb6c26b 100644 --- a/internal/MozziGuts_impl_RENESAS.hpp +++ b/internal/MozziGuts_impl_RENESAS.hpp @@ -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 From 3d6c5ea21d2f75c7d61a13a406dfb7e84b229528 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 20:53:03 +0200 Subject: [PATCH 04/18] Added generic config --- internal/config_checks_generic.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/config_checks_generic.h b/internal/config_checks_generic.h index 3bd8f0b23..6886882eb 100644 --- a/internal/config_checks_generic.h +++ b/internal/config_checks_generic.h @@ -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 @@ -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" From ff7b7828a77694e5e075f3957c184858b1b0d8cf Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 21:44:35 +0200 Subject: [PATCH 05/18] Modified CircularBuffer to have the buffer size as a template parameter hence allowing for differently sized bufers to be used (for instance with audio input) --- CircularBuffer.h | 140 ++++++++++++++++++++++++++++++----------------- 1 file changed, 91 insertions(+), 49 deletions(-) diff --git a/CircularBuffer.h b/CircularBuffer.h index 0fd0d2aba..d05d2f0a6 100644 --- a/CircularBuffer.h +++ b/CircularBuffer.h @@ -19,36 +19,13 @@ April 2025: modified for different buffer sizes under the suggestion of Meebleeps (https://github.com/sensorium/Mozzi/issues/281) */ -// TODO: remove this define from here, put a default value in config -#define MOZZI_OUTPUT_BUFFER_SIZE 256 - -// This is to get the correct cound for audioticks() (there might be a smarter way...) -#if (MOZZI_OUTPUT_BUFFER_SIZE == 256) -#define COUNT_LSHIFT 8 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 128) -#define COUNT_LSHIFT 7 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 64) -#define COUNT_LSHIFT 6 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 32) -#define COUNT_LSHIFT 5 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 16) -#define COUNT_LSHIFT 4 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 8) -#define COUNT_LSHIFT 3 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 4) -#define COUNT_LSHIFT 2 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 2) -#define COUNT_LSHIFT 1 -#elif (MOZZI_OUTPUT_BUFFER_SIZE == 1) -#define COUNT_LSHIFT 0 -#endif - - - -/** 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 +template class CircularBuffer { @@ -93,33 +70,26 @@ class CircularBuffer } private: - ITEM_TYPE items[MOZZI_OUTPUT_BUFFER_SIZE]; + 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; -#if (MOZZI_OUTPUT_BUFFER_SIZE == 256) - inline - void cbIncrStart() { - start++; - if (start == 0) { - s_msb ^= 1; - num_buffers_read++; - } - } - - inline - void cbIncrEnd() { - end++; - if (end == 0) e_msb ^= 1; - } -#else // if circular buffer length is != 256, use less efficient version for to manage start/end index buffer index inline void cbIncrStart() { start++; - if (start == MOZZI_OUTPUT_BUFFER_SIZE) + if (start == BUFFER_SIZE) { start = 0; s_msb ^= 1; @@ -131,16 +101,88 @@ class CircularBuffer void cbIncrEnd() { end++; - if (end == MOZZI_OUTPUT_BUFFER_SIZE) + if (end == BUFFER_SIZE) { end = 0; e_msb ^= 1; } } -#endif }; -#undef COUNT_LSHIFT // avoid macro spil + +/** 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 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 << 8) + start; + } + inline + ITEM_TYPE * address() { + return items; + } + +private: + 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; + uint8_t e_msb; + unsigned long num_buffers_read; + + + inline + void cbIncrStart() { + start++; + if (start == 0) { + s_msb ^= 1; + num_buffers_read++; + } + } + + inline + void cbIncrEnd() { + end++; + if (end == 0) e_msb ^= 1; + } +}; + From ba7857166e08332e06a2575821641d16d403b99c Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 21:46:37 +0200 Subject: [PATCH 06/18] Updated MozziGuts for different buffer size --- internal/MozziGuts.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/MozziGuts.hpp b/internal/MozziGuts.hpp index 660171f28..a060af371 100644 --- a/internal/MozziGuts.hpp +++ b/internal/MozziGuts.hpp @@ -86,7 +86,7 @@ inline void bufferAudioOutput(const AudioOutput f) { ++samples_written_to_buffer; } #else -CircularBuffer output_buffer; // fixed size 256 + CircularBuffer output_buffer; # define canBufferAudioOutput() (!output_buffer.isFull()) # define bufferAudioOutput(f) output_buffer.write(f) static void CACHED_FUNCTION_ATTR defaultAudioOutput() { @@ -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 input_buffer; // fixed size 256 + CircularBuffer 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(). */ From 8c1a6e742e4cb9d621dace4e2aa93dcc249e82b4 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Fri, 18 Apr 2025 21:56:14 +0200 Subject: [PATCH 07/18] Fix Renesas compilation with MOZZI_OUTPUT_BUFFER_SIZE --- internal/MozziGuts_impl_RENESAS.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/MozziGuts_impl_RENESAS.hpp b/internal/MozziGuts_impl_RENESAS.hpp index 12bb6c26b..c30c18177 100644 --- a/internal/MozziGuts_impl_RENESAS.hpp +++ b/internal/MozziGuts_impl_RENESAS.hpp @@ -86,7 +86,7 @@ FspTimer timer; #endif #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC) -CircularBuffer output_buffer; + CircularBuffer output_buffer; } // namespace MozziPrivate #include "MozziGuts_impl_RENESAS_analog.hpp" namespace MozziPrivate { From 9ecdd0900e4221b2d3bfec5301572e35b93a5d3b Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Sat, 19 Apr 2025 18:45:24 +0200 Subject: [PATCH 08/18] FixESP8266 I2S --- internal/MozziGuts_impl_ESP8266.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/MozziGuts_impl_ESP8266.hpp b/internal/MozziGuts_impl_ESP8266.hpp index f4623c96d..20d37d2e2 100644 --- a/internal/MozziGuts_impl_ESP8266.hpp +++ b/internal/MozziGuts_impl_ESP8266.hpp @@ -48,7 +48,7 @@ uint16_t output_buffer_size = 0; # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S) } // namespace MozziPrivate -# include +# include namespace MozziPrivate { inline bool canBufferAudioOutput() { return (i2s_available() >= MOZZI_PDM_RESOLUTION); @@ -60,7 +60,7 @@ inline void audioOutput(const AudioOutput f) { } # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) } // namespace MozziPrivate -# include +# include namespace MozziPrivate { inline bool canBufferAudioOutput() { return (i2s_available() >= MOZZI_PDM_RESOLUTION); From aa967f1c718f61d3ad17203af2b107632940dda7 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Sat, 19 Apr 2025 18:45:41 +0200 Subject: [PATCH 09/18] Added warning messages when defining a smaller does not have an effect Removed old todo --- config/mozzi_config_documentation.h | 1 - internal/config_checks_esp32.h | 3 +++ internal/config_checks_esp8266.h | 3 +++ internal/config_checks_mbed.h | 5 ++++- internal/config_checks_rp2040.h | 14 ++++++++++++-- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/config/mozzi_config_documentation.h b/config/mozzi_config_documentation.h index e2d5a9706..f68b76d5b 100644 --- a/config/mozzi_config_documentation.h +++ b/config/mozzi_config_documentation.h @@ -262,7 +262,6 @@ * 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 diff --git a/internal/config_checks_esp32.h b/internal/config_checks_esp32.h index e4f1d6cc7..989a3bb1c 100644 --- a/internal/config_checks_esp32.h +++ b/internal/config_checks_esp32.h @@ -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 diff --git a/internal/config_checks_esp8266.h b/internal/config_checks_esp8266.h index 00cfff8ab..ab6c516f8 100644 --- a/internal/config_checks_esp8266.h +++ b/internal/config_checks_esp8266.h @@ -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 diff --git a/internal/config_checks_mbed.h b/internal/config_checks_mbed.h index 7ac69ba91..a99c8de7f 100644 --- a/internal/config_checks_mbed.h +++ b/internal/config_checks_mbed.h @@ -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 diff --git a/internal/config_checks_rp2040.h b/internal/config_checks_rp2040.h index 5ba884415..569e3406d 100644 --- a/internal/config_checks_rp2040.h +++ b/internal/config_checks_rp2040.h @@ -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) @@ -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) From f7cf290ca08dbbc9953d1300de216a525b92b040 Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Fri, 16 Jan 2026 16:06:14 -0500 Subject: [PATCH 10/18] Add support for CH32X035 microcontrollers Includes platform definitions, configuration checks, and core implementation for CH32 architecture. --- hardware_defines.h | 10 +- internal/MozziGuts_impl_CH32.hpp | 156 +++++++++++++++++++++++++++++++ internal/config_checks_CH32.h | 29 ++++++ 3 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 internal/MozziGuts_impl_CH32.hpp create mode 100644 internal/config_checks_CH32.h diff --git a/hardware_defines.h b/hardware_defines.h index 7d61adf46..34740f480 100644 --- a/hardware_defines.h +++ b/hardware_defines.h @@ -84,7 +84,7 @@ #define IS_RENESAS() 0 #endif -#if (defined(__arm__) && !IS_STM32MAPLE() && !IS_TEENSY3() && !IS_TEENSY4() && !IS_RP2040() && !IS_SAMD21() && !IS_MBED() && !IS_RENESAS()) +#if (defined(__arm__) && !IS_STM32MAPLE() && !IS_TEENSY3() && !IS_TEENSY4() && !IS_RP2040() && !IS_SAMD21() && !IS_MBED() && !IS_RENESAS() || IS_CH32()) #define IS_STM32DUINO() 1 #else #define IS_STM32DUINO() 0 @@ -102,7 +102,13 @@ #define IS_ESP32() 0 #endif -#if !(IS_AVR() || IS_TEENSY3() || IS_TEENSY4() || IS_STM32MAPLE() || IS_STM32DUINO() || IS_ESP8266() || IS_SAMD21() || IS_ESP32() || IS_RP2040() || IS_MBED() || IS_RENESAS()) +#if (defined(ARDUINO_ARCH_CH32) || defined(ARDUINO_ARCH_CH32V) || defined(CH32X035)) +#define IS_CH32() 1 +#else +#define IS_CH32() 0 +#endif + +#if !(IS_AVR() || IS_TEENSY3() || IS_TEENSY4() || IS_STM32MAPLE() || IS_STM32DUINO() || IS_ESP8266() || IS_SAMD21() || IS_ESP32() || IS_RP2040() || IS_MBED() || IS_RENESAS() || IS_CH32()) // TODO: add an exception for MOZZI_OUTPUT_EXTERNAL_CUSTOM #error Your hardware is not supported by Mozzi or not recognized. Edit hardware_defines.h to proceed. #endif diff --git a/internal/MozziGuts_impl_CH32.hpp b/internal/MozziGuts_impl_CH32.hpp new file mode 100644 index 000000000..5c9eb81d6 --- /dev/null +++ b/internal/MozziGuts_impl_CH32.hpp @@ -0,0 +1,156 @@ +/* + * MozziGuts_impl_CH32.hpp + * + * Implementation for WCH CH32X035 (RISC-V) using Standard Peripheral Library (SPL) + * + */ + +// Suppress "narrowing conversion" error for CH32 (RISC-V GCC is strict) +// This allows legacy Mozzi tables (declared as int8_t but containing uint8_t values) to compile. +#pragma GCC diagnostic ignored "-Wnarrowing" + +#if !(IS_CH32()) +# error "Wrong implementation included for this platform" +#endif + +#include + +// Forward declare the ISR to ensure C linkage +extern "C" void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); + +namespace MozziPrivate { + +#define MOZZI_AUDIO_PIN_1 PA6 // High Byte (Main) +#define MOZZI_AUDIO_PIN_2 PA7 // Low Byte (Fine tune) for 2PIN_PWM + +static void startAudio() { + // --- 1. Audio Interrupt Timer Setup (TIM1) --- + TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; + NVIC_InitTypeDef NVIC_InitStructure; + + RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); + + uint32_t period = (SystemCoreClock / MOZZI_AUDIO_RATE) - 1; + + TIM_DeInit(TIM1); + TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); + TIM_TimeBaseStructure.TIM_Prescaler = 0; + TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; + TIM_TimeBaseStructure.TIM_Period = period; + TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; + TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; + TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); + + TIM_ClearFlag(TIM1, TIM_FLAG_Update); + TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); + + NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); + + TIM_Cmd(TIM1, ENABLE); + TIM_CtrlPWMOutputs(TIM1, ENABLE); + + // --- 2. PWM Output Setup (TIM3) --- + GPIO_InitTypeDef GPIO_InitStructure; + TIM_OCInitTypeDef TIM_OCInitStructure; + + // Enable Clocks + RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); + RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); + + // Configure TIM3 Time Base + TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); + TIM_TimeBaseStructure.TIM_Period = 255; // 8-bit resolution + TIM_TimeBaseStructure.TIM_Prescaler = 0; + TIM_TimeBaseStructure.TIM_ClockDivision = 0; + TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; + TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); + + // Common PWM Configuration + TIM_OCStructInit(&TIM_OCInitStructure); + TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; + TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; + TIM_OCInitStructure.TIM_Pulse = 128; + TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; + + // --- Setup Pin 1 (PA6 / CH1) --- + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; + GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; + GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_Init(GPIOA, &GPIO_InitStructure); + + TIM_OC1Init(TIM3, &TIM_OCInitStructure); + TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); + +#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM) + // --- Setup Pin 2 (PA7 / CH2) for Hi-Fi --- + GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; + GPIO_Init(GPIOA, &GPIO_InitStructure); // Init PA7 as AF_PP too + + TIM_OC2Init(TIM3, &TIM_OCInitStructure); // Init CH2 + TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); +#endif + + TIM_ARRPreloadConfig(TIM3, ENABLE); + + // Enable TIM3 + TIM_Cmd(TIM3, ENABLE); + TIM_CtrlPWMOutputs(TIM3, ENABLE); +} + +void stopMozzi() { + TIM_Cmd(TIM1, DISABLE); + TIM_Cmd(TIM3, DISABLE); +} + +void MozziRandPrivate::autoSeed() {} + +#if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD) +void setupMozziADC(int8_t speed) {} +void setupFastAnalogRead(int8_t speed) {} +void adcStartConversion(uint8_t channel) {} +void startSecondADCReadOnCurrentChannel() {} +uint8_t adcPinToChannelNum(uint8_t pin) { return pin; } +#endif + +// --- Audio Output Functions --- + +#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) +inline void audioOutput(const AudioOutput f) { + long out = (long)f.l() + MOZZI_AUDIO_BIAS; + if (out < 0) out = 0; + out = out >> (MOZZI_AUDIO_BITS - 8); + if (out > 255) out = 255; + TIM_SetCompare1(TIM3, (uint16_t)out); +} + +#elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM) +inline void audioOutput(const AudioOutput f) { + // 16-bit output split into two 8-bit channels + long out = (long)f.l() + MOZZI_AUDIO_BIAS; + + // Clamp to 16-bit unsigned range just in case + if (out < 0) out = 0; + if (out > 65535) out = 65535; + + // High Byte -> CH1 (PA6) + TIM_SetCompare1(TIM3, (uint16_t)(out >> 8)); + + // Low Byte -> CH2 (PA7) + TIM_SetCompare2(TIM3, (uint16_t)(out & 0xFF)); +} +#endif + +} // namespace MozziPrivate + +extern "C" void TIM1_UP_IRQHandler(void) { + if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { + TIM_ClearITPendingBit(TIM1, TIM_IT_Update); +#if (BYPASS_MOZZI_OUTPUT_BUFFER != true) + MozziPrivate::defaultAudioOutput(); +#endif + } +} diff --git a/internal/config_checks_CH32.h b/internal/config_checks_CH32.h new file mode 100644 index 000000000..366694e54 --- /dev/null +++ b/internal/config_checks_CH32.h @@ -0,0 +1,29 @@ +#ifndef MOZZI_CONFIG_CHECKS_CH32_H +#define MOZZI_CONFIG_CHECKS_CH32_H + +#if !defined(MOZZI_AUDIO_MODE) +#define MOZZI_AUDIO_MODE MOZZI_OUTPUT_PWM +#endif + +// Configure Audio Bits based on Mode +#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) + #if !defined(MOZZI_AUDIO_BITS) + #define MOZZI_AUDIO_BITS 16 // Internal calc at 16, output scaled to 8 + #endif +#elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM) + #if !defined(MOZZI_AUDIO_BITS) + #define MOZZI_AUDIO_BITS 16 // Full 16-bit output + #endif +#endif + +#if !defined(MOZZI_AUDIO_RATE) +#define MOZZI_AUDIO_RATE 16384 +#endif + +#if !defined(MOZZI_ANALOG_READ) +#define MOZZI_ANALOG_READ MOZZI_ANALOG_READ_NONE +#endif + +#define MOZZI__INTERNAL_ANALOG_READ_RESOLUTION 10 + +#endif \ No newline at end of file From a03409087d2cd5412eebb7394db127b9f8eaf9e6 Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Fri, 16 Jan 2026 16:11:46 -0500 Subject: [PATCH 11/18] Refine platform detection macros: use ARDUINO_ARCH_CH32V for IS_CH32, and check specifically for CH32X035 in implementation. --- hardware_defines.h | 21 +++++++++++---------- internal/MozziGuts_impl_CH32.hpp | 6 +++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/hardware_defines.h b/hardware_defines.h index 34740f480..4eb63b8d1 100644 --- a/hardware_defines.h +++ b/hardware_defines.h @@ -1,5 +1,5 @@ /* - * hardware_defines.h.h + * hardware_defines.h * * This file is part of Mozzi. * @@ -84,7 +84,14 @@ #define IS_RENESAS() 0 #endif -#if (defined(__arm__) && !IS_STM32MAPLE() && !IS_TEENSY3() && !IS_TEENSY4() && !IS_RP2040() && !IS_SAMD21() && !IS_MBED() && !IS_RENESAS() || IS_CH32()) +// CH32 Architecture (WCH RISC-V) +#if (defined(ARDUINO_ARCH_CH32V) || defined(ARDUINO_ARCH_CH32)) +#define IS_CH32() 1 +#else +#define IS_CH32() 0 +#endif + +#if (defined(__arm__) && !IS_STM32MAPLE() && !IS_TEENSY3() && !IS_TEENSY4() && !IS_RP2040() && !IS_SAMD21() && !IS_MBED() && !IS_RENESAS()) #define IS_STM32DUINO() 1 #else #define IS_STM32DUINO() 0 @@ -102,15 +109,9 @@ #define IS_ESP32() 0 #endif -#if (defined(ARDUINO_ARCH_CH32) || defined(ARDUINO_ARCH_CH32V) || defined(CH32X035)) -#define IS_CH32() 1 -#else -#define IS_CH32() 0 -#endif - #if !(IS_AVR() || IS_TEENSY3() || IS_TEENSY4() || IS_STM32MAPLE() || IS_STM32DUINO() || IS_ESP8266() || IS_SAMD21() || IS_ESP32() || IS_RP2040() || IS_MBED() || IS_RENESAS() || IS_CH32()) // TODO: add an exception for MOZZI_OUTPUT_EXTERNAL_CUSTOM -#error Your hardware is not supported by Mozzi or not recognized. Edit hardware_defines.h to proceed. +#error Your hardware is not supported by Mozzi or not recognized. Edit hardware_defines.h to proceed. #endif // Hardware detail defines @@ -132,4 +133,4 @@ #include #endif -#endif /* HARDWARE_DEFINES_H_ */ +#endif /* HARDWARE_DEFINES_H_ */ \ No newline at end of file diff --git a/internal/MozziGuts_impl_CH32.hpp b/internal/MozziGuts_impl_CH32.hpp index 5c9eb81d6..f88c7d920 100644 --- a/internal/MozziGuts_impl_CH32.hpp +++ b/internal/MozziGuts_impl_CH32.hpp @@ -13,6 +13,10 @@ # error "Wrong implementation included for this platform" #endif +#if !defined(CH32X035) +# error "This CH32 chip is not supported by Mozzi yet. Only CH32X035 is currently supported." +#endif + #include // Forward declare the ISR to ensure C linkage @@ -153,4 +157,4 @@ extern "C" void TIM1_UP_IRQHandler(void) { MozziPrivate::defaultAudioOutput(); #endif } -} +} \ No newline at end of file From 468b0ecaa8d9dae1872b3b850bca5eb13aa1a017 Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Fri, 16 Jan 2026 16:32:43 -0500 Subject: [PATCH 12/18] Ensure CH32 headers are included in generic dispatchers (config checks and guts implementation). --- internal/MozziGuts.hpp | 4 +++- internal/config_checks_generic.h | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/MozziGuts.hpp b/internal/MozziGuts.hpp index 660171f28..cf40a8651 100644 --- a/internal/MozziGuts.hpp +++ b/internal/MozziGuts.hpp @@ -61,6 +61,8 @@ template constexpr T smartShift(T value # include "MozziGuts_impl_MBED.hpp" #elif (IS_RENESAS()) # include "MozziGuts_impl_RENESAS.hpp" +#elif (IS_CH32()) +# include "MozziGuts_impl_CH32.hpp" #else # error "Platform not (yet) supported. Check MozziGuts_impl_template.hpp and existing implementations for a blueprint for adding your favorite MCU." #endif @@ -319,4 +321,4 @@ MOZZI_DEPRECATED("n/a", "Sketch has audioOutput() function, although external ou #if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_CUSTOM) // TODO: This won't work without a rename: //MOZZI_DEPRECATED("n/a", "Sketch has canBufferAudioOutput() function, although custom external output is not configured.") bool canBufferAudioOutput() {}; -#endif +#endif \ No newline at end of file diff --git a/internal/config_checks_generic.h b/internal/config_checks_generic.h index 3bd8f0b23..653c56c7b 100644 --- a/internal/config_checks_generic.h +++ b/internal/config_checks_generic.h @@ -104,6 +104,8 @@ #include "config_checks_stm32duino.h" #elif IS_STM32MAPLE() #include "config_checks_stm32maple.h" +#elif IS_CH32() +#include "config_checks_CH32.h" #elif (IS_TEENSY3() || IS_TEENSY4()) #include "config_checks_teensy.h" #else @@ -199,4 +201,4 @@ MOZZI_CHECK_SUPPORTED(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE, MOZZI_ANALOG_RE #include "../AudioOutput.h" static_assert(MOZZI_AUDIO_BITS <= (8*sizeof(AudioOutputStorage_t)), "Configured MOZZI_AUDIO_BITS is too large for the internal storage type"); -#endif +#endif \ No newline at end of file From a62138313d858ec460fbdad2bfabbaee8fd99934 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Sat, 17 Jan 2026 18:18:02 +0100 Subject: [PATCH 13/18] Increase version number --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 23e5622b5..9de07e230 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Mozzi -version=2.0.2 +version=2.0.3 author=Tim Barrass and contributors as documented in source, and at https://github.com/sensorium/Mozzi/graphs/contributors maintainer=Tim Barrass sentence=Sound synthesis library for Arduino From d7a80c7744d48da57eab4fedbed2b242a7d27322 Mon Sep 17 00:00:00 2001 From: tomcombriat Date: Sun, 18 Jan 2026 19:26:34 +0100 Subject: [PATCH 14/18] Fix compilation for CH32 --- hardware_defines.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hardware_defines.h b/hardware_defines.h index 4eb63b8d1..e399adabf 100644 --- a/hardware_defines.h +++ b/hardware_defines.h @@ -85,7 +85,7 @@ #endif // CH32 Architecture (WCH RISC-V) -#if (defined(ARDUINO_ARCH_CH32V) || defined(ARDUINO_ARCH_CH32)) +#if (defined(ARDUINO_ARCH_CH32V) || defined(ARDUINO_ARCH_CH32) || defined(CH32X035)) #define IS_CH32() 1 #else #define IS_CH32() 0 @@ -133,4 +133,4 @@ #include #endif -#endif /* HARDWARE_DEFINES_H_ */ \ No newline at end of file +#endif /* HARDWARE_DEFINES_H_ */ From bf55f56b41f8e7a0b27c2e75a6ecd91083fda20d Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Tue, 20 Jan 2026 10:09:03 -0500 Subject: [PATCH 15/18] feat(ch32): implement non-blocking analog read support Implemented ADC1_IRQHandler and setupFastAnalogRead for CH32X035 using direct SPL calls. Added robust pin-to-channel mapping fallback logic. --- README.md | 3 +- .../website/_includes/boards-tested.markdown | 2 + extras/website/index.markdown | 2 +- hardware_defines.h | 1 + internal/MozziGuts_impl_CH32.hpp | 125 ++++++++++++++++-- internal/config_checks_CH32.h | 2 +- 6 files changed, 120 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b6f2278a8..aa2204964 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ passing or external synths. *** ## Features -- Available for a wide and growing range of MCUs, with and without inbuilt DACs: Arduino Uno R3 and R4, STM32, Teensy, ESP8266, ESP32, Raspberry Pi Pico, and more. +- Available for a wide and growing range of MCUs, with and without inbuilt DACs: Arduino Uno R3 and R4, STM32, Teensy, ESP8266, ESP32, Raspberry Pi Pico, CH32, and more. - Configurable sample rate, usually in powers of two (16384 Hz, or 32768 Hz). - Variable control rate from 64 Hz upwards. - Various inbuilt output modes, including 16 bit output to an external DAC. @@ -69,6 +69,7 @@ If desparate, there is still a "Mozzi_1" branch in the git repository which cont | *ESP32: that has an external DAC (only ESP32) note: Beware of vastly different pin labels across board variants* | 15 (+16) | | yes | **GPIO25 (+GPIO26)** | yes | | *ESP32-S/C/H/P: that do not have an external DAC note: Beware of vastly different pin labels across board variants* | **15 (+16)** | | yes | | yes | | *RP2040*: Raspberry Pi Pico and friends | **0 (+1)** | 0, 1 | | | yes | +| *CH32*: WCH CH32X035 (RISC-V) | **PA6** | PA6, PA7 | | | | > - PWM-1: 1-pin PWM mode (`MOZZI_OUTPUT_PWM`) > - PWM-2: 2-pin PWM mode (`MOZZI_OUTPUT_2PIN_PWM`) diff --git a/extras/website/_includes/boards-tested.markdown b/extras/website/_includes/boards-tested.markdown index da328b318..f73d84197 100644 --- a/extras/website/_includes/boards-tested.markdown +++ b/extras/website/_includes/boards-tested.markdown @@ -47,6 +47,8 @@ td[data-sd] { border: 2px solid; background: linear-gradient(-45deg,rgba(0,0,255 ESP32-S/C/H/P: that do not have an internal DAC note: Beware of vastly different pin labels across board variants 15 (+16) yes yes RP2040: Raspberry Pi Pico and friends 0 (+1) 0, 1 - - yes + + CH32: WCH CH32X035 (RISC-V) PA6 PA6, PA7 - - - diff --git a/extras/website/index.markdown b/extras/website/index.markdown index f8435478f..a8acfb5ff 100644 --- a/extras/website/index.markdown +++ b/extras/website/index.markdown @@ -58,7 +58,7 @@ Your browser does not support the audio element. ## Features -- Available for a wide and growing range of MCUs, with and without inbuilt DACs: Arduino Uno R3 and R4, STM32, Teensy, ESP8266, ESP32, Raspberry Pi Pico, and more. +- Available for a wide and growing range of MCUs, with and without inbuilt DACs: Arduino Uno R3 and R4, STM32, Teensy, ESP8266, ESP32, Raspberry Pi Pico, CH32, and more. - Configurable sample rate, usually in powers of two (16384 Hz, or 32768 Hz). - Variable control rate from 64 Hz upwards. - Various inbuilt output modes, including 16 bit output to an external DAC. diff --git a/hardware_defines.h b/hardware_defines.h index e399adabf..5e939b78b 100644 --- a/hardware_defines.h +++ b/hardware_defines.h @@ -85,6 +85,7 @@ #endif // CH32 Architecture (WCH RISC-V) +// Explicit check for CH32X035 is needed as ARDUINO_ARCH macros might not be defined by all cores/tools #if (defined(ARDUINO_ARCH_CH32V) || defined(ARDUINO_ARCH_CH32) || defined(CH32X035)) #define IS_CH32() 1 #else diff --git a/internal/MozziGuts_impl_CH32.hpp b/internal/MozziGuts_impl_CH32.hpp index f88c7d920..0581651d0 100644 --- a/internal/MozziGuts_impl_CH32.hpp +++ b/internal/MozziGuts_impl_CH32.hpp @@ -21,6 +21,7 @@ // Forward declare the ISR to ensure C linkage extern "C" void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); +extern "C" void ADC1_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); namespace MozziPrivate { @@ -113,11 +114,99 @@ void stopMozzi() { void MozziRandPrivate::autoSeed() {} #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD) -void setupMozziADC(int8_t speed) {} -void setupFastAnalogRead(int8_t speed) {} -void adcStartConversion(uint8_t channel) {} -void startSecondADCReadOnCurrentChannel() {} -uint8_t adcPinToChannelNum(uint8_t pin) { return pin; } + +// --- Macros required by MozziGuts.hpp --- +#define getADCReading() ADC_GetConversionValue(ADC1) +#define channelNumToIndex(channel) channel + +// --- ADC Helper Variables --- +static uint8_t ch32_current_adc_channel = 0; + +// --- Helper for pin mapping --- +uint8_t adcPinToChannelNum(uint8_t pin) { +#if defined(analogPinToChannel) + return analogPinToChannel(pin); +#else + // Fallback: If A0 is defined, assume standard Arduino mapping (A0 -> 0, A1 -> 1...) + // If pin is small (e.g. 0-15), assume it's already a channel. + if (pin >= A0) return pin - A0; + return pin; +#endif +} + +void setupFastAnalogRead(int8_t speed) { + ADC_InitTypeDef ADC_InitStructure; + NVIC_InitTypeDef NVIC_InitStructure; + + // 1. Enable ADC1 Clock + RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); + + // 2. Configure ADC Clock Divider (PCLK2 / Div) + // CH32X035 PCLK2 is typically 48MHz. + // ADCCLK max is usually 14MHz. + // Div4 = 12MHz (Safe, Fast), Div6 = 8MHz, Div8 = 6MHz + if (speed == FASTEST_ADC) { + RCC_ADCCLKConfig(RCC_PCLK2_Div4); + } else if (speed == FASTER_ADC) { + RCC_ADCCLKConfig(RCC_PCLK2_Div6); + } else { + RCC_ADCCLKConfig(RCC_PCLK2_Div8); // Default safe speed + } + + // 3. Reset ADC + ADC_DeInit(ADC1); + + // 4. Configure ADC Parameters + ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; + ADC_InitStructure.ADC_ScanConvMode = DISABLE; // Single channel mode for manual control + ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // We trigger manually + ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // Software trigger + ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; + ADC_InitStructure.ADC_NbrOfChannel = 1; + ADC_Init(ADC1, &ADC_InitStructure); + + // 5. Calibration (Crucial for accuracy) + ADC_Cmd(ADC1, ENABLE); + + ADC_ResetCalibration(ADC1); + while(ADC_GetResetCalibrationStatus(ADC1)); + + ADC_StartCalibration(ADC1); + while(ADC_GetCalibrationStatus(ADC1)); + + // 6. Configure Interrupts (NVIC) + // ADC1 is usually IRQ channel 29 on CH32 + NVIC_InitStructure.NVIC_IRQChannel = ADC1_IRQn; + NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // Lower priority than Audio (1) + NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; + NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; + NVIC_Init(&NVIC_InitStructure); + + // 7. Enable End-Of-Conversion Interrupt + ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); +} + +void setupMozziADC(int8_t speed) { + setupFastAnalogRead(speed); +} + +void adcStartConversion(uint8_t channel) { + ch32_current_adc_channel = channel; // Store for context if needed + + // Select the channel + // Rank 1, and sample time. + // 241 Cycles is safer for higher impedance, but 43 or 55 is faster. + // Let's stick to a middle ground or safe default for audio (55 or 71 cycles). + ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_71Cycles5); + + // Trigger conversion + ADC_SoftwareStartConvCmd(ADC1, ENABLE); +} + +void startSecondADCReadOnCurrentChannel() { + // Just trigger again on the already configured channel + ADC_SoftwareStartConvCmd(ADC1, ENABLE); +} #endif // --- Audio Output Functions --- @@ -125,9 +214,6 @@ uint8_t adcPinToChannelNum(uint8_t pin) { return pin; } #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) inline void audioOutput(const AudioOutput f) { long out = (long)f.l() + MOZZI_AUDIO_BIAS; - if (out < 0) out = 0; - out = out >> (MOZZI_AUDIO_BITS - 8); - if (out > 255) out = 255; TIM_SetCompare1(TIM3, (uint16_t)out); } @@ -135,10 +221,6 @@ inline void audioOutput(const AudioOutput f) { inline void audioOutput(const AudioOutput f) { // 16-bit output split into two 8-bit channels long out = (long)f.l() + MOZZI_AUDIO_BIAS; - - // Clamp to 16-bit unsigned range just in case - if (out < 0) out = 0; - if (out > 65535) out = 65535; // High Byte -> CH1 (PA6) TIM_SetCompare1(TIM3, (uint16_t)(out >> 8)); @@ -157,4 +239,23 @@ extern "C" void TIM1_UP_IRQHandler(void) { MozziPrivate::defaultAudioOutput(); #endif } +} + +// --- ADC Interrupt Handler --- +extern "C" void ADC1_IRQHandler(void) { + if(ADC_GetITStatus(ADC1, ADC_IT_EOC) != RESET) { + // Read value (this also might clear the EOC flag on some families, but explicit clear is safer) + // We don't store it here, Mozzi's advanceADCStep will call getADCReading() (macro) to get it. + // Wait! Mozzi architecture expects getADCReading() to return the value. + // But advanceADCStep() is a void function. + // Let's look at AVR implementation. + // AVR ISR calls advanceADCStep(). advanceADCStep calls getADCReading(). + + // So we need to ensure getADCReading() works inside advanceADCStep. + // We can't pass the value. + // We just clear the flag and let Mozzi read the register. + + ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); + MozziPrivate::advanceADCStep(); + } } \ No newline at end of file diff --git a/internal/config_checks_CH32.h b/internal/config_checks_CH32.h index 366694e54..bc10e91ca 100644 --- a/internal/config_checks_CH32.h +++ b/internal/config_checks_CH32.h @@ -8,7 +8,7 @@ // Configure Audio Bits based on Mode #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) #if !defined(MOZZI_AUDIO_BITS) - #define MOZZI_AUDIO_BITS 16 // Internal calc at 16, output scaled to 8 + #define MOZZI_AUDIO_BITS 8 // Output resolution is 8-bit #endif #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_2PIN_PWM) #if !defined(MOZZI_AUDIO_BITS) From a0d1678b1ba004fbb5f0bd4c2612d50d601d4537 Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Tue, 20 Jan 2026 10:29:02 -0500 Subject: [PATCH 16/18] chore: enable MOZZI_ANALOG_READ_STANDARD by default for CH32 --- internal/config_checks_CH32.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config_checks_CH32.h b/internal/config_checks_CH32.h index bc10e91ca..61eb063db 100644 --- a/internal/config_checks_CH32.h +++ b/internal/config_checks_CH32.h @@ -21,7 +21,7 @@ #endif #if !defined(MOZZI_ANALOG_READ) -#define MOZZI_ANALOG_READ MOZZI_ANALOG_READ_NONE +#define MOZZI_ANALOG_READ MOZZI_ANALOG_READ_STANDARD #endif #define MOZZI__INTERNAL_ANALOG_READ_RESOLUTION 10 From 05570d558f5148c8943b57ac985ffdae9c1406f0 Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Tue, 20 Jan 2026 10:36:01 -0500 Subject: [PATCH 17/18] fix(ch32): align ADC implementation with CH32X035 SPL Fixed RCC and ADC function names, removed non-existent calibration calls, and updated sample time constants to match WCH core v1.0.4. Verified compilation with arduino-cli. --- internal/MozziGuts_impl_CH32.hpp | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/internal/MozziGuts_impl_CH32.hpp b/internal/MozziGuts_impl_CH32.hpp index 0581651d0..67688ed84 100644 --- a/internal/MozziGuts_impl_CH32.hpp +++ b/internal/MozziGuts_impl_CH32.hpp @@ -141,16 +141,13 @@ void setupFastAnalogRead(int8_t speed) { // 1. Enable ADC1 Clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); - // 2. Configure ADC Clock Divider (PCLK2 / Div) - // CH32X035 PCLK2 is typically 48MHz. - // ADCCLK max is usually 14MHz. - // Div4 = 12MHz (Safe, Fast), Div6 = 8MHz, Div8 = 6MHz + // 2. Configure ADC Clock Divider if (speed == FASTEST_ADC) { - RCC_ADCCLKConfig(RCC_PCLK2_Div4); + ADC_CLKConfig(ADC1, ADC_CLK_Div4); } else if (speed == FASTER_ADC) { - RCC_ADCCLKConfig(RCC_PCLK2_Div6); + ADC_CLKConfig(ADC1, ADC_CLK_Div6); } else { - RCC_ADCCLKConfig(RCC_PCLK2_Div8); // Default safe speed + ADC_CLKConfig(ADC1, ADC_CLK_Div8); // Default safe speed } // 3. Reset ADC @@ -165,14 +162,8 @@ void setupFastAnalogRead(int8_t speed) { ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); - // 5. Calibration (Crucial for accuracy) + // 5. Enable ADC ADC_Cmd(ADC1, ENABLE); - - ADC_ResetCalibration(ADC1); - while(ADC_GetResetCalibrationStatus(ADC1)); - - ADC_StartCalibration(ADC1); - while(ADC_GetCalibrationStatus(ADC1)); // 6. Configure Interrupts (NVIC) // ADC1 is usually IRQ channel 29 on CH32 @@ -195,9 +186,7 @@ void adcStartConversion(uint8_t channel) { // Select the channel // Rank 1, and sample time. - // 241 Cycles is safer for higher impedance, but 43 or 55 is faster. - // Let's stick to a middle ground or safe default for audio (55 or 71 cycles). - ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_71Cycles5); + ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_11Cycles); // Trigger conversion ADC_SoftwareStartConvCmd(ADC1, ENABLE); From 0678911f984557e1a5834aa341928aeb95683a31 Mon Sep 17 00:00:00 2001 From: NoNamedCat Date: Tue, 20 Jan 2026 12:57:32 -0500 Subject: [PATCH 18/18] Fixed CH32X035 ADC resolution definition to 12-bit --- internal/config_checks_CH32.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config_checks_CH32.h b/internal/config_checks_CH32.h index 61eb063db..dfaa274bb 100644 --- a/internal/config_checks_CH32.h +++ b/internal/config_checks_CH32.h @@ -24,6 +24,6 @@ #define MOZZI_ANALOG_READ MOZZI_ANALOG_READ_STANDARD #endif -#define MOZZI__INTERNAL_ANALOG_READ_RESOLUTION 10 +#define MOZZI__INTERNAL_ANALOG_READ_RESOLUTION 12 #endif \ No newline at end of file