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