Skip to content

Commit e7de433

Browse files
committed
extended Mozzi support
1 parent 35c6d4d commit e7de433

File tree

8 files changed

+298
-20
lines changed

8 files changed

+298
-20
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ This is currently work in progress:
185185
|------------------------|---------|
186186
| Analog input - ADC | tested |
187187
| I2S | tested |
188+
| PWM Output | tested |
188189
| Files (RAW, MP3...) | tested |
189190
| Streams | tested |
190191
| WAV encoding/deconding | tested |

src/AudioMozzi.h

Lines changed: 212 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@
55
#include "mozzi_analog.h"
66
#include "Mozzi.h"
77

8-
98
namespace audio_tools {
109

10+
/**
11+
* @brief Mozzi Configuration for input or output stream
12+
*
13+
*/
14+
struct MozziConfig : AudioBaseInfo {
15+
uint16_t control_rate=0;
16+
void (*updateControl)() = nullptr; //&::updateControl;
17+
AudioOutput_t (*updateAudio)() = nullptr; // = &::updateAudio;
1118

19+
MozziConfig(){
20+
channels = AUDIO_CHANNELS;
21+
sample_rate = AUDIO_RATE;
22+
bits_per_sample = sizeof(AudioOutputStorage_t)*8;
23+
}
24+
};
1225
/**
1326
* @brief Support for https://sensorium.github.io/Mozzi/
1427
* Define your updateControl() method.
@@ -18,42 +31,226 @@ namespace audio_tools {
1831
1932
* @author Phil Schatzmann
2033
* @copyright GPLv3
21-
*
2234
*/
23-
class MozziGenerator : public SoundGenerator<int16_t> {
35+
36+
class MozziGenerator : public SoundGenerator<AudioOutputStorage_t> {
2437
public:
25-
MozziGenerator(int control_rate){
26-
info.sample_rate = AUDIO_RATE;
27-
info.bits_per_sample = 16;
28-
info.channels = 2;
29-
control_counter_max = info.sample_rate / control_rate;
38+
MozziGenerator(){
39+
LOGD(__FUNCTION__);
40+
}
41+
42+
MozziGenerator(MozziConfig config){
43+
begin(config);
44+
}
45+
46+
virtual ~MozziGenerator() {
47+
end();
48+
}
49+
50+
void begin(MozziConfig config){
51+
info = config;
52+
if (info.control_rate==0){
53+
info.control_rate = CONTROL_RATE;
54+
}
55+
control_counter_max = info.sample_rate / info.control_rate;
3056
if (control_counter_max <= 0){
3157
control_counter_max = 1;
3258
}
3359
control_counter = control_counter_max;
3460
}
3561

3662
/// Provides some key audio information
37-
AudioBaseInfo config() {
63+
MozziConfig config() {
3864
return info;
3965
}
4066

4167
/// Provides a single sample
42-
virtual int16_t readSample() {
68+
virtual AudioOutputStorage_t readSample() {
69+
if (is_read_buffer_filled){
70+
// for stereo output we might have the value already
71+
is_read_buffer_filled = false;
72+
return read_buffer;
73+
}
74+
4375
if (--control_counter<0){
4476
control_counter = control_counter_max;
45-
::updateControl();
77+
info.updateControl();
4678
}
47-
int result = (int) ::updateAudio();
48-
samples_written_to_buffer++;
79+
80+
AudioOutput out = info.updateAudio();
81+
// requested mono
82+
AudioOutputStorage_t result = 0;
83+
#if (AUDIO_CHANNELS == MONO)
84+
// generated stereo
85+
if (sizeof(out) == sizeof(AudioOutputStorage_t)*2){
86+
result = out[0]/2 + out[1]/2;
87+
// generated mono
88+
} else if (sizeof(out) == sizeof(AudioOutputStorage_t)){
89+
result = out[0];
90+
}
91+
92+
// requested stereo
93+
#elif (AUDIO_CHANNELS == STEREO)
94+
result = out[0];
95+
// generated stereo
96+
if (sizeof(out) == sizeof(AudioOutputStorage_t)*2){
97+
read_buffer = out[1];
98+
is_read_buffer_filled = true;
99+
// generated mono
100+
} else if (sizeof(out) == sizeof(AudioOutputStorage_t)){
101+
read_buffer = out[0];
102+
is_read_buffer_filled = true;
103+
}
104+
#endif
49105
return result;
50106
}
51107

52108
protected:
53-
AudioBaseInfo info;
109+
MozziConfig info;
54110
int control_counter_max;
55111
int control_counter;
56-
uint64_t samples_written_to_buffer;
112+
int read_buffer;
113+
bool is_read_buffer_filled = false;
114+
115+
};
116+
117+
118+
119+
/**
120+
* @brief We use the output functionality of Mozzi to output audio data. We expect the data as array of int16_t with
121+
* one or two channels. Though we support the setting of a sample rate, we recommend to use the default sample rate
122+
* from Mozzi which is available with the AUDIO_RATE define.
123+
*/
124+
class MozziStream : public Stream {
125+
126+
public:
127+
MozziStream(){
128+
LOGD(__FUNCTION__);
129+
}
130+
131+
~MozziStream(){
132+
LOGD(__FUNCTION__);
133+
end();
134+
if (input_ptr!=nullptr){
135+
delete input_ptr;
136+
input_ptr = nullptr;
137+
}
138+
}
139+
140+
MozziConfig defaultConfig() {
141+
MozziConfig default_config;
142+
return default_config;
143+
}
144+
145+
/// Starts Mozzi with its default parameters
146+
void begin(){
147+
begin(defaultConfig());
148+
}
149+
150+
// Start Mozzi - if controlRate > 0 we actiavate the sound generation (=>allow reads); the parameters describe the values if the
151+
// provided input stream or resulting output stream;
152+
void begin(MozziConfig cfg){
153+
LOGD(__FUNCTION__);
154+
config = cfg;
155+
Mozzi.setAudioRate(config.sample_rate);
156+
// in output mode we do not allocate any unnecessary functionality
157+
if (input_ptr == nullptr && config.control_rate>0){
158+
input_ptr = new MozziGenerator(config);
159+
}
160+
Mozzi.start(0);
161+
}
162+
163+
void end(){
164+
LOGD(__FUNCTION__);
165+
Mozzi.stop();
166+
}
167+
168+
/// unsupported operations
169+
virtual int availableForWrite() {
170+
return Mozzi.canWrite() ? sizeof(int) : 0;
171+
}
172+
173+
/// write an individual byte - if the frame is complete we pass it to Mozzi
174+
virtual size_t write(uint8_t c) {
175+
if (Mozzi.canWrite()){
176+
writeChar(c);
177+
return 1;
178+
} else {
179+
return 0;
180+
}
181+
}
182+
183+
virtual size_t write(const uint8_t *buffer, size_t size) {
184+
for (size_t j=0;j<size;j++){
185+
if (write(buffer[j])<=0){
186+
return j;
187+
}
188+
}
189+
return size;
190+
}
191+
192+
virtual int available(){
193+
return 100000; // just a random big number
194+
}
195+
196+
virtual size_t readBytes(char *buffer, size_t length){
197+
return input_ptr==nullptr ? 0 : input_ptr->readBytes((uint8_t*)buffer, length);
198+
}
199+
200+
virtual int read(){
201+
LOGE("read() not supported - use readBytes!");
202+
return -1;
203+
}
204+
205+
virtual int peek(){
206+
LOGE("peek() not supported!");
207+
return -1;
208+
}
209+
210+
virtual void flush(){
211+
};
212+
213+
protected:
214+
MozziConfig config;
215+
MozziGenerator *input_ptr = nullptr;
216+
NumberReader numberReader;
217+
int32_t frame[2];
218+
uint8_t buffer[64];
219+
int buffer_pos;
220+
221+
// writes individual bytes and puts them together as MonoOutput or StereoOutput
222+
void writeChar(uint8_t c){
223+
buffer[buffer_pos++] = c;
224+
225+
#if (AUDIO_CHANNELS == MONO)
226+
#warning "Mozzi is configured for Mono Output (to one channel only)"
227+
if (config.channels == 1 && buffer_pos==config.bits_per_sample/8){
228+
numberReader.toNumbers(buffer, config.bits_per_sample,sizeof(AudioOutputStorage_t) * 8, true, 1, frame );
229+
MonoOutput out = MonoOutput(frame[0]);
230+
Mozzi.write(out);
231+
buffer_pos = 0;
232+
} else if (config.channels == 2 && buffer_pos==config.bits_per_sample/8*2){
233+
numberReader.toNumbers(buffer, config.bits_per_sample,sizeof(AudioOutputStorage_t) * 8, true, 2, frame );
234+
MonoOutput out = MonoOutput(frame[0]/2 + frame[1]/2);
235+
Mozzi.write(out);
236+
buffer_pos = 0;
237+
}
238+
239+
#elif (AUDIO_CHANNELS == STEREO)
240+
if (config.channels == 1 && buffer_pos==config.bits_per_sample/8){
241+
numberReader.toNumbers(buffer, config.bits_per_sample,sizeof(AudioOutputStorage_t) * 8, true, 1, frame );
242+
StereoOutput out = StereoOutput(frame[0],frame[0]);
243+
Mozzi.write(out);
244+
buffer_pos = 0;
245+
} else if (config.channels == 2 && buffer_pos==config.bits_per_sample/8*2){
246+
numberReader.toNumbers(buffer, config.bits_per_sample,sizeof(AudioOutputStorage_t) * 8, true, 2, frame );
247+
StereoOutput out = StereoOutput(frame[0],frame[1]);
248+
Mozzi.write(out);
249+
buffer_pos = 0;
250+
}
251+
252+
#endif
253+
}
57254

58255
};
59256

src/AudioPWM/PWMAudioStreamBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ class PWMAudioStreamBase : public Stream {
173173
LOGI("->Buffer available: %d", buffer->available());
174174
LOGI("->Buffer available for write: %d", buffer->availableToWrite());
175175
LOGI("->is_timer_started: %s ", is_timer_started ? "true" : "false");
176+
return true;
176177
}
177178

178179
virtual void end(){

src/AudioTools/AudioCopy.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ class StreamCopyT {
130130
break;
131131
}
132132

133-
if (retry>1) LOGI("try write - %d ",retry);
133+
if (retry>1) {
134+
LOGI("try write - %d ",retry);
135+
}
134136

135137
}
136138
return total;

src/AudioTools/Converter.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,5 +308,76 @@ class CallbackConverter {
308308
};
309309

310310

311+
/**
312+
* @brief Reads n numbers from an Arduino Stream
313+
*
314+
*/
315+
class NumberReader {
316+
public:
317+
NumberReader(Stream &in) {
318+
stream_ptr = &in;
319+
}
320+
321+
NumberReader() {
322+
}
323+
324+
bool read(int inBits, int outBits, bool outSigned, int n, int32_t *result){
325+
bool result_bool = false;
326+
int len = inBits/8 * n;
327+
if (stream_ptr!=nullptr && stream_ptr->available()>len){
328+
uint8_t buffer[len];
329+
stream_ptr->readBytes((uint8_t*)buffer, n * len);
330+
result_bool = toNumbers((void*)buffer, inBits, outBits, outSigned, n, result);
331+
}
332+
return result_bool;
333+
}
334+
335+
/// converts a buffer to a number array
336+
bool toNumbers(void *bufferIn, int inBits, int outBits, bool outSigned, int n, int32_t *result){
337+
bool result_bool = false;
338+
switch(inBits){
339+
case 8: {
340+
int8_t *buffer=(int8_t *)bufferIn;
341+
for (int j=0;j<n;j++){
342+
result[j] = scale(buffer[j],inBits,outBits,outSigned);
343+
}
344+
result_bool = true;
345+
}
346+
break;
347+
case 16: {
348+
int16_t *buffer=(int16_t *)bufferIn;
349+
for (int j=0;j<n;j++){
350+
result[j] = scale(buffer[j],inBits,outBits,outSigned);
351+
}
352+
result_bool = true;
353+
}
354+
break;
355+
case 32: {
356+
int32_t *buffer=(int32_t*)bufferIn;
357+
for (int j=0;j<n;j++){
358+
result[j] = scale(buffer[j],inBits,outBits,outSigned);
359+
}
360+
result_bool = true;
361+
}
362+
break;
363+
}
364+
return result_bool;
365+
366+
}
367+
368+
protected:
369+
Stream *stream_ptr=nullptr;
370+
371+
/// scale the value
372+
int32_t scale(int32_t value, int inBits, int outBits, bool outSigned=true){
373+
int32_t result = static_cast<float>(value) / maxValue(inBits) * maxValue(outBits);
374+
if (!outSigned){
375+
result += (maxValue(outBits) / 2);
376+
}
377+
return result;
378+
}
379+
380+
};
381+
311382

312383
}

src/AudioTools/I2S_ESP8266.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#ifdef ESP8266
44
#include "AudioTools/AudioLogger.h"
5-
#include "i2s.h"
5+
#include <I2S.h>
66

77
namespace audio_tools {
88

src/AudioTools/SoundGenerator.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@ namespace audio_tools {
1414
template <class T>
1515
class SoundGenerator {
1616
public:
17+
SoundGenerator() {
18+
}
19+
20+
virtual ~SoundGenerator() {
21+
end();
22+
}
23+
1724
/// Provides the samples into simple array - which represents 1 channel
1825
virtual size_t readSamples(T* data, size_t sampleCount=512){
19-
for (int j=0;j<sampleCount;j++){
26+
for (size_t j=0;j<sampleCount;j++){
2027
data[j] = readSample();
2128
}
2229
return sampleCount;

0 commit comments

Comments
 (0)