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 #else
65 # error "Platform not (yet) supported. Check MozziGuts_impl_template.hpp and existing implementations for a blueprint for adding your favorite MCU."
66 #endif
67 
68 /* Retro-compatibility with "legacy" boards which use the async
69  ADC for getting AUDIO_INPUT
70 */
71 #if !defined(MOZZI__LEGACY_AUDIO_INPUT_IMPL)
72 # if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
73 # define MOZZI__LEGACY_AUDIO_INPUT_IMPL 1
74 # else
75 # define MOZZI__LEGACY_AUDIO_INPUT_IMPL 0
76 # endif
77 #endif
78 
79 namespace MozziPrivate {
81 #if BYPASS_MOZZI_OUTPUT_BUFFER == true
82 uint64_t samples_written_to_buffer = 0;
83 
84 inline void bufferAudioOutput(const AudioOutput f) {
85  audioOutput(f);
86  ++samples_written_to_buffer;
87 }
88 #else
89 CircularBuffer<AudioOutput> output_buffer; // fixed size 256
90 # define canBufferAudioOutput() (!output_buffer.isFull())
91 # define bufferAudioOutput(f) output_buffer.write(f)
92 static void CACHED_FUNCTION_ATTR defaultAudioOutput() {
93 
94 #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
95  MOZZI_ASSERT_NOTEQUAL(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE);
96  adc_count = 0;
97  startSecondADCReadOnCurrentChannel(); // the current channel is the AUDIO_INPUT pin
98 # endif
99  audioOutput(output_buffer.read());
100 }
101 #endif // #if (AUDIO_INPUT_MODE == AUDIO_INPUT_LEGACY)
103 
104 
106 /* Analog input code was informed initially by a discussion between
107 jRaskell, bobgardner, theusch, Koshchi, and code by jRaskell.
108 http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=789581
109 */
110 
111 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
112 
113 #include "Stack.h"
114 static volatile uint16_t analog_readings[NUM_ANALOG_INPUTS];
115 static Stack <volatile int8_t,NUM_ANALOG_INPUTS> adc_channels_to_read;
116 volatile static int8_t current_channel = -1; // volatile because accessed in control and adc ISRs
117 
118 /* 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.
119 */
120 void adcReadSelectedChannels() {
121  // ugly syntax below saves a few bytes/instructions (as current_channel is declared volatile)
122  if((current_channel = adc_channels_to_read.pop()) >= 0) adcStartConversion(current_channel);
123 }
124 
125 /* Called each time in updateControlWithAutoADC(), after updateControl()
126  Forbidding inline, here, saves a wholesome 16 bytes flash on AVR (without USE_AUDIO_INPUT). No idea, why.
127 */
128 __attribute__((noinline)) void adcStartReadCycle() {
129  if (current_channel < 0) // last read of adc_channels_to_read stack was empty, ie. all channels from last time have been read
130  {
131 #if MOZZI_IS(MOZZI__LEGACY_AUDIO_INPUT_IMPL, 1) // use of async ADC for audio input
132  adc_channels_to_read.push(MOZZI_AUDIO_INPUT_PIN); // for audio
133 #else
134  adcReadSelectedChannels();
135  adc_count = 0;
136 #endif
137  }
138 }
139 
140 uint16_t mozziAnalogRead(uint8_t pin) {
141  pin = adcPinToChannelNum(pin); // allow for channel or pin numbers; on most platforms other than AVR this has no effect. See note on pins/channels
142  adc_channels_to_read.push(pin);
143  return analog_readings[channelNumToIndex(pin)];
144 }
145 
146 #if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
147 static uint16_t audio_input; // holds the latest audio from input_buffer
148 uint16_t getAudioInput() { return audio_input; }
149 #endif
150 
151 #if MOZZI_IS(MOZZI__LEGACY_AUDIO_INPUT_IMPL, 1)
152 // ring buffer for audio input
153 CircularBuffer<uint16_t> input_buffer; // fixed size 256
154 #define audioInputAvailable() (!input_buffer.isEmpty())
155 #define readAudioInput() (input_buffer.read())
157 inline void advanceADCStep() {
158  switch (adc_count) {
159  case 0:
160  // 6us
161  // the input pin was the last thing we read
162  input_buffer.write(getADCReading());
163  adcReadSelectedChannels(); // TODO: doesn't this stop the cycle, in case no pins to read?
164  break;
165 
166  case 1:
167  // <2us, <1us w/o receive
168  // receiveFirstControlADC();
169  startSecondADCReadOnCurrentChannel();
170  break;
171 
172  case 2:
173  // 3us
174  analog_readings[channelNumToIndex(current_channel)] = getADCReading();
175  adcStartConversion(adcPinToChannelNum(MOZZI_AUDIO_INPUT_PIN)); // -> result is ignored, but first thing in the next cycle, a second reading is taken.
176  break;
177 
178  }
179  adc_count++;
180 }
181 #else // no (legacy) audio input
187 inline void advanceADCStep() {
188  if (!adc_count) { // i.e. first step
189  //<1us
190  startSecondADCReadOnCurrentChannel(); // discard fist - noisy - reading, start another on same pin
191  adc_count=1;
192  } else {
193  // 3us
194  analog_readings[channelNumToIndex(current_channel)] = getADCReading(); // register second reading
195  adcReadSelectedChannels(); // start first reading on next pin (if any)
196  adc_count=0;
197  }
198 }
199 #endif
200 
201 #else
202 MOZZI_ASSERT_EQUAL(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE)
203 
204 uint16_t mozziAnalogRead(uint8_t pin) {
205  return analogRead(pin);
206 }
207 
208 #endif // MOZZI_ANALOG_READ
209 
211 
212 
214 static uint16_t update_control_timeout;
215 static uint16_t update_control_counter;
216 
217 inline void advanceControlLoop() {
218  if (!update_control_counter) {
219  update_control_counter = update_control_timeout;
220  updateControl();
221 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
222  adcStartReadCycle();
223 #endif
224  } else {
225  --update_control_counter;
226  }
227 }
228 
229 void audioHook() // 2us on AVR excluding updateAudio()
230 {
231 // setPin13High();
232  if (canBufferAudioOutput()) {
233  advanceControlLoop();
234  bufferAudioOutput(updateAudio());
235 
236 #if defined(LOOP_YIELD)
237  LOOP_YIELD
238 #endif
239 
240 #if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
241  if (audioInputAvailable()) audio_input = readAudioInput();
242 #endif
243  }
244 // Like LOOP_YIELD, but running every cycle of audioHook(), not just once per sample
245 #if defined(AUDIO_HOOK_HOOK)
246  AUDIO_HOOK_HOOK
247 #endif
248  // setPin13Low();
249 }
250 
251 // NOTE: This function counts the ticks of audio _output_, corresponding to real time elapsed.
252 // It does _not_ provide the count of the current audio frame to be generated by updateAudio(). These two things will differ, slightly,
253 // depending on the fill state of the buffer.
254 // 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,
255 // the existing semantics have always been in place, so far.
256 unsigned long audioTicks() {
257 #if (BYPASS_MOZZI_OUTPUT_BUFFER != true)
258  return output_buffer.count();
259 #elif defined(AUDIOTICK_ADJUSTMENT)
260  return samples_written_to_buffer - (AUDIOTICK_ADJUSTMENT);
261 #else
262  return samples_written_to_buffer;
263 #endif
264 }
265 
266 unsigned long mozziMicros() { return audioTicks() * MICROS_PER_AUDIO_TICK; }
267 
269 
271 void startMozzi(int control_rate_hz) {
272 #if !MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_NONE)
273  MozziPrivate::setupMozziADC(FAST_ADC); // you can use setupFastAnalogRead() with FASTER_ADC or FASTEST_ADC
274  // in setup() if desired (not for Teensy 3.* )
275 #endif
276  // delay(200); // so AutoRange doesn't read 0 to start with
277  update_control_timeout = MOZZI_AUDIO_RATE / control_rate_hz - 1;
278  startAudio();
279 }
280 
281 uint32_t MozziRandPrivate::x=132456789;
282 uint32_t MozziRandPrivate::y=362436069;
283 uint32_t MozziRandPrivate::z=521288629;
284 
286 }
287 
288 // reduce Macro leakage
289 #undef LOOP_YIELD
290 #undef BYPASS_MOZZI_OUTPUT_BUFFER
291 #undef AUDIO_HOOK_HOOK
292 #undef AUDIOTICK_ADJUSTMENT
293 #undef MOZZI__LEGACY_AUDIO_INPUT_IMPL
294 
295 // "export" publicly accessible functions defined in this file
296 // NOTE: unfortunately, we cannot just write "using MozziPrivate::mozziMicros()", etc. as that would conflict with, rather than define mozziMicros().
297 // Instead, for now, we forward the global-scope functions to their implementations inside MozziPrivate.
298 // We might want to rethink how this is done. What matters is that these functions are user accessible, though, while most of what we
299 // now keep in MozziPrivate is hidden away.
300 unsigned long mozziMicros() { return MozziPrivate::mozziMicros(); };
301 unsigned long audioTicks() { return MozziPrivate::audioTicks(); };
302 void startMozzi(int control_rate_hz) { MozziPrivate::startMozzi(control_rate_hz); };
303 void stopMozzi() { MozziPrivate::stopMozzi(); };
304 template<byte RES> uint16_t mozziAnalogRead(uint8_t pin) { return MozziPrivate::smartShift<MOZZI__INTERNAL_ANALOG_READ_RESOLUTION, RES>(MozziPrivate::mozziAnalogRead(pin));};
305 #if !MOZZI_IS(MOZZI_AUDIO_INPUT, MOZZI_AUDIO_INPUT_NONE)
306 template<byte RES> uint16_t getAudioInput() { return MozziPrivate::smartShift<MOZZI__INTERNAL_ANALOG_READ_RESOLUTION, RES>(MozziPrivate::getAudioInput()); };
307 #endif
308 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
309 void setupMozziADC(int8_t speed) { MozziPrivate::setupMozziADC(speed); };
310 void setupFastAnalogRead(int8_t speed) { MozziPrivate::setupFastAnalogRead(speed); };
311 uint8_t adcPinToChannelNum(uint8_t pin) { return MozziPrivate::adcPinToChannelNum(pin); };
312 #endif
313 void audioHook() { MozziPrivate::audioHook(); };
314 
315 // This is not strictly needed, but we want it to throw an error, if users have audioOutput() in their sketch without external output configured
316 #if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPUT_EXTERNAL_CUSTOM)
317 MOZZI_DEPRECATED("n/a", "Sketch has audioOutput() function, although external output is not configured.") void audioOutput(const AudioOutput) {};
318 #endif
319 #if !MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_CUSTOM)
320 // TODO: This won't work without a rename:
321 //MOZZI_DEPRECATED("n/a", "Sketch has canBufferAudioOutput() function, although custom external output is not configured.") bool canBufferAudioOutput() {};
322 #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:313
unsigned long audioTicks()
An alternative for Arduino time functions like micros() and millis().
Definition: MozziGuts.hpp:301
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:303
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:300
Internal.
Definition: mozzi_rand_p.h:15
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:111