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
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;
234 void (*noteCallback)(float, int, int, void*) = nullptr;
235
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);
249 if (p_print == nullptr || p_generator == nullptr) return;
250
251 AudioInfo info = audioInfo();
252 if (!info) return;
253
256 uint8_t buffer[1024];
257 size_t len = p_generator->readBytes(buffer, 1024);
258 while (len > 0) {
259 p_print->write(buffer, len);
260 len = p_generator->readBytes(buffer, 1024);
261 delay(1);
262 }
263 }
264
265 char next_char(bool convertToLower = true) {
266 uint8_t c;
267 if (!ring_buffer.read(c)) {
268 c = 0;
269 }
272 return m_actual;
273 }
274
275 void parse_title() {
276 next_char(false);
277 m_title = "";
278 for (; m_actual != ':' && m_actual != '\0'; next_char(false)) {
279 m_title += m_actual;
280 }
281 if (!m_title.isEmpty()) LOGI("title: %s", m_title.c_str());
282 }
283
284 int parse_num() {
285 int val = 0;
286 do {
287 val *= 10;
288 val += m_actual - '0';
289 } while (isdigit(next_char()));
290
291 return val;
292 }
293
295 char id{' '};
296
297 while (' ' == next_char());
298 do {
299 if (isdigit(m_actual)) {
300 switch (id) {
301 case 'o':
303 LOGI("default octave: %d", m_octave);
304 break;
305 case 'd':
307 LOGI("default duration: %d", m_duration);
308 break;
309 case 'b':
310 m_bpm = parse_num();
311 LOGI("default bpm: %d", m_bpm);
312 break;
313 }
314 continue;
315 } else if (isalpha(m_actual)) {
316 id = m_actual;
317 }
318 next_char();
319 } while (':' != m_actual);
320
321 if (m_bpm != 0)
322 m_msec_semi = 240000.0 / m_bpm;
323 else
324 m_msec_semi = 750.0;
325
326 LOGI("msec per semi: %.2f", m_msec_semi);
327 }
328
329 float transpose(float frequency, int8_t octaves) {
330 if (octaves == 0) return frequency;
331 return frequency * powf(2.0f, octaves);
332 }
333
334 void parse_notes() {
335 // Ensure we start reading after the defaults section
336 if (m_actual == ':') next_char();
337 // If m_actual is 0 but there is data in the buffer, advance to next char
338 if (m_actual == 0 && ring_buffer.available() > 0) next_char();
339
340 bool last_note_is_valid = false;
341 float last_mult_duration = 1.0;
342 int last_octave = 0;
343 int last_duration = 0;
345
346 while (m_actual != 0) {
347 // skip separators
348 while (m_actual == ' ' || m_actual == ',') {
349 if (next_char() == 0) return;
350 }
351
352 if (m_actual == 0) break;
353
354 // optional duration (number)
355 int duration = m_duration;
356 if (isdigit(m_actual)) {
357 duration = parse_num();
358 if (m_actual == 0) break;
359 }
360 last_duration = duration;
361
362 // note letter or rest
363 char name = m_actual;
364 if (name == 'p' || name == 'r') { // pause/rest
365 next_char();
366 float mult = 1.0;
367 if (m_actual == '.') {
368 mult = 1.5;
369 next_char();
370 }
371 int msec = (int)((m_msec_semi / duration) * mult);
372 play_note(0.0f, msec, -1);
373 last_note_is_valid = false;
374 continue;
375 }
376
377 // map letter to MusicalNotes enum
379 switch (name) {
380 case 'c':
382 break;
383 case 'd':
385 break;
386 case 'e':
388 break;
389 case 'f':
391 break;
392 case 'g':
394 break;
395 case 'a':
397 break;
398 case 'b':
399 case 'h':
401 break;
402 default:
403 next_char();
404 last_note_is_valid = false;
405 continue;
406 }
408
409 // advance to next char to inspect
410 // accidental/octave/dot
411 if (next_char() == 0) {
412 last_note_is_valid = true;
414 last_mult_duration = 1.0;
415 break;
416 }
417
418 // accidental
419 int semitone = (int)noteEnum;
420 if (m_actual == '#') {
421 semitone++;
422 if (semitone > 11) {
423 semitone = 0;
424 m_octave += 1;
425 }
427 if (next_char() == 0) {
428 last_note_is_valid = true;
430 last_mult_duration = 1.0;
431 break;
432 }
433 }
434
435 // octave
436 int octave = m_octave;
437 if (m_actual >= '0' && m_actual <= '9') {
438 octave = m_actual - '0';
439 if (next_char() == 0) {
440 last_note_is_valid = true;
442 last_mult_duration = 1.0;
443 break;
444 }
445 }
446
447 // dotted duration
448 float mult_duration = 1.0;
449 if (m_actual == '.') {
450 mult_duration = 1.5;
451 if (next_char() == 0) {
452 last_note_is_valid = true;
455 break;
456 }
457 }
458
459 float freq = m_notes.frequency(noteEnum, (uint8_t)octave);
460 freq = transpose(freq, m_tranpose_octaves);
461 int msec = (int)((m_msec_semi / duration) * mult_duration);
462 int midi = m_notes.frequencyToMidiNote(freq);
463 play_note(freq, msec, midi);
464 last_note_is_valid = false;
465 }
466
467 // If a note is pending at the end, play it
468 if (last_note_is_valid) {
470 freq = transpose(freq, m_tranpose_octaves);
472 int midi = m_notes.frequencyToMidiNote(freq);
473 play_note(freq, msec, midi);
474 }
475 }
476};
477
478} // 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:149
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
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:56
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:294
RTTTLOutput(SoundGenerator< T > &generator)
Definition RTTTLOutput.h:99
void parse_title()
Definition RTTTLOutput.h:275
int m_octave
Definition RTTTLOutput.h:227
int parse_num()
Definition RTTTLOutput.h:284
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:265
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:334
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:329
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:353
bool read(T &result) override
reads a single value
Definition Buffers.h:360
virtual void reset() override
clears the buffer
Definition Buffers.h:415
virtual bool resize(size_t len)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:430
virtual int available() override
provides the number of entries that are available to read
Definition Buffers.h:422
Base class to define the abstract interface for the sound generating classes.
Definition SoundGenerator.h:28
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:100
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:63
virtual bool begin(AudioInfo info)
Starts the processing with the provided AudioInfo.
Definition SoundGenerator.h:35
virtual void setFrequency(float frequency)
Abstract method: not implemented! Just provides an error message...
Definition SoundGenerator.h:83
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:386
virtual const char * c_str()
provides the string value as const char*
Definition StrView.h:379
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 AudioCodecsBase.h:10
void delay(uint32_t ms)
Definition Arduino.h:255
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:508
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:51