Skip to content

Commit b55c9f1

Browse files
committed
Merge branch 'master' of https://github.com/sensorium/Mozzi
2 parents f46ea52 + 60e5560 commit b55c9f1

File tree

18 files changed

+706
-183
lines changed

18 files changed

+706
-183
lines changed

AudioConfigRP2040.h

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,46 @@
55
#error This header should be included for RP2040, only
66
#endif
77

8+
9+
// AUDIO output modes
10+
#define PWM_VIA_BARE_CHIP 1 // output using one of the gpio of the board
11+
#define EXTERNAL_DAC_VIA_I2S 2 // output via external DAC connected to I2S (PT8211 or similar)
12+
13+
//******* BEGIN: These are the defines you may want to change. Best not to touch anything outside this range. ************/
14+
#define RP2040_AUDIO_OUT_MODE PWM_VIA_BARE_CHIP
15+
//******* END: These are the defines you may want to change. Best not to touch anything outside this range. ************/
16+
17+
18+
#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP)
819
#define AUDIO_CHANNEL_1_PIN 0
920
#if (AUDIO_CHANNELS > 1)
1021
// Audio channel pins for stereo or HIFI must be on the same PWM slice (which is the case for the pairs (0,1), (2,3), (4,5), etc.
11-
# define AUDIO_CHANNEL_2_PIN 1
22+
#define AUDIO_CHANNEL_2_PIN 1
1223
#endif
1324

1425
// The more audio bits you use, the slower the carrier frequency of the PWM signal. 11 bits yields ~ 60kHz on a 133Mhz CPU (which appears to be a reasonable compromise)
1526
#define AUDIO_BITS 11
27+
#endif
28+
29+
#if (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
30+
// ****** BEGIN: These are define you may want to change. Best not to touch anything outside this range. ************/
31+
#define BCLK_PIN 20
32+
#define WS_PIN (pBCLK+1) // CANNOT BE CHANGED, HAS TO BE NEXT TO pBCLK
33+
#define DOUT_PIN 22
34+
#define LSBJ_FORMAT false // some DAC, like the PT8211, use a variant of I2S data format called LSBJ
35+
// set this to true to use this kind of DAC or false for plain I2S.
36+
#define AUDIO_BITS 16 // available values are 8, 16, 24 (LEFT ALIGN in 32 bits type!!) and 32 bits
37+
// ****** END: These are define you may want to change. Best not to touch anything outside this range. ************/
38+
39+
#define BYPASS_MOZZI_OUTPUT_BUFFER true
40+
41+
// Configuration of the I2S port, especially DMA. Set in stone here as default of the library when this was written.
42+
// Probably do not change if you are not sure of what you are doing
43+
#define BUFFERS 8 // number of DMA buffers used
44+
#define BUFFER_SIZE 256 // total size of the buffer, in samples
45+
#endif
46+
47+
1648
#define AUDIO_BITS_PER_CHANNEL AUDIO_BITS
1749

1850
#define AUDIO_BIAS ((uint16_t) 1<<(AUDIO_BITS-1))

AudioOutput.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ struct StereoOutput {
103103
StereoOutput& clip() { _l = CLIP_AUDIO(_l); _r = CLIP_AUDIO(_r); return *this; };
104104

105105
/** @see MonoOutput::fromNBit(), stereo variant */
106-
static inline StereoOutput fromNBit(uint8_t bits, int16_t l, int16_t r) { return StereoOutput(SCALE_AUDIO(l, bits), SCALE_AUDIO(r, bits)); }
107-
/** @see MonoOutput::fromNBit(), stereo variant, 32 bit overload */
108-
static inline StereoOutput fromNBit(uint8_t bits, int32_t l, int32_t r) { return StereoOutput(SCALE_AUDIO(l, bits), SCALE_AUDIO(r, bits)); }
106+
template<typename T> static inline StereoOutput fromNBit(uint8_t bits, T l, T r) { return StereoOutput(SCALE_AUDIO(l, bits), SCALE_AUDIO(r, bits)); }
109107
/** @see MonoOutput::from8Bit(), stereo variant */
110108
static inline StereoOutput from8Bit(int16_t l, int16_t r) { return fromNBit(8, l, r); }
111109
/** @see MonoOutput::from16Bit(), stereo variant */
@@ -152,9 +150,7 @@ struct MonoOutput {
152150
/** Construct an audio frame a zero-centered value known to be in the N bit range. Appropriate left- or right-shifting will be performed, based on the number of output
153151
* bits available. While this function takes care of the shifting, beware of potential overflow issues, if your intermediary results exceed the 16 bit range. Use proper
154152
* casts to int32_t or larger in that case (and the compiler will automatically pick the 32 bit overload in this case) */
155-
static inline MonoOutput fromNBit(uint8_t bits, int16_t l) { return MonoOutput(SCALE_AUDIO(l, bits)); }
156-
/** 32bit overload. See above. */
157-
static inline MonoOutput fromNBit(uint8_t bits, int32_t l) { return MonoOutput(SCALE_AUDIO(l, bits)); }
153+
template<typename T> static inline MonoOutput fromNBit(uint8_t bits, T l) { return MonoOutput(SCALE_AUDIO(l, bits)); }
158154
/** Construct an audio frame from a zero-centered value known to be in the 8 bit range. On AVR, STANDADR or STANDARD_PLUS mode, this is effectively the same as calling the
159155
* constructor, directly (no scaling gets applied). On platforms/configs using more bits, an appropriate left-shift will be performed. */
160156
static inline MonoOutput from8Bit(int16_t l) { return fromNBit(8, l); }

LowPassFilter.h

Lines changed: 2 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -13,146 +13,7 @@
1313
#ifndef LOWPASS_H_
1414
#define LOWPASS_H_
1515

16-
#include "IntegerType.h"
17-
#include "AudioOutput.h"
18-
19-
20-
21-
/*
22-
simple resonant filter posted to musicdsp.org by Paul Kellett
23-
http://www.musicdsp.org/archive.php?classid=3#259
24-
25-
Two versions are available: LowPassFilter and LowPassFilter16.
26-
LowPassFilter is an optimized version that uses 8bits values to set
27-
the resonance and the cutoff_freq. It can works on 8bits samples only
28-
on 8bits platforms.
29-
LowPassFilter16 consumes more CPU ressources but uses 16bits values
30-
for resonance and cutoff_freq and can work on samples up to 16bits on
31-
8bits platforms and up to 32 on 32bits platforms.
32-
33-
// set feedback amount given f and q between 0 and 1
34-
fb = q + q/(1.0 - f);
35-
In order to avoid a slow division we use the use a Taylor expansion to approximate 1/(1.0 - f):
36-
Close to f=0: 1/(1.0-f) approx 1.0+f.
37-
Hence: fb = q + q * (1.0 + f)
38-
This approximation is less and less valid with an increasing cutoff, leading to a reduction of the resonance of the filter at high cutoff frequencies.
39-
40-
// for each sample...
41-
buf0 = buf0 + f * (in - buf0 + fb * (buf0 - buf1));
42-
buf1 = buf1 + f * (buf0 - buf1);
43-
out = buf1;
44-
45-
fixed point version of the filter
46-
"dave's blog of art and programming" http://www.pawfal.org/dave/blog/2011/09/
47-
*/
48-
49-
// we are using .n fixed point (n bits for the fractional part)
50-
//#define FX_SHIFT 8
51-
//#define SHIFTED_1 ((uint8_t)255)
52-
53-
/** A resonant low pass filter for audio signals.
54-
*/
55-
template<typename su=uint8_t>
56-
class LowPassFilterNbits
57-
{
58-
59-
public:
60-
/** Constructor.
61-
*/
62-
LowPassFilterNbits() { ; }
63-
64-
65-
/** deprecated. Use setCutoffFreqAndResonance(su cutoff, su
66-
resonance).
67-
68-
Set the cut off frequency,
69-
@param cutoff use the range 0-255 to represent 0-8191 Hz (AUDIO_RATE/2) for LowPassFilter, cutoff use the range 0-65535 to represent 0-AUDIO_RATE/2.
70-
Be careful of distortion at the lower end, especially with high resonance.
71-
*/
72-
void setCutoffFreq(su cutoff)
73-
{
74-
f = cutoff;
75-
fb = q + ucfxmul(q, (typename IntegerType<sizeof(su)+sizeof(su)>::unsigned_type) SHIFTED_1 + cutoff);
76-
}
77-
78-
/** deprecated. Use setCutoffFreqAndResonance(su cutoff, su
79-
resonance).
80-
81-
Set the resonance. If you hear unwanted distortion, back off the resonance.
82-
After setting resonance, you need to call setCuttoffFreq() to hear the change!
83-
@param resonance in the range 0-255 for LowPassFilter, 0-65535 for LowPassFilter16, with 255/65535 being most resonant
84-
@note Remember to call setCuttoffFreq() after resonance is changed!
85-
*/
86-
void setResonance(su resonance) { q = resonance; }
87-
88-
/**
89-
Set the cut off frequency and resonance. Replaces setCutoffFreq() and
90-
setResonance(). (Because the internal calculations need to be done whenever either parameter changes.)
91-
@param cutoff range 0-255 represents 0-8191 Hz (AUDIO_RATE/2) for LowPassFilter, range 0-65535 for LowPassFilter16
92-
Be careful of distortion at the lower end, especially with high resonance.
93-
@param resonance range 0-255 for LowPassFilter, 0-65535 for LowPassFilter16, 255/65535 is most resonant.
94-
*/
95-
void setCutoffFreqAndResonance(su cutoff, su resonance)
96-
{
97-
f = cutoff;
98-
q = resonance; // hopefully optimised away when compiled, just here for
99-
// backwards compatibility
100-
fb = q + ucfxmul(q,(typename IntegerType<sizeof(su)+sizeof(su)>::unsigned_type) SHIFTED_1 + cutoff);
101-
}
102-
103-
/** Calculate the next sample, given an input signal.
104-
@param in the signal input. Should not be more than 8bits on 8bits platforms (Arduino) if using LowPassFilter and not LowPassFilter16.
105-
@return the signal output.
106-
@note Timing: about 11us.
107-
*/
108-
// 10.5 to 12.5 us, mostly 10.5 us (was 14us)
109-
inline AudioOutputStorage_t next(AudioOutputStorage_t in)
110-
{
111-
// setPin13High();
112-
buf0 += fxmul(((in - buf0) + fxmul(fb, buf0 - buf1)), f);
113-
buf1 += ifxmul(buf0 - buf1, f); // could overflow if input changes fast
114-
return buf1;
115-
}
116-
117-
private:
118-
su q;
119-
su f;
120-
typename IntegerType<sizeof(su)+sizeof(su)>::unsigned_type fb;
121-
AudioOutputStorage_t buf0, buf1;
122-
const uint8_t FX_SHIFT = sizeof(su) << 3;
123-
const uint8_t FX_SHIFT_M_1 = FX_SHIFT-1;
124-
const su SHIFTED_1 = (1<<FX_SHIFT)-1;
125-
126-
// // multiply two fixed point numbers (returns fixed point)
127-
// inline
128-
// long fxmul(long a, long b)
129-
// {
130-
// return (a*b)>>FX_SHIFT;
131-
// }
132-
133-
// multiply two fixed point numbers (returns fixed point)
134-
inline typename IntegerType<sizeof(su)+sizeof(su)>::unsigned_type ucfxmul(su a, typename IntegerType<sizeof(su)+sizeof(su)>::unsigned_type b)
135-
{
136-
return (((typename IntegerType<sizeof(su)+sizeof(su)>::unsigned_type)a * (b >> 1)) >> (FX_SHIFT_M_1));
137-
}
138-
139-
// multiply two fixed point numbers (returns fixed point)
140-
inline typename IntegerType<sizeof(AudioOutputStorage_t)+sizeof(su)-1>::signed_type ifxmul(typename IntegerType<sizeof(AudioOutputStorage_t )+sizeof(su)-1>::signed_type a, su b) { return ((a * b) >> FX_SHIFT); }
141-
142-
// multiply two fixed point numbers (returns fixed point)
143-
inline typename IntegerType<sizeof(AudioOutputStorage_t)+sizeof(AudioOutputStorage_t)>::signed_type fxmul(typename IntegerType<sizeof(AudioOutputStorage_t)+sizeof(AudioOutputStorage_t)>::signed_type a, typename IntegerType<sizeof(AudioOutputStorage_t)+sizeof(su)-1>::signed_type b) { return ((a * b) >> FX_SHIFT); }
144-
};
145-
146-
typedef LowPassFilterNbits<> LowPassFilter;
147-
typedef LowPassFilterNbits<uint16_t> LowPassFilter16;
148-
149-
150-
/**
151-
@example 10.Audio_Filters/LowPassFilter/LowPassFilter.ino
152-
This example demonstrates the LowPassFilter class.
153-
154-
@example 10.Audio_Filters/LowPassFilter/LowPassFilter16.ino
155-
This example demonstrates the LowPassFilter16 class.
156-
*/
16+
#include<ResonantFilter.h>
17+
#warning This header is deprecated, please use ResonantFilter.h instead.
15718

15819
#endif /* LOWPASS_H_ */

Mozzi

Lines changed: 0 additions & 1 deletion
This file was deleted.

MozziGuts_impl_ESP32.hpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ void setupMozziADC(int8_t speed) {
3737

3838

3939

40+
4041
//// BEGIN AUDIO OUTPUT code ///////
4142
#include <driver/i2s.h> // for I2S-based output modes
4243
#include <driver/timer.h> // for EXTERNAL_AUDIO_OUTPUT
@@ -103,17 +104,19 @@ void CACHED_FUNCTION_ATTR timer0_audio_output_isr(void *) {
103104
static void startAudio() {
104105
#if (BYPASS_MOZZI_OUTPUT_BUFFER != true) // for external audio output, set up a timer running a audio rate
105106
static intr_handle_t s_timer_handle;
107+
const int div = 2;
106108
timer_config_t config = {
107-
.alarm_en = true,
108-
.counter_en = false,
109-
.intr_type = TIMER_INTR_LEVEL,
109+
.alarm_en = (timer_alarm_t)true,
110+
.counter_en = (timer_start_t)false,
111+
.intr_type = (timer_intr_mode_t) TIMER_INTR_LEVEL,
110112
.counter_dir = TIMER_COUNT_UP,
111-
.auto_reload = true,
112-
.divider = 1 // For max available precision: The APB_CLK clock signal is running at 80 MHz, i.e. 1/80 uS per tick
113+
.auto_reload = (timer_autoreload_t) true,
114+
.divider = div // For max available precision: The APB_CLK clock signal is running at 80 MHz, i.e. 2/80 uS per tick
115+
// Min acceptable value is 2
113116
};
114117
timer_init(TIMER_GROUP_0, TIMER_0, &config);
115118
timer_set_counter_value(TIMER_GROUP_0, TIMER_0, 0);
116-
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 80000000UL / AUDIO_RATE);
119+
timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 80000000UL / AUDIO_RATE / div);
117120
timer_enable_intr(TIMER_GROUP_0, TIMER_0);
118121
timer_isr_register(TIMER_GROUP_0, TIMER_0, &timer0_audio_output_isr, nullptr, 0, &s_timer_handle);
119122
timer_start(TIMER_GROUP_0, TIMER_0);

MozziGuts_impl_RP2040.hpp

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ void setupMozziADC(int8_t speed) {
8383
);
8484

8585
uint dma_chan = dma_claim_unused_channel(true);
86+
rp2040_adc_dma_chan = dma_chan;
8687
static dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
8788

8889
// Reading from constant address, writing to incrementing byte addresses
@@ -102,12 +103,14 @@ void setupMozziADC(int8_t speed) {
102103

103104
// we want notification, when a sample has arrived
104105
dma_channel_set_irq0_enabled(dma_chan, true);
105-
irq_set_exclusive_handler(DMA_IRQ_0, rp2040_adc_queue_handler);
106+
irq_add_shared_handler(DMA_IRQ_0, rp2040_adc_queue_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
106107
irq_set_enabled(DMA_IRQ_0, true);
108+
dma_channel_start(dma_chan);
107109
}
108110

109111
void rp2040_adc_queue_handler() {
110-
dma_hw->ints0 = 1u << rp2040_adc_dma_chan; // clear interrupt flag
112+
if (!dma_channel_get_irq0_status(rp2040_adc_dma_chan)) return; // shared handler may get called on unrelated events
113+
dma_channel_acknowledge_irq0(rp2040_adc_dma_chan); // clear interrupt flag
111114
//adc_run(false); // adc not running continuous
112115
//adc_fifo_drain(); // no need to drain fifo, the dma transfer did that
113116
dma_channel_set_trans_count(rp2040_adc_dma_chan, 1, true); // set up for another read
@@ -119,16 +122,17 @@ void rp2040_adc_queue_handler() {
119122
////// BEGIN audio output code //////
120123
#define LOOP_YIELD tight_loop_contents(); // apparently needed, among other things, to service the alarm pool
121124

122-
#include <hardware/pwm.h>
123125

126+
#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true)
127+
#include <hardware/pwm.h>
124128
#if (EXTERNAL_AUDIO_OUTPUT != true) // otherwise, the last stage - audioOutput() - will be provided by the user
125129
inline void audioOutput(const AudioOutput f) {
126130
pwm_set_gpio_level(AUDIO_CHANNEL_1_PIN, f.l()+AUDIO_BIAS);
127131
#if (AUDIO_CHANNELS > 1)
128132
pwm_set_gpio_level(AUDIO_CHANNEL_2_PIN, f.r()+AUDIO_BIAS);
129133
#endif
130134
}
131-
#endif
135+
#endif // #if (EXTERNAL_AUDIO_OUTPUT != true)
132136

133137
#include <pico/time.h>
134138
/** Implementation notes:
@@ -150,8 +154,56 @@ void audioOutputCallback(uint) {
150154
// NOTE: hardware_alarm_set_target returns true, if the target was already missed. In that case, keep pushing samples, until we have caught up.
151155
} while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
152156
}
157+
158+
#elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
159+
#include <I2S.h>
160+
I2S i2s(OUTPUT);
161+
162+
163+
inline bool canBufferAudioOutput() {
164+
return (i2s.availableForWrite());
165+
}
166+
167+
inline void audioOutput(const AudioOutput f) {
168+
169+
#if (AUDIO_BITS == 8)
170+
#if (AUDIO_CHANNELS > 1)
171+
i2s.write8(f.l(), f.r());
172+
#else
173+
i2s.write8(f.l(), 0);
174+
#endif
175+
176+
#elif (AUDIO_BITS == 16)
177+
#if (AUDIO_CHANNELS > 1)
178+
i2s.write16(f.l(), f.r());
179+
#else
180+
i2s.write16(f.l(), 0);
181+
#endif
182+
183+
#elif (AUDIO_BITS == 24)
184+
#if (AUDIO_CHANNELS > 1)
185+
i2s.write24(f.l(), f.r());
186+
#else
187+
i2s.write24(f.l(), 0);
188+
#endif
189+
190+
#elif (AUDIO_BITS == 32)
191+
#if (AUDIO_CHANNELS > 1)
192+
i2s.write32(f.l(), f.r());
193+
#else
194+
i2s.write32(f.l(), 0);
195+
#endif
196+
#else
197+
#error The number of AUDIO_BITS set in AudioConfigRP2040.h is incorrect
198+
#endif
199+
200+
201+
}
202+
#endif
203+
153204

154205
static void startAudio() {
206+
#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true) // EXTERNAL AUDIO needs the timers set here
155207
#if (EXTERNAL_AUDIO_OUTPUT != true)
156208
// calling analogWrite for the first time will try to init the pwm frequency and range on all pins. We don't want that happening after we've set up our own,
157209
// so we start off with a dummy call to analogWrite:
@@ -185,9 +237,30 @@ static void startAudio() {
185237
next_audio_update = make_timeout_time_us(micros_per_update);
186238
// See audioOutputCallback(), above. In _theory_ some interrupt stuff might delay us, here, causing us to miss the first beat (and everything that follows)
187239
} while (hardware_alarm_set_target(audio_update_alarm_num, next_audio_update));
240+
241+
#elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
242+
i2s.setBCLK(BCLK_PIN);
243+
i2s.setDATA(DOUT_PIN);
244+
i2s.setBitsPerSample(AUDIO_BITS);
245+
246+
#if (AUDIO_BITS > 16)
247+
i2s.setBuffers(BUFFERS, (size_t) (BUFFER_SIZE/BUFFERS), 0);
248+
#else
249+
i2s.setBuffers(BUFFERS, (size_t) (BUFFER_SIZE/BUFFERS/2), 0);
250+
#endif
251+
#if (LSBJ_FORMAT == true)
252+
i2s.setLSBJFormat();
253+
#endif
254+
i2s.begin(AUDIO_RATE);
255+
#endif
188256
}
189257

190258
void stopMozzi() {
259+
#if (RP2040_AUDIO_OUT_MODE == PWM_VIA_BARE_CHIP) || (EXTERNAL_AUDIO_OUTPUT == true)
191260
hardware_alarm_set_callback(audio_update_alarm_num, NULL);
261+
#elif (RP2040_AUDIO_OUT_MODE == EXTERNAL_DAC_VIA_I2S)
262+
i2s.end();
263+
#endif
264+
192265
}
193266
////// END audio output code //////

0 commit comments

Comments
 (0)