arduino-audio-tools
Loading...
Searching...
No Matches
RTTTLOutput.h
Go to the documentation of this file.
1#pragma once
2
6
7namespace audio_tools {
8
95template <class T = int16_t>
96class RTTTLOutput : public AudioOutput {
97 public:
98 RTTTLOutput() = default;
99 RTTTLOutput(SoundGenerator<T>& generator) { setGenerator(generator); }
101 : RTTTLOutput(generator) {
102 setOutput(out);
103 }
105 : RTTTLOutput(generator) {
106 setOutput(out);
107 }
109 : RTTTLOutput(generator) {
110 setOutput(out);
111 }
112
113 // Expose base-class begin overloads (e.g. begin(AudioInfo)) to avoid
114 // hiding them with the no-arg begin() override below.
115 using AudioOutput::begin;
116
118 void setOutput(Print& out) { p_print = &out; }
119
122 void setGenerator(SoundGenerator<T>& generator) { p_generator = &generator; }
123
127 setOutput((Print&)out);
129 }
130
134 setOutput((Print&)out);
136 }
137
144 bool begin() override {
145 LOGD("RTTTLOutput::begin");
146 if (p_generator) {
148 }
149 is_start = true;
150 return true;
151 }
152
155 const char* getTitle() { return m_title.c_str(); }
156
159 int getDefaultOctave() const { return m_octave; }
160
163 int getDefaultDuration() const { return m_duration; }
164
167 int getDefaultBpm() const { return m_bpm; }
168
170 size_t write(const uint8_t* data, size_t len) override {
171 LOGD("write: %d", len);
172 ring_buffer.resize(len);
174 ring_buffer.writeArray(const_cast<uint8_t*>(data), len);
175 // If we haven't started yet and we find a ':', we need to call begin()
176 if (!is_start && find_byte(data, len, ':') >= 0) {
177 LOGI("found ':' - resetting parser state by calling begin()");
178 begin();
179 }
180 // start parsing of new rtttl string
181 if (is_start) {
182 // parse rtttl string
183 parse_title();
185 is_start = false;
186 }
187 parse_notes();
188 return len;
189 }
190
200 void (*cb)(float freqHz, int durationMs, int midiNote, void* ref)) {
201 noteCallback = cb;
202 }
203
205 void setReference(void* ref) { reference = ref; }
206
209 void setTransposeOctaves(int8_t octaves) { m_tranpose_octaves = octaves; }
210
212 void setRamp(uint8_t upPercent = 20, uint8_t downPercent = 30) {
213 m_ramp_upPercent = upPercent;
214 m_ramp_downPercent = downPercent;
215 }
216
217 protected:
221 Print* p_print = nullptr;
223 bool is_start = true;
224 char m_actual = 0;
225 char m_prec = 0;
227 int m_octave = 4;
228 int m_duration = 4;
229 int m_bpm = 120;
230 float m_msec_semi = 750;
231 void* reference = nullptr;
232 uint8_t m_ramp_upPercent = 20;
233 uint8_t m_ramp_downPercent = 30;
234 void (*noteCallback)(float, int, int, void*) = nullptr;
235
236 int find_byte(const uint8_t* haystack, size_t haystack_len, uint8_t needle) {
237 for (size_t i = 0; i < haystack_len; i++) {
238 if (haystack[i] == needle) {
239 return i; // Return the index of the first match
240 }
241 }
242 return -1; // Return -1 if the byte is not found
243 }
244
245 void play_note(float freq, int msec, int midi = -1) {
246 // invoke the optional callback first
247 LOGI("play_note: freq=%.2f Hz, msec=%d, midi=%d", freq, msec, midi);
248 if (noteCallback) noteCallback(freq, msec, midi, reference);
249 if (p_print == nullptr || p_generator == nullptr) return;
250
251 AudioInfo info = audioInfo();
252 if (!info) return;
253
257 uint8_t buffer[1024];
258 size_t len = p_generator->readBytes(buffer, 1024);
259 while (len > 0) {
260 p_print->write(buffer, len);
261 len = p_generator->readBytes(buffer, 1024);
262 delay(1);
263 }
264 }
265
266 char next_char(bool convertToLower = true) {
267 uint8_t c;
268 if (!ring_buffer.read(c)) {
269 c = 0;
270 }
272 m_actual = convertToLower ? tolower(c) : c;
273 return m_actual;
274 }
275
276 void parse_title() {
277 next_char(false);
278 m_title = "";
279 for (; m_actual != ':' && m_actual != '\0'; next_char(false)) {
280 m_title += m_actual;
281 }
282 if (!m_title.isEmpty()) LOGI("title: %s", m_title.c_str());
283 }
284
285 int parse_num() {
286 int val = 0;
287 do {
288 val *= 10;
289 val += m_actual - '0';
290 } while (isdigit(next_char()));
291
292 return val;
293 }
294
296 char id{' '};
297
298 while (' ' == next_char());
299 do {
300 if (isdigit(m_actual)) {
301 switch (id) {
302 case 'o':
304 LOGI("default octave: %d", m_octave);
305 break;
306 case 'd':
308 LOGI("default duration: %d", m_duration);
309 break;
310 case 'b':
311 m_bpm = parse_num();
312 LOGI("default bpm: %d", m_bpm);
313 break;
314 }
315 continue;
316 } else if (isalpha(m_actual)) {
317 id = m_actual;
318 }
319 next_char();
320 } while (':' != m_actual);
321
322 if (m_bpm != 0)
323 m_msec_semi = 240000.0 / m_bpm;
324 else
325 m_msec_semi = 750.0;
326
327 LOGI("msec per semi: %.2f", m_msec_semi);
328 }
329
330 float transpose(float frequency, int8_t octaves) {
331 if (octaves == 0) return frequency;
332 return frequency * powf(2.0f, octaves);
333 }
334
335 void parse_notes() {
336 // Ensure we start reading after the defaults section
337 if (m_actual == ':') next_char();
338 // If m_actual is 0 but there is data in the buffer, advance to next char
339 if (m_actual == 0 && ring_buffer.available() > 0) next_char();
340
341 bool last_note_is_valid = false;
342 float last_mult_duration = 1.0;
343 int last_octave = 0;
344 int last_duration = 0;
346
347 while (m_actual != 0) {
348 // skip separators
349 while (m_actual == ' ' || m_actual == ',') {
350 if (next_char() == 0) return;
351 }
352
353 if (m_actual == 0) break;
354
355 // optional duration (number)
356 int duration = m_duration;
357 if (isdigit(m_actual)) {
358 duration = parse_num();
359 if (m_actual == 0) break;
360 }
361 last_duration = duration;
362
363 // note letter or rest
364 char name = m_actual;
365 if (name == 'p' || name == 'r') { // pause/rest
366 next_char();
367 float mult = 1.0;
368 if (m_actual == '.') {
369 mult = 1.5;
370 next_char();
371 }
372 int msec = (int)((m_msec_semi / duration) * mult);
373 play_note(0.0f, msec, -1);
374 last_note_is_valid = false;
375 continue;
376 }
377
378 // map letter to MusicalNotes enum
380 switch (name) {
381 case 'c':
382 noteEnum = MusicalNotes::C;
383 break;
384 case 'd':
385 noteEnum = MusicalNotes::D;
386 break;
387 case 'e':
388 noteEnum = MusicalNotes::E;
389 break;
390 case 'f':
391 noteEnum = MusicalNotes::F;
392 break;
393 case 'g':
394 noteEnum = MusicalNotes::G;
395 break;
396 case 'a':
397 noteEnum = MusicalNotes::A;
398 break;
399 case 'b':
400 case 'h':
401 noteEnum = MusicalNotes::B;
402 break;
403 default:
404 next_char();
405 last_note_is_valid = false;
406 continue;
407 }
408 last_noteEnum = noteEnum;
409
410 // advance to next char to inspect
411 // accidental/octave/dot
412 if (next_char() == 0) {
413 last_note_is_valid = true;
414 last_octave = m_octave;
415 last_mult_duration = 1.0;
416 break;
417 }
418
419 // accidental
420 int semitone = (int)noteEnum;
421 if (m_actual == '#') {
422 semitone++;
423 if (semitone > 11) {
424 semitone = 0;
425 m_octave += 1;
426 }
427 noteEnum = (MusicalNotes::MusicalNotesEnum)semitone;
428 if (next_char() == 0) {
429 last_note_is_valid = true;
430 last_octave = m_octave;
431 last_mult_duration = 1.0;
432 break;
433 }
434 }
435
436 // octave
437 int octave = m_octave;
438 if (m_actual >= '0' && m_actual <= '9') {
439 octave = m_actual - '0';
440 if (next_char() == 0) {
441 last_note_is_valid = true;
442 last_octave = octave;
443 last_mult_duration = 1.0;
444 break;
445 }
446 }
447
448 // dotted duration
449 float mult_duration = 1.0;
450 if (m_actual == '.') {
451 mult_duration = 1.5;
452 if (next_char() == 0) {
453 last_note_is_valid = true;
454 last_octave = octave;
455 last_mult_duration = mult_duration;
456 break;
457 }
458 }
459
460 float freq = m_notes.frequency(noteEnum, (uint8_t)octave);
461 freq = transpose(freq, m_tranpose_octaves);
462 int msec = (int)((m_msec_semi / duration) * mult_duration);
463 int midi = m_notes.frequencyToMidiNote(freq);
464 play_note(freq, msec, midi);
465 last_note_is_valid = false;
466 }
467
468 // If a note is pending at the end, play it
469 if (last_note_is_valid) {
470 float freq = m_notes.frequency(last_noteEnum, (uint8_t)last_octave);
471 freq = transpose(freq, m_tranpose_octaves);
472 int msec = (int)((m_msec_semi / last_duration) * last_mult_duration);
473 int midi = m_notes.frequencyToMidiNote(freq);
474 play_note(freq, msec, midi);
475 }
476 }
477};
478
479} // namespace audio_tools
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
Definition Arduino.h:56
virtual size_t write(const uint8_t *data, size_t len)
Definition Arduino.h:120
virtual void addNotifyAudioChange(AudioInfoSupport &bi)
Adds target to be notified about audio changes.
Definition AudioTypes.h:155
Abstract Audio Ouptut class.
Definition AudioOutput.h:25
virtual bool begin()
Definition AudioOutput.h:78
virtual AudioInfo audioInfo() override
provides the actual input AudioInfo
Definition AudioOutput.h:62
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:120
Determination of the frequency of a music note.
Definition MusicalNotes.h:125
float frequency(MusicalNotesEnum note, uint8_t octave) const
Determines the frequency of the indicate note and octave (0-8)
Definition MusicalNotes.h:131
int frequencyToMidiNote(float freq) const
Provide MIDI note for frequency.
Definition MusicalNotes.h:207
RTTTL (Ring Tone Text Transfer Language) parser and player stream.
Definition RTTTLOutput.h:96
bool is_start
Definition RTTTLOutput.h:223
void parse_defaults()
Definition RTTTLOutput.h:295
RTTTLOutput(SoundGenerator< T > &generator)
Definition RTTTLOutput.h:99
void parse_title()
Definition RTTTLOutput.h:276
int m_octave
Definition RTTTLOutput.h:227
int parse_num()
Definition RTTTLOutput.h:285
RTTTLOutput(SoundGenerator< T > &generator, AudioStream &out)
Definition RTTTLOutput.h:100
int getDefaultOctave() const
Definition RTTTLOutput.h:159
float m_msec_semi
Definition RTTTLOutput.h:230
void play_note(float freq, int msec, int midi=-1)
Definition RTTTLOutput.h:245
char next_char(bool convertToLower=true)
Definition RTTTLOutput.h:266
void setOutput(AudioOutput &out)
Definition RTTTLOutput.h:133
void(* noteCallback)(float, int, int, void *)
Definition RTTTLOutput.h:234
void setTransposeOctaves(int8_t octaves)
Definition RTTTLOutput.h:209
char m_actual
Definition RTTTLOutput.h:224
int8_t m_tranpose_octaves
Definition RTTTLOutput.h:222
size_t write(const uint8_t *data, size_t len) override
Writes RTTTL data to the parser and plays the notes.
Definition RTTTLOutput.h:170
RTTTLOutput(SoundGenerator< T > &generator, Print &out)
Definition RTTTLOutput.h:108
const char * getTitle()
Definition RTTTLOutput.h:155
int find_byte(const uint8_t *haystack, size_t haystack_len, uint8_t needle)
Definition RTTTLOutput.h:236
void setReference(void *ref)
Provide reference for callback.
Definition RTTTLOutput.h:205
int getDefaultDuration() const
Definition RTTTLOutput.h:163
void parse_notes()
Definition RTTTLOutput.h:335
uint8_t m_ramp_upPercent
Definition RTTTLOutput.h:232
int getDefaultBpm() const
Definition RTTTLOutput.h:167
RTTTLOutput(SoundGenerator< T > &generator, AudioOutput &out)
Definition RTTTLOutput.h:104
void setNoteCallback(void(*cb)(float freqHz, int durationMs, int midiNote, void *ref))
Definition RTTTLOutput.h:199
char m_prec
Definition RTTTLOutput.h:225
bool begin() override
Start the RTTTL stream: we start with parsing the title and defaults.
Definition RTTTLOutput.h:144
Print * p_print
Definition RTTTLOutput.h:221
float transpose(float frequency, int8_t octaves)
Definition RTTTLOutput.h:330
void setGenerator(SoundGenerator< T > &generator)
Definition RTTTLOutput.h:122
void setRamp(uint8_t upPercent=20, uint8_t downPercent=30)
Defines the ramp up and down percentages for note playback (default: 20% up, 30% down)
Definition RTTTLOutput.h:212
void setOutput(Print &out)
Defines/Changes the output target (Print)
Definition RTTTLOutput.h:118
uint8_t m_ramp_downPercent
Definition RTTTLOutput.h:233
void * reference
Definition RTTTLOutput.h:231
void setOutput(AudioStream &out)
Definition RTTTLOutput.h:126
int m_bpm
Definition RTTTLOutput.h:229
MusicalNotes m_notes
Definition RTTTLOutput.h:218
int m_duration
Definition RTTTLOutput.h:228
RingBuffer< uint8_t > ring_buffer
Definition RTTTLOutput.h:220
SoundGenerator< T > * p_generator
Definition RTTTLOutput.h:219
Str m_title
Definition RTTTLOutput.h:226
Implements a typed Ringbuffer.
Definition Buffers.h:363
virtual int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:432
bool read(T &result) override
reads a single value
Definition Buffers.h:370
virtual void reset() override
clears the buffer
Definition Buffers.h:468
virtual bool resize(size_t len)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:483
virtual int available() override
provides the number of entries that are available to read
Definition Buffers.h:475
Base class to define the abstract interface for the sound generating classes.
Definition SoundGenerator.h:30
virtual void restart()
Restarts the generator, e.g. after a play time has been defined and reached.
Definition SoundGenerator.h:167
virtual size_t readBytes(uint8_t *data, size_t len)
Provides the data as byte array with the requested number of channels.
Definition SoundGenerator.h:83
virtual bool begin(AudioInfo info)
Starts the processing with the provided AudioInfo.
Definition SoundGenerator.h:37
virtual void setFrequency(float frequency)
Abstract method: not implemented! Just provides an error message...
Definition SoundGenerator.h:115
virtual void setPlayTime(uint32_t playMs, uint8_t upPercent=20, uint8_t downPercent=30)
Defines the play time in ms and the ramp up and ramp down time in percent.
Definition SoundGenerator.h:132
Str which keeps the data on the heap. We grow the allocated memory only if the copy source is not fit...
Definition Str.h:24
virtual bool isEmpty()
checks if the string is empty
Definition StrView.h:463
virtual const char * c_str()
provides the string value as const char*
Definition StrView.h:456
MusicalNotesEnum
Notes.
Definition MusicalNotes.h:128
@ G
Definition MusicalNotes.h:128
@ B
Definition MusicalNotes.h:128
@ A
Definition MusicalNotes.h:128
@ C
Definition MusicalNotes.h:128
@ D
Definition MusicalNotes.h:128
@ E
Definition MusicalNotes.h:128
@ F
Definition MusicalNotes.h:128
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
void delay(uint32_t ms)
Definition Arduino.h:259
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:56