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 
19 /** @brief Internal. Do not use function in this namespace in your sketch!
20 
21 This namespace contains various functions that are used by Mozzi, internally, but are not meant to be used in a sketch.
22 
23 The details of these may change without warning. I repeat: Do not use these in your sketch!
24 */
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 {
80 ////// BEGIN Output buffering /////
81 #if BYPASS_MOZZI_OUTPUT_BUFFER == true
83 
84 inline void bufferAudioOutput(const AudioOutput_t f) {
85  audioOutput(f);
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)
102 ////// END Output buffering ///////
103 
104 
105 ////// BEGIN Analog input code ////////
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"
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 */
121  // ugly syntax below saves a few bytes/instructions (as current_channel is declared volatile)
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
133 #else
135  adc_count = 0;
136 #endif
137  }
138 }
139 
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
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())
156 /** NOTE: Triggered at MOZZI_AUDIO_RATE via defaultAudioOutput(). In addition to the AUDIO_INPUT_PIN, at most one reading is taken for mozziAnalogRead(). */
157 inline void advanceADCStep() {
158  switch (adc_count) {
159  case 0:
160  // 6us
161  // the input pin was the last thing we read
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();
170  break;
171 
172  case 2:
173  // 3us
175  adcStartConversion(adcPinToChannelNum(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
182 /** NOTE: Triggered at CONTROL_RATE via advanceControlLoop().
183 
184 This interrupt handler cycles through all analog inputs on the adc_channels_to_read Stack,
185 doing 2 conversions on each channel but only keeping the second conversion each time,
186 because the first conversion after changing channels is often inaccurate (on atmel-based arduinos).*/
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
203 
205  return analogRead(pin);
206 }
207 
208 #endif // MOZZI_ANALOG_READ
209 
210 ////// END analog input code ////////
211 
212 
213 ////// BEGIN audio/control hook /////
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 
268 ////// END audio/control hook /////
269 
270 ////// BEGIN initialization ///////
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;
278  startAudio();
279 }
280 
281 uint32_t MozziRandPrivate::x=132456789;
282 uint32_t MozziRandPrivate::y=362436069;
283 uint32_t MozziRandPrivate::z=521288629;
284 
285 ////// END initialization ///////
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