Mozzi  version v2.0
sound synthesis library for Arduino
MozziGuts.hpp
1 /*
2  * MozziGuts.hpp
3  *
4  * This file is part of Mozzi.
5  *
6  * Copyright 2012-2024 Tim Barrass and the Mozzi Team
7  *
8  * Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later.
9  *
10 */
11 
12 #include <Arduino.h>
13 
14 #include "CircularBuffer.h"
15 #include "mozzi_analog.h"
16 #include "internal/mozzi_rand_p.h"
17 #include "AudioOutput.h"
18 
25 namespace MozziPrivate {
26 
27 // Forward declarations of functions to be provided by platform specific implementations
28 #if (!BYPASS_MOZZI_OUTPUT_BUFFER)
29 static void CACHED_FUNCTION_ATTR defaultAudioOutput();
30 #endif
31 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
32 static void advanceADCStep(); // to be provided by platform implementation
33 static void startSecondADCReadOnCurrentChannel(); // to be provided by platform implementation
34 static uint8_t adc_count = 0; // needed below
35 #endif
36 
37 // TODO: make this helper public?
38 template<byte BITS_IN, byte BITS_OUT, typename T> constexpr T smartShift(T value) {
39  return (BITS_IN > BITS_OUT) ? value >> (BITS_IN - BITS_OUT) : (BITS_IN < BITS_OUT) ? value << (BITS_OUT - BITS_IN) : value;
40 }
41 }
42 
43 // Include the appropriate implementation
44 #if IS_AVR()
45 # include "MozziGuts_impl_AVR.hpp"
46 #elif IS_STM32MAPLE()
47 # include "MozziGuts_impl_STM32.hpp"
48 #elif IS_STM32DUINO()
49 # include "MozziGuts_impl_STM32duino.hpp"
50 #elif IS_ESP32()
51 # include "MozziGuts_impl_ESP32.hpp"
52 #elif IS_ESP8266()
53 # include "MozziGuts_impl_ESP8266.hpp"
54 #elif (IS_TEENSY3() || IS_TEENSY4())
55 # include "MozziGuts_impl_TEENSY.hpp"
56 #elif (IS_SAMD21())
57 # include "MozziGuts_impl_SAMD.hpp"
58 #elif (IS_RP2040())
59 # include "MozziGuts_impl_RP2040.hpp"
60 #elif (IS_MBED())
61 # include "MozziGuts_impl_MBED.hpp"
62 #elif (IS_RENESAS())
63 # include "MozziGuts_impl_RENESAS.hpp"
64 #elif (IS_CH32())
65 # include "MozziGuts_impl_CH32.hpp"
66 #else
67 # error "Platform not (yet) supported. Check MozziGuts_impl_template.hpp and existing implementations for a blueprint for adding your favorite MCU."
68 #endif
69 
70 /* Retro-compatibility with "legacy" boards which use the async
71  ADC for getting AUDIO_INPUT
72 */
73 #if !defined(MOZZI__LEGACY_AUDIO_INPUT_IMPL)
74 # if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
75 # define MOZZI__LEGACY_AUDIO_INPUT_IMPL 1
76 # else
77 # define MOZZI__LEGACY_AUDIO_INPUT_IMPL 0
78 # endif
79 #endif
80 
81 namespace MozziPrivate {
83 #if BYPASS_MOZZI_OUTPUT_BUFFER == true
84 uint64_t samples_written_to_buffer = 0;
85 
86 inline void bufferAudioOutput(const AudioOutput f) {
87  audioOutput(f);
88  ++samples_written_to_buffer;
89 }
90 #else
92 # define canBufferAudioOutput() (!output_buffer.isFull())
93 # define bufferAudioOutput(f) output_buffer.write(f)
94 static void CACHED_FUNCTION_ATTR defaultAudioOutput() {
95 
96 #if MOZZI_IS(MOZZI__LEGACY_AUDIO_INPUT_IMPL, 1) // in that case, we rely on asynchroneous ADC reads implemented for mozziAnalogRead to get the audio in samples
97  MOZZI_ASSERT_NOTEQUAL(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE);
98  adc_count = 0;
99  startSecondADCReadOnCurrentChannel(); // the current channel is the AUDIO_INPUT pin
100 # endif
101  audioOutput(output_buffer.read());
102 }
103 #endif // #if (AUDIO_INPUT_MODE == AUDIO_INPUT_LEGACY)
105 
106 
108 /* Analog input code was informed initially by a discussion between
109 jRaskell, bobgardner, theusch, Koshchi, and code by jRaskell.
110 http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=789581
111 */
112 
113 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
114 
115 #include "Stack.h"
116 static volatile uint16_t analog_readings[NUM_ANALOG_INPUTS];
117 static Stack <volatile int8_t,NUM_ANALOG_INPUTS> adc_channels_to_read;
118 volatile static int8_t current_channel = -1; // volatile because accessed in control and adc ISRs
119 
120 /* gets the next channel to read off the stack, and if there is a channel there, it changes to that channel and starts a conversion.
121 */
122 void adcReadSelectedChannels() {
123  // ugly syntax below saves a few bytes/instructions (as current_channel is declared volatile)
124  if((current_channel = adc_channels_to_read.pop()) >= 0) adcStartConversion(current_channel);
125 }
126 
127 /* Called each time in updateControlWithAutoADC(), after updateControl()
128  Forbidding inline, here, saves a wholesome 16 bytes flash on AVR (without USE_AUDIO_INPUT). No idea, why.
129 */
130 __attribute__((noinline)) void adcStartReadCycle() {
131  if (current_channel < 0) // last read of adc_channels_to_read stack was empty, ie. all channels from last time have been read
132  {
133 #if MOZZI_IS(MOZZI__LEGACY_AUDIO_INPUT_IMPL, 1) // use of async ADC for audio input
134  adc_channels_to_read.push(MOZZI_AUDIO_INPUT_PIN); // for audio
135 #else
136  adcReadSelectedChannels();
137  adc_count = 0;
138 #endif
139  }
140 }
141 
142 uint16_t mozziAnalogRead(uint8_t pin) {
143  pin = adcPinToChannelNum(pin); // allow for channel or pin numbers; on most platforms other than AVR this has no effect. See note on pins/channels
144  adc_channels_to_read.push(pin);
145  return analog_readings[channelNumToIndex(pin)];
146 }
147 
148 #if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
149 static uint16_t audio_input; // holds the latest audio from input_buffer
150 uint16_t getAudioInput() { return audio_input; }
151 #endif
152 
153 #if MOZZI_IS(MOZZI__LEGACY_AUDIO_INPUT_IMPL, 1)
154 // ring buffer for audio input
155  CircularBuffer<uint16_t, 256> input_buffer; // fixed size 256
156 #define audioInputAvailable() (!input_buffer.isEmpty())
157 #define readAudioInput() (input_buffer.read())
159 inline void advanceADCStep() {
160  switch (adc_count) {
161  case 0:
162  // 6us
163  // the input pin was the last thing we read
164  input_buffer.write(getADCReading());
165  adcReadSelectedChannels(); // TODO: doesn't this stop the cycle, in case no pins to read?
166  break;
167 
168  case 1:
169  // <2us, <1us w/o receive
170  // receiveFirstControlADC();
171  startSecondADCReadOnCurrentChannel();
172  break;
173 
174  case 2:
175  // 3us
176  analog_readings[channelNumToIndex(current_channel)] = getADCReading();
177  adcStartConversion(adcPinToChannelNum(MOZZI_AUDIO_INPUT_PIN)); // -> result is ignored, but first thing in the next cycle, a second reading is taken.
178  break;
179 
180  }
181  adc_count++;
182 }
183 #else // no (legacy) audio input
189 inline void advanceADCStep() {
190  if (!adc_count) { // i.e. first step
191  //<1us
192  startSecondADCReadOnCurrentChannel(); // discard fist - noisy - reading, start another on same pin
193  adc_count=1;
194  } else {
195  // 3us
196  analog_readings[channelNumToIndex(current_channel)] = getADCReading(); // register second reading
197  adcReadSelectedChannels(); // start first reading on next pin (if any)
198  adc_count=0;
199  }
200 }
201 #endif
202 
203 #else
204 MOZZI_ASSERT_EQUAL(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE)
205 
206 uint16_t mozziAnalogRead(uint8_t pin) {
207  return analogRead(pin);
208 }
209 
210 #endif // MOZZI_ANALOG_READ
211 
213 
214 
216 static uint16_t update_control_timeout;
217 static uint16_t update_control_counter;
218 
219 inline void advanceControlLoop() {
220  if (!update_control_counter) {
221  update_control_counter = update_control_timeout;
222  updateControl();
223 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
224  adcStartReadCycle();
225 #endif
226  } else {
227  --update_control_counter;
228  }
229 }
230 
231 void audioHook() // 2us on AVR excluding updateAudio()
232 {
233 // setPin13High();
234  if (canBufferAudioOutput()) {
235  advanceControlLoop();
236  bufferAudioOutput(updateAudio());
237 
238 #if defined(LOOP_YIELD)
239  LOOP_YIELD
240 #endif
241 
242 #if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
243  if (audioInputAvailable()) audio_input = readAudioInput();
244 #endif
245  }
246 // Like LOOP_YIELD, but running every cycle of audioHook(), not just once per sample
247 #if defined(AUDIO_HOOK_HOOK)
248  AUDIO_HOOK_HOOK
249 #endif
250  // setPin13Low();
251 }
252 
253 // NOTE: This function counts the ticks of audio _output_, corresponding to real time elapsed.
254 // It does _not_ provide the count of the current audio frame to be generated by updateAudio(). These two things will differ, slightly,
255 // depending on the fill state of the buffer.
256 // TODO: In many - but not all - use cases, it might be more useful to provide a count of the current audio frame to be generated, however,
257 // the existing semantics have always been in place, so far.
258 unsigned long audioTicks() {
259 #if (BYPASS_MOZZI_OUTPUT_BUFFER != true)
260  return output_buffer.count();
261 #elif defined(AUDIOTICK_ADJUSTMENT)
262  return samples_written_to_buffer - (AUDIOTICK_ADJUSTMENT);
263 #else
264  return samples_written_to_buffer;
265 #endif
266 }
267 
268 unsigned long mozziMicros() { return audioTicks() * MICROS_PER_AUDIO_TICK; }
269 
271 
273 void startMozzi(int control_rate_hz) {
274 #if !MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE)
275  MozziPrivate::setupMozziADC(FAST_ADC); // you can use setupFastAnalogRead() with FASTER_ADC or FASTEST_ADC
276  // in setup() if desired (not for Teensy 3.* )
277 #endif
278  // delay(200); // so AutoRange doesn't read 0 to start with
279  update_control_timeout = MOZZI_AUDIO_RATE / control_rate_hz - 1;
280  startAudio();
281 }
282 
283 uint32_t MozziRandPrivate::x=132456789;
284 uint32_t MozziRandPrivate::y=362436069;
285 uint32_t MozziRandPrivate::z=521288629;
286 
288 }
289 
290 // reduce Macro leakage
291 #undef LOOP_YIELD
292 #undef BYPASS_MOZZI_OUTPUT_BUFFER
293 #undef AUDIO_HOOK_HOOK
294 #undef AUDIOTICK_ADJUSTMENT
295 #undef MOZZI__LEGACY_AUDIO_INPUT_IMPL
296 
297 // "export" publicly accessible functions defined in this file
298 // NOTE: unfortunately, we cannot just write "using MozziPrivate::mozziMicros()", etc. as that would conflict with, rather than define mozziMicros().
299 // Instead, for now, we forward the global-scope functions to their implementations inside MozziPrivate.
300 // We might want to rethink how this is done. What matters is that these functions are user accessible, though, while most of what we
301 // now keep in MozziPrivate is hidden away.
302 unsigned long mozziMicros() { return MozziPrivate::mozziMicros(); };
303 unsigned long audioTicks() { return MozziPrivate::audioTicks(); };
304 void startMozzi(int control_rate_hz) { MozziPrivate::startMozzi(control_rate_hz); };
305 void stopMozzi() { MozziPrivate::stopMozzi(); };
306 template<byte RES> uint16_t mozziAnalogRead(uint8_t pin) { return MozziPrivate::smartShift<MOZZI__INTERNAL_ANALOG_READ_RESOLUTION, RES>(MozziPrivate::mozziAnalogRead(pin));};
307 #if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
308 template<byte RES> uint16_t getAudioInput() { return MozziPrivate::smartShift<MOZZI__INTERNAL_ANALOG_READ_RESOLUTION, RES>(MozziPrivate::getAudioInput()); };
309 #endif
310 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
311 void setupMozziADC(int8_t speed) { MozziPrivate::setupMozziADC(speed); };
312 void setupFastAnalogRead(int8_t speed) { MozziPrivate::setupFastAnalogRead(speed); };
313 uint8_t adcPinToChannelNum(uint8_t pin) { return MozziPrivate::adcPinToChannelNum(pin); };
314 #endif
315 void audioHook() { MozziPrivate::audioHook(); };
316 
317 // This is not strictly needed, but we want it to throw an error, if users have audioOutput() in their sketch without external output configured
318 #if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPUT_EXTERNAL_CUSTOM)
319 MOZZI_DEPRECATED("n/a", "Sketch has audioOutput() function, although external output is not configured.") void audioOutput(const AudioOutput) {};
320 #endif
321 #if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_CUSTOM)
322 // TODO: This won't work without a rename:
323 //MOZZI_DEPRECATED("n/a", "Sketch has canBufferAudioOutput() function, although custom external output is not configured.") bool canBufferAudioOutput() {};
324 #endif
Circular buffer object.
A simple stack, used internally for keeping track of analog input channels as they are read.
Definition: Stack.h:19
void push(T item)
Put an item on the stack.
Definition: Stack.h:34
T pop()
Get the item on top of the stack.
Definition: Stack.h:45
uint16_t getAudioInput()
See getAudioInput().
Definition: MozziGuts.h:172
uint16_t mozziAnalogRead(uint8_t pin)
See mozziAnalogRead().
Definition: mozzi_analog.h:174
void setupFastAnalogRead(int8_t speed=FAST_ADC)
This is automatically called in startMozzi.
#define MOZZI_AUDIO_RATE
Defines the audio rate, i.e.
#define MOZZI_AUDIO_INPUT_PIN
This sets which analog input channel to use for audio input, if you have enabled MOZZI_AUDIO_INPUT,...
#define MOZZI_ANALOG_READ
Whether to compile in support for non-blocking analog reads.
void audioHook()
This is required in Arduino's loop().
Definition: MozziGuts.hpp:315
unsigned long audioTicks()
An alternative for Arduino time functions like micros() and millis().
Definition: MozziGuts.hpp:303
void updateControl()
This is where you put your control code.
void stopMozzi()
Stops audio and control interrupts and restores the timers to the values they had before Mozzi was st...
Definition: MozziGuts.hpp:305
AudioOutput_t updateAudio()
This is where you put your audio code.
unsigned long mozziMicros()
An alternative for Arduino time functions like micros() and millis().
Definition: MozziGuts.hpp:302
Internal.
Definition: mozzi_rand_p.h:15
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:111