Skip to content

Commit 07d5d7a

Browse files
committed
Mozzi support
1 parent 1f3a6f6 commit 07d5d7a

File tree

4 files changed

+264
-1
lines changed

4 files changed

+264
-1
lines changed

examples/streams-mozzi-a2dp/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Using Mozzi on Bluetooth
2+
3+
I am providing a simple integration for [Mozzi](https://sensorium.github.io/Mozzi/). Unfortunatly I needed to make some small changes to make things work together. Those can be found in my [fork](https://github.com/pschatzmann/Mozzi).
4+
5+
A standard Sketch will need to combine the Audio-Tools with the Mozzi Logic:
6+
7+
```
8+
#include "AudioTools.h"
9+
#include "AudioA2DP.h"
10+
#include "AudioMozzi.h"
11+
// Mozzi includes
12+
// ....
13+
14+
15+
using namespace audio_tools;
16+
17+
// Audio Tools
18+
MozziGenerator mozzi(CONTROL_RATE); // subclass of SoundGenerator
19+
GeneratedSoundStream<int16_t> in(mozzi, 2); // Stream with 2 channels generated with mozzi
20+
A2DPStream out = A2DPStream::instance() ; // A2DP input - A2DPStream is a singleton!
21+
StreamCopy copier(out, in); // copy in to out
22+
23+
```
24+
25+
As you can see above you can add the __MozziGenerator__ like any other Generator to the __GeneratedSoundStream__ class.
26+
In the setup you need to combine the Audio-Tools setup and then call the Mozzi specific setup functions. Replace the __startMozzi()__ with our __GeneratedSoundStream.begin()__:
27+
28+
```
29+
void setup(){
30+
Serial.begin(115200);
31+
32+
if (mozzi.config().sample_rate!=44100){
33+
Serial.println("Please set the AUDIO_RATE in the mozzi_config.h to 44100");
34+
stop();
35+
}
36+
37+
// We send the generated sound via A2DP - so we conect to the MyMusic Bluetooth Speaker
38+
out.begin(TX_MODE, "MyMusic");
39+
Serial.println("A2DP is connected now...");
40+
41+
// Add your Mozzi setup here
42+
// ...
43+
in.begin();
44+
}
45+
```
46+
47+
Like in any other Mozzi Sketch you need to define the __updateControl()__
48+
49+
```
50+
// Mozzi updateControl
51+
void updateControl(){
52+
}
53+
```
54+
55+
And like in any other Mozzi Sketch you need to define the __updateAudio()__
56+
57+
```
58+
// Mozzi updateAudio
59+
AudioOutput_t updateAudio(){
60+
}
61+
```
62+
63+
In the loop you need to replace the __audioHook()__ with your copy logic:
64+
65+
```
66+
// Arduino loop
67+
void loop() {
68+
if (out)
69+
copier.copy();
70+
}
71+
```
72+
73+
That's all to output the generated sound to a Bluetooth Speaker...
74+
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @file streams-generator-a2dp.ino
3+
* @author Phil Schatzmann
4+
* @brief see https://github.com/pschatzmann/arduino-audio-tools/blob/main/examples/streams-generator-a2dp/README.md
5+
*
6+
* @author Phil Schatzmann
7+
* @copyright GPLv3
8+
*
9+
*/
10+
#include "AudioTools.h"
11+
#include "AudioA2DP.h"
12+
#include "AudioMozzi.h"
13+
#include <Oscil.h>
14+
#include <tables/cos2048_int8.h> // table for Oscils to play
15+
#include <mozzi_fixmath.h>
16+
#include <EventDelay.h>
17+
#include <mozzi_rand.h>
18+
#include <mozzi_midi.h>
19+
20+
using namespace audio_tools;
21+
22+
typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)
23+
uint8_t channels = 2; // The stream will have 2 channels
24+
MozziGenerator mozzi; // subclass of SoundGenerator
25+
GeneratedSoundStream<sound_t> in(mozzi, channels); // Stream generated with mozzi
26+
A2DPStream out = A2DPStream::instance() ; // A2DP input - A2DPStream is a singleton!
27+
StreamCopy copier(out, in); // copy in to out
28+
29+
/// Copied from AMsynth.ino
30+
#define CONTROL_RATE 64 // Hz, powers of 2 are most reliable
31+
32+
// audio oscils
33+
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aCarrier(COS2048_DATA);
34+
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aModulator(COS2048_DATA);
35+
Oscil<COS2048_NUM_CELLS, AUDIO_RATE> aModDepth(COS2048_DATA);
36+
37+
// for scheduling note changes in updateControl()
38+
EventDelay kNoteChangeDelay;
39+
40+
// synthesis parameters in fixed point formats
41+
Q8n8 ratio; // unsigned int with 8 integer bits and 8 fractional bits
42+
Q24n8 carrier_freq; // unsigned long with 24 integer bits and 8 fractional bits
43+
Q24n8 mod_freq; // unsigned long with 24 integer bits and 8 fractional bits
44+
45+
// for random notes
46+
Q8n0 octave_start_note = 42;
47+
48+
49+
void setup(){
50+
Serial.begin(115200);
51+
52+
if (mozzi.info().sample_rate!=44100){
53+
Serial.println("Please set the AUDIO_RATE in the mozzi_config.h to 44100");
54+
stop();
55+
}
56+
57+
// We send the test signal via A2DP - so we conect to the MyMusic Bluetooth Speaker
58+
out.begin(TX_MODE, "MyMusic");
59+
Serial.println("A2DP is connected now...");
60+
61+
ratio = float_to_Q8n8(3.0f); // define modulation ratio in float and convert to fixed-point
62+
kNoteChangeDelay.set(200); // note duration ms, within resolution of CONTROL_RATE
63+
aModDepth.setFreq(13.f); // vary mod depth to highlight am effects
64+
randSeed(); // reseed the random generator for different results each time the sketch runs
65+
mozzi.begin(CONTROL_RATE);
66+
}
67+
68+
void updateControl(){
69+
static Q16n16 last_note = octave_start_note;
70+
71+
if(kNoteChangeDelay.ready()){
72+
73+
// change octave now and then
74+
if(rand((byte)5)==0){
75+
last_note = 36+(rand((byte)6)*12);
76+
}
77+
78+
// change step up or down a semitone occasionally
79+
if(rand((byte)13)==0){
80+
last_note += 1-rand((byte)3);
81+
}
82+
83+
// change modulation ratio now and then
84+
if(rand((byte)5)==0){
85+
ratio = ((Q8n8) 1+ rand((byte)5)) <<8;
86+
}
87+
88+
// sometimes add a fractionto the ratio
89+
if(rand((byte)5)==0){
90+
ratio += rand((byte)255);
91+
}
92+
93+
// step up or down 3 semitones (or 0)
94+
last_note += 3 * (1-rand((byte)3));
95+
96+
// convert midi to frequency
97+
Q16n16 midi_note = Q8n0_to_Q16n16(last_note);
98+
carrier_freq = Q16n16_to_Q24n8(Q16n16_mtof(midi_note));
99+
100+
// calculate modulation frequency to stay in ratio with carrier
101+
mod_freq = (carrier_freq * ratio)>>8; // (Q24n8 Q8n8) >> 8 = Q24n8
102+
103+
// set frequencies of the oscillators
104+
aCarrier.setFreq_Q24n8(carrier_freq);
105+
aModulator.setFreq_Q24n8(mod_freq);
106+
107+
// reset the note scheduler
108+
kNoteChangeDelay.start();
109+
}
110+
}
111+
112+
AudioOutput_t updateAudio(){
113+
long mod = (128u+ aModulator.next()) * ((byte)128+ aModDepth.next());
114+
return MonoOutput::fromNBit(24, mod * aCarrier.next());
115+
}
116+
117+
// Arduino loop
118+
void loop() {
119+
if (out)
120+
copier.copy();
121+
}

src/AudioConfig.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
* @brief Optional Functionality - comment out if not wanted
1111
*/
1212

13+
// Activate ESP32 Audio - for ESP32, ESP8266 and Raspberry Pico
1314
#define USE_ESP8266_AUDIO
15+
16+
// Activate the A2DP library - for ESP32 only
1417
#ifdef ESP32
1518
#define USE_A2DP
1619
#endif
@@ -33,7 +36,6 @@
3336
#define DEFAUT_ADC_PIN 34
3437
#define I2S_TAG "I2S"
3538
#define AUDIO_TAG "AUDIO"
36-
3739
/**
3840
* @brief Platform specific Settings
3941
*
@@ -50,6 +52,8 @@
5052
#define PIN_I2S_MUTE 23
5153
#define SOFT_MUTE_VALUE LOW
5254

55+
56+
5357
#endif
5458

5559
#ifdef ESP8266

src/AudioMozzi.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#pragma once
2+
3+
#include "mozzi_config.h"
4+
#include "hardware_defines.h"
5+
#include "mozzi_analog.h"
6+
#include "MozziGuts.h"
7+
8+
unsigned long total_tick_count = 0;
9+
10+
unsigned long audioTicks() {
11+
return total_tick_count;
12+
}
13+
14+
namespace audio_tools {
15+
16+
/**
17+
* @brief Support for https://sensorium.github.io/Mozzi/
18+
* Define your updateControl() method.
19+
* Define your updateAudio() method.
20+
* Start by calling begin(control_rate)
21+
* do not call audioHook(); in the loop !
22+
23+
* @author Phil Schatzmann
24+
* @copyright GPLv3
25+
*
26+
*/
27+
class MozziGenerator : public SoundGenerator<int16_t> {
28+
public:
29+
MozziGenerator(int control_rate){
30+
info.sample_rate = AUDIO_RATE;
31+
info.bits_per_sample = 16;
32+
info.channels = 2;
33+
control_counter_max = info.sample_rate / control_rate;
34+
if (control_counter_max <= 0){
35+
control_counter_max = 1;
36+
}
37+
control_counter = control_counter_max;
38+
}
39+
40+
/// Provides some key audio information
41+
AudioBaseInfo config() {
42+
return info;
43+
}
44+
45+
/// Provides a single sample
46+
virtual int16_t readSample() {
47+
if (--control_counter<0){
48+
control_counter = control_counter_max;
49+
::updateControl();
50+
}
51+
int result = (int) ::updateAudio();
52+
total_tick_count++;
53+
return result;
54+
}
55+
56+
protected:
57+
AudioBaseInfo info;
58+
int control_counter_max;
59+
int control_counter;
60+
61+
};
62+
63+
64+
} // Namespace

0 commit comments

Comments
 (0)