Mozzi  version v2.0
sound synthesis library for Arduino
MozziGuts_impl_ESP32.hpp
1 /*
2  * MozziGuts_impl_ESP32.hpp
3  *
4  * This file is part of Mozzi.
5  *
6  * Copyright 2020-2024 Dieter Vandoren, Thomas Friedrichsmeier and the Mozzi Team
7  * Copyright 2025 Thomas Combriat and the Mozzi Team
8  *
9  * Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later.
10  *
11  */
12 
13 #if !(IS_ESP32())
14 # error "Wrong implementation included for this platform"
15 #endif
16 
17 namespace MozziPrivate {
19 #if MOZZI_IS(MOZZI_ANALOG_READ, MOZZI_ANALOG_READ_STANDARD)
20 #error not yet implemented
21 
22 #define getADCReading() 0
23 #define channelNumToIndex(channel) channel
24  uint8_t adcPinToChannelNum(uint8_t pin) {
25  return pin;
26  }
27  void adcStartConversion(uint8_t channel) {
28  }
29  void startSecondADCReadOnCurrentChannel() {
30  }
31  void setupMozziADC(int8_t speed) {
32  }
33  void setupFastAnalogRead(int8_t speed) {
34  }
35 
36 #endif
38 
39 } // namespace MozziPrivate
41  //#if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
42 #if /*MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC) || */MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
43 
44 //# include <driver/i2s.h> // for I2S-based output modes, including - technically - internal DAC
45 #include<driver/i2s_std.h>
46 
49 #if SOC_I2S_HW_VERSION_2
50 #undef I2S_STD_CLK_DEFAULT_CONFIG
51 #define I2S_STD_CLK_DEFAULT_CONFIG(rate) \
52  { .sample_rate_hz = rate, .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, }
53 #endif
55 /*
56 #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
57 #include<driver/dac_continuous.h>
58 namespace MozziPrivate {
59  static dac_continuous_handle_t dac_handle;
60  }*/
61 #endif
62 
63 //#elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
64 //#include<driver/i2s_pdm.h>
65 namespace MozziPrivate {
66  #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
67  const i2s_port_t i2s_num = MOZZI_I2S_PORT;
68  static i2s_chan_handle_t tx_handle;
69  // On ESP32 we cannot test wether the DMA buffer has room. Instead, we have to use a one-sample mini buffer. In each iteration we
70  // _try_ to write that sample to the DMA buffer, and if successful, we can buffer the next sample. Somewhat cumbersome, but works.
71  // TODO: Should ESP32 gain an implemenation of i2s_available(), we should switch to using that, instead.
72 #endif
73  static bool _esp32_can_buffer_next = true;
74 
75  # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
76  static uint8_t _esp32_prev_sample[2];
77  # define ESP_SAMPLE_SIZE (sizeof(uint8_t))
78  # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
79  # define ESP_SAMPLE_SIZE I2S_DATA_BIT_WIDTH_16BIT
80  static int16_t _esp32_prev_sample[2]; // for simplicity, but also because the output is cleaner (why??) we always send stereo samples
81  # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
82  static uint32_t _esp32_prev_sample[MOZZI_PDM_RESOLUTION];
83  # define ESP_SAMPLE_SIZE I2S_DATA_BIT_WIDTH_16BIT
84  # endif
85 
86 
87 
88  inline bool esp32_tryWriteSample() {
89  size_t bytes_written;
90  //i2s_write(i2s_num, &_esp32_prev_sample, ESP_SAMPLE_SIZE, &bytes_written, 0);
91  #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC)
92  i2s_channel_write(tx_handle,_esp32_prev_sample, 2*sizeof(_esp32_prev_sample[0]), &bytes_written, 0);
93  #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
94  i2s_channel_write(tx_handle,_esp32_prev_sample, MOZZI_PDM_RESOLUTION*sizeof(_esp32_prev_sample[0]), &bytes_written, 0);
95  /* #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
96  dac_continuous_write(dac_handle, _esp32_prev_sample, ESP_SAMPLE_SIZE, &bytes_written, 0);
97  */
98  #endif
99  return (bytes_written != 0);
100  }
101 
102  inline bool canBufferAudioOutput() {
103  if (_esp32_can_buffer_next) return true;
104  _esp32_can_buffer_next = esp32_tryWriteSample();
105  return _esp32_can_buffer_next;
106  }
107 
108 
109 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
110  inline void audioOutput(const AudioOutput f) {
111 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
112  dacWrite(25, f.l() + MOZZI_AUDIO_BIAS);
113 # if (MOZZI_AUDIO_CHANNELS > 1)
114  dacWrite(26, f.r() + MOZZI_AUDIO_BIAS);
115 # endif
116 
117 # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
118  ledcWrite(MOZZI_AUDIO_PIN_1,(f.l()+MOZZI_AUDIO_BIAS));
119 # if (MOZZI_AUDIO_CHANNELS > 1)
120  ledcWrite(MOZZI_AUDIO_PIN_2,(f.r()+MOZZI_AUDIO_BIAS));
121 # endif
122 
123 # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
124  for (uint8_t i=0; i<MOZZI_PDM_RESOLUTION; ++i) {
125  _esp32_prev_sample[i] = pdmCode32(f.l() + MOZZI_AUDIO_BIAS);
126  }
127 # else // EXTERNAL I2S
128  // PT8211 takes signed samples
129  _esp32_prev_sample[0] = f.l();
130  # if (MOZZI_AUDIO_CHANNELS > 1)
131  _esp32_prev_sample[1] = f.r();
132  # else
133  _esp32_prev_sample[1] = 0;
134  # endif
135 # endif
136 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
137  _esp32_can_buffer_next = esp32_tryWriteSample();
138 # endif
139  }
140  #endif
141  } // namespace MozziPrivate
142 
143 
144 namespace MozziPrivate {
145 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPUT_INTERNAL_DAC, MOZZI_OUTPUT_PWM)
146 #include <driver/gptimer.h>
147 
148  bool CACHED_FUNCTION_ATTR timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
149  {
150  defaultAudioOutput();
151  return true;
152  }
153 #endif
154 
155  static void startAudio() {
156  /* Normally, the internal DAC can run on DMA, hence self triggering. Did not managed to get that to work (see: https://github.com/espressif/arduino-esp32/issues/10851) so, for now, we are just using the Mozzi buffer and send dacWrite orders.
157  */
158 #if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_EXTERNAL_TIMED, MOZZI_OUTPUT_INTERNAL_DAC, MOZZI_OUTPUT_PWM) //|| MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM) // set up a timer running a audio rate
159 
160  gptimer_handle_t gptimer = NULL;
161  gptimer_config_t timer_config = {
162  .clk_src = GPTIMER_CLK_SRC_DEFAULT,
163  .direction = GPTIMER_COUNT_UP,
164  .resolution_hz = 40 * 1000 * 1000, // 40MHz
165 
166  };
167  gptimer_new_timer(&timer_config, &gptimer);
168 
169  gptimer_alarm_config_t alarm_config; // note: inline config for the flag does not work unless we have access to c++20, hence the manual attributes setting.
170  alarm_config.reload_count = 0;
171  alarm_config.alarm_count = (40000000UL / MOZZI_AUDIO_RATE);
172  alarm_config.flags.auto_reload_on_alarm = true;
173 
174  gptimer_set_alarm_action(gptimer, &alarm_config);
175 
176  gptimer_event_callbacks_t cbs = {
177  .on_alarm = timer_on_alarm_cb, // register user callback
178  };
179 
180 
181  gptimer_register_event_callbacks(gptimer,&cbs,NULL);
182  gptimer_enable(gptimer);
183  gptimer_start(gptimer);
184 
185 # if MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PWM)
187 # if (MOZZI_AUDIO_CHANNELS > 1)
188  ledcAttach(MOZZI_AUDIO_PIN_2, MOZZI_AUDIO_RATE, MOZZI_AUDIO_BITS);
189 # endif
190 # endif
191 
192 # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_I2S_DAC) || MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_PDM_VIA_I2S)
193 
194  static i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(MOZZI_I2S_PORT, I2S_ROLE_MASTER);
195  i2s_new_channel(&chan_cfg, &tx_handle, NULL);
196  //static const i2s_config_t i2s_config = {
197  i2s_std_config_t std_cfg = {
198  .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MOZZI_AUDIO_RATE * MOZZI_PDM_RESOLUTION),
199 # if MOZZI_IS(MOZZI_I2S_FORMAT, MOZZI_I2S_FORMAT_PLAIN)
200 # if (MOZZI_AUDIO_CHANNELS > 1)
201  .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(ESP_SAMPLE_SIZE, I2S_SLOT_MODE_STEREO),
202 # else // for some reason, sound is way better in stereo. Keeping that here in case this gets solved
203  .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(ESP_SAMPLE_SIZE, I2S_SLOT_MODE_STEREO),
204 # endif
205 # elif MOZZI_IS(MOZZI_I2S_FORMAT, MOZZI_I2S_FORMAT_LSBJ)
206 # if (MOZZI_AUDIO_CHANNELS > 1)
207  .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(ESP_SAMPLE_SIZE, I2S_SLOT_MODE_STEREO),
208 # else // for some reason, sound is way better in stereo. Keeping that here in case this gets solved
209  .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(ESP_SAMPLE_SIZE, I2S_SLOT_MODE_STEREO),
210 # endif
211 # endif
212  .gpio_cfg = {
213  .mclk = gpio_num_t(MOZZI_I2S_PIN_MCLK),
214  .bclk = gpio_num_t(MOZZI_I2S_PIN_BCK),
215  .ws = gpio_num_t(MOZZI_I2S_PIN_WS),
216  .dout = gpio_num_t(MOZZI_I2S_PIN_DATA),
217  .din = I2S_GPIO_UNUSED,
218  .invert_flags = {
219  .mclk_inv = MOZZI_I2S_MBCK_INV,
220  .bclk_inv = MOZZI_I2S_BCK_INV,
221  .ws_inv = MOZZI_I2S_WS_INV,
222  },
223  },
224 
225  /*
226 # elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
227  .slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG(ESP_SAMPLE_SIZE, I2S_SLOT_MODE_STEREO),
228  # endif*/
229 
230  };
231 
232 
233  i2s_channel_init_std_mode(tx_handle, &std_cfg);
234  i2s_channel_enable(tx_handle);
235 
236  /* #elif MOZZI_IS(MOZZI_AUDIO_MODE, MOZZI_OUTPUT_INTERNAL_DAC)
237  dac_continuous_config_t dac_config= {
238  .chan_mask = DAC_CHANNEL_MASK_ALL,
239  .desc_num = 2,
240  .buf_size = 4,
241  .freq_hz = MOZZI_AUDIO_RATE,
242  .offset = 120,
243  .clk_src = DAC_DIGI_CLK_SRC_DEFAULT,
244  .chan_mode = DAC_CHANNEL_MODE_SIMUL, };
245 
246  esp_err_t err;
247  dac_continuous_new_channels(&dac_config, &dac_handle);
248  err = dac_continuous_enable(dac_handle);
249  */
250  #endif
251  }
252 
253  void stopMozzi() {
254  // TODO: implement me
255  }
257 
259  void MozziRandPrivate::autoSeed() {
260  x = esp_random();
261  y = esp_random();
262  z = esp_random();
263  }
265 
266 #undef ESP_SAMPLE_SIZE // only used inside this file
267 
268 } // namespace MozziPrivate
#define MOZZI_AUDIO_BITS
Output resolution of audio samples.
#define MOZZI_AUDIO_PIN_1
Only for MOZZI_AUDIO_MODE s MOZZI_OUTPUT_PWM and MOZZI_OUTPUT_2PIN_PWM: The IO pin to use as (first) ...
#define MOZZI_AUDIO_RATE
Defines the audio rate, i.e.
void stopMozzi()
Stops audio and control interrupts and restores the timers to the values they had before Mozzi was st...
Definition: MozziGuts.hpp:303
Internal.
Definition: mozzi_rand_p.h:15
This struct encapsulates one frame of mono audio output.
Definition: AudioOutput.h:111