Mozzi  version v1.1.0
sound synthesis library for Arduino
MozziGuts.cpp
1 /*
2  * MozziGuts.cpp
3  *
4  * Copyright 2012 Tim Barrass.
5  *
6  * This file is part of Mozzi.
7  *
8  * Mozzi by Tim Barrass is licensed under a Creative Commons
9  * Attribution-NonCommercial-ShareAlike 4.0 International License.
10  *
11  */
12 #include <Arduino.h>
13 
14 #include "CircularBuffer.h"
15 #include "MozziGuts.h"
16 #include "mozzi_analog.h"
17 #include "mozzi_config.h" // at the top of all MozziGuts and analog files
18 //#include "mozzi_utils.h"
19 #include "AudioOutput.h"
20 
21 // forward-declarations for use in hardware-specific implementations:
22 static void advanceADCStep();
23 static uint8_t adc_count = 0;
24 
25 // forward-declarations; to be supplied by plaform specific implementations
26 static void startSecondADCReadOnCurrentChannel();
27 
28 ////// BEGIN Output buffering /////
29 #if BYPASS_MOZZI_OUTPUT_BUFFER == true
30 uint64_t samples_written_to_buffer = 0;
31 
32 inline void bufferAudioOutput(const AudioOutput_t f) {
33  audioOutput(f);
34  ++samples_written_to_buffer;
35 }
36 #else
37 # if (STEREO_HACK == true)
38 // ring buffer for audio output
39 CircularBuffer<StereoOutput> output_buffer; // fixed size 256
40 # else
41 CircularBuffer<AudioOutput_t> output_buffer; // fixed size 256
42 # endif
43 # define canBufferAudioOutput() (!output_buffer.isFull())
44 # define bufferAudioOutput(f) output_buffer.write(f)
45 static void CACHED_FUNCTION_ATTR defaultAudioOutput() {
46 # if (USE_AUDIO_INPUT == true)
47  adc_count = 0;
48  startSecondADCReadOnCurrentChannel(); // the current channel is the AUDIO_INPUT pin
49 # endif
50  audioOutput(output_buffer.read());
51 }
52 #endif
53 ////// END Output buffering ///////
54 
55 
56 // Include the appropriate implementation
57 #if IS_AVR()
58 # include "MozziGuts_impl_AVR.hpp"
59 #elif IS_STM32()
60 # include "MozziGuts_impl_STM32.hpp"
61 #elif IS_ESP32()
62 # include "MozziGuts_impl_ESP32.hpp"
63 #elif IS_ESP8266()
64 # include "MozziGuts_impl_ESP8266.hpp"
65 #elif (IS_TEENSY3() || IS_TEENSY4())
66 # include "MozziGuts_impl_TEENSY.hpp"
67 #elif (IS_SAMD21())
68 # include "MozziGuts_impl_SAMD.hpp"
69 #elif (IS_RP2040())
70 # include "MozziGuts_impl_RP2040.hpp"
71 #else
72 # error "Platform not (yet) supported. Check MozziGuts_impl_template.hpp and existing implementations for a blueprint for adding your favorite MCU."
73 #endif
74 
75 
76 ////// BEGIN Analog inpput code ////////
77 /* Analog input code was informed initially by a discussion between
78 jRaskell, bobgardner, theusch, Koshchi, and code by jRaskell.
79 http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=789581
80 */
81 
82 #include "Stack.h"
83 static volatile int analog_readings[NUM_ANALOG_INPUTS];
84 static Stack <volatile int8_t,NUM_ANALOG_INPUTS> adc_channels_to_read;
85 volatile static int8_t current_channel = -1; // volatile because accessed in control and adc ISRs
86 
87 /* 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.
88 */
89 void adcReadSelectedChannels() {
90  // ugly syntax below saves a few bytes/instructions (as current_channel is declared volatile)
91  if((current_channel = adc_channels_to_read.pop()) >= 0) adcStartConversion(current_channel);
92 }
93 
94 /* Called each time in updateControlWithAutoADC(), after updateControl()
95  Forbidding inline, here, saves a wholesome 16 bytes flash on AVR (without USE_AUDIO_INPUT). No idea, why.
96 */
97 __attribute__((noinline)) void adcStartReadCycle() {
98  if (current_channel < 0) // last read of adc_channels_to_read stack was empty, ie. all channels from last time have been read
99  {
100 #if (USE_AUDIO_INPUT == true)
101  adc_channels_to_read.push(AUDIO_INPUT_PIN); // for audio
102 #else
103  adcReadSelectedChannels();
104  adc_count = 0;
105 #endif
106  }
107 }
108 
109 int mozziAnalogRead(uint8_t pin) {
110 #if defined(MOZZI_FAST_ANALOG_IMPLEMENTED)
111  pin = adcPinToChannelNum(pin); // allow for channel or pin numbers; on most platforms other than AVR this has no effect. See note on pins/channels
112  adc_channels_to_read.push(pin);
113  return analog_readings[channelNumToIndex(pin)];
114 #else
115 # warning Asynchronouos analog reads not implemented for this platform
116  return analogRead(pin);
117 #endif
118 }
119 
120 #if (USE_AUDIO_INPUT == true)
121 // ring buffer for audio input
122 CircularBuffer<unsigned int> input_buffer; // fixed size 256
123 
124 static int audio_input; // holds the latest audio from input_buffer
125 
126 int getAudioInput() { return audio_input; }
127 
128 /** NOTE: Triggered at AUDIO_RATE via defaultAudioOutput(). In addition to the AUDIO_INPUT_PIN, at most one reading is taken for mozziAnalogRead(). */
129 inline void advanceADCStep() {
130  switch (adc_count) {
131  case 0:
132  // 6us
133  // the input pin was the last thing we read
134  input_buffer.write(getADCReading());
135  adcReadSelectedChannels(); // TODO: doesn't this stop the cycle, in case no pins to read?
136  break;
137 
138  case 1:
139  // <2us, <1us w/o receive
140  // receiveFirstControlADC();
141  startSecondADCReadOnCurrentChannel();
142  break;
143 
144  case 2:
145  // 3us
146  analog_readings[channelNumToIndex(current_channel)] = getADCReading();
147  adcStartConversion(adcPinToChannelNum(AUDIO_INPUT_PIN)); // -> result is ignored, but first thing in the next cycle, a second reading is taken.
148  break;
149 
150  }
151  adc_count++;
152 }
153 #else
154 /** NOTE: Triggered at CONTROL_RATE via advanceControlLoop().
155 
156 This interrupt handler cycles through all analog inputs on the adc_channels_to_read Stack,
157 doing 2 conversions on each channel but only keeping the second conversion each time,
158 because the first conversion after changing channels is often inaccurate (on atmel-based arduinos).*/
159 inline void advanceADCStep() {
160  if (!adc_count) { // i.e. first step
161  //<1us
162  startSecondADCReadOnCurrentChannel(); // discard fist - noisy - reading, start another on same pin
163  adc_count=1;
164  } else {
165  // 3us
166  analog_readings[channelNumToIndex(current_channel)] = getADCReading(); // register second reading
167  adcReadSelectedChannels(); // start first reading on next pin (if any)
168  adc_count=0;
169  }
170 }
171 #endif
172 
173 ////// END analog input code ////////
174 
175 
176 ////// BEGIN audio/control hook /////
177 static uint16_t update_control_timeout;
178 static uint16_t update_control_counter;
179 
180 inline void advanceControlLoop() {
181  if (!update_control_counter) {
182  update_control_counter = update_control_timeout;
184  adcStartReadCycle();
185  } else {
186  --update_control_counter;
187  }
188 }
189 
190 void audioHook() // 2us on AVR excluding updateAudio()
191 {
192 // setPin13High();
193 #if (USE_AUDIO_INPUT == true)
194  if (!input_buffer.isEmpty())
195  audio_input = input_buffer.read();
196 #endif
197 
198  if (canBufferAudioOutput()) {
199  advanceControlLoop();
200 #if (STEREO_HACK == true)
201  updateAudio(); // in hacked version, this returns void
202  bufferAudioOutput(StereoOutput(audio_out_1, audio_out_2));
203 #else
204  bufferAudioOutput(updateAudio());
205 #endif
206 
207 #if defined(LOOP_YIELD)
208  LOOP_YIELD
209 #endif
210  }
211  // setPin13Low();
212 }
213 
214 // NOTE: This function counts the ticks of audio _output_, corresponding to real time elapsed.
215 // It does _not_ provide the count of the current audio frame to be generated by updateAudio(). These two things will differ, slightly,
216 // depending on the fill state of the buffer.
217 // 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,
218 // the existing semantics have always been in place, so far.
219 unsigned long audioTicks() {
220 #if (BYPASS_MOZZI_OUTPUT_BUFFER != true)
221  return output_buffer.count();
222 #elif defined(AUDIOTICK_ADJUSTMENT)
223  return samples_written_to_buffer - (AUDIOTICK_ADJUSTMENT);
224 #else
225  return samples_written_to_buffer;
226 #endif
227 }
228 
229 unsigned long mozziMicros() { return audioTicks() * MICROS_PER_AUDIO_TICK; }
230 
231 ////// END audio/control hook /////
232 
233 ////// BEGIN initialization ///////
234 void startMozzi(int control_rate_hz) {
235  setupMozziADC(); // you can use setupFastAnalogRead() with FASTER or FASTEST
236  // in setup() if desired (not for Teensy 3.* )
237  setupFastAnalogRead();
238  // delay(200); // so AutoRange doesn't read 0 to start with
239  update_control_timeout = AUDIO_RATE / control_rate_hz;
240  startAudio();
241 }
242 
243 ////// END initialization ///////
unsigned long mozziMicros()
An alternative for Arduino time functions like micros() and millis().
Definition: MozziGuts.cpp:229
int mozziAnalogRead(uint8_t pin)
Reads the analog input of a chosen channel, without blocking other operations from running...
Definition: MozziGuts.cpp:109
#define IS_SAMD21()
#define IS_TEENSY4()
void updateControl()
This is where you put your control code.
#define AUDIO_RATE
Holds the audio rate setting.
Definition: mozzi_config.h:62
Circular buffer object.
#define IS_STM32()
void audioHook()
This is required in Arduino&#39;s loop().
Definition: MozziGuts.cpp:190
#define IS_AVR()
#define IS_TEENSY3()
#define IS_RP2040()
unsigned long audioTicks()
An alternative for Arduino time functions like micros() and millis().
Definition: MozziGuts.cpp:219
#define AudioOutput_t
Representation of an single audio output sample/frame.
Definition: AudioOutput.h:77
#define MICROS_PER_AUDIO_TICK
Definition: MozziGuts.h:194
#define CACHED_FUNCTION_ATTR
void startMozzi(int control_rate_hz=CONTROL_RATE)
Sets up the timers for audio and control rate processes, storing the timer registers so they can be r...
Definition: MozziGuts.cpp:234
#define IS_ESP8266()
#define IS_ESP32()