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.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();
184 parse_defaults();
185 is_start = false;
186 }
187 parse_notes();
188 return len;
189 }
190
200 std::function<void(float freqHz, int durationMs, int midiNote, void* ref)>
201 cb) {
202 noteCallback = cb;
203 }
204
206 void setReference(void* ref) { reference = ref; }
207
210 void setTransposeOctaves(int8_t octaves) { m_tranpose_octaves = octaves; }
211
212 protected:
213 MusicalNotes m_notes;
214 SoundGenerator<T>* p_generator = nullptr;
215 RingBuffer<uint8_t> ring_buffer{0};
216 Print* p_print = nullptr;
217 int8_t m_tranpose_octaves = 0;
218 bool is_start = true;
219 char m_actual = 0;
220 char m_prec = 0;
221 Str m_title{40};
222 int m_octave = 4;
223 int m_duration = 4;
224 int m_bpm = 120;
225 float m_msec_semi = 750;
226 void* reference = nullptr;
227 std::function<void(float, int, int, void*)> noteCallback;
228
229 int find_byte(const uint8_t* haystack, size_t haystack_len, uint8_t needle) {
230 for (size_t i = 0; i < haystack_len; i++) {
231 if (haystack[i] == needle) {
232 return i; // Return the index of the first match
233 }
234 }
235 return -1; // Return -1 if the byte is not found
236 }
237
238 void play_note(float freq, int msec, int midi = -1) {
239 // invoke the optional callback first
240 LOGI("play_note: freq=%.2f Hz, msec=%d, midi=%d", freq, msec, midi);
241 if (noteCallback) noteCallback(freq, msec, midi, reference);
242 if (p_print == nullptr || p_generator == nullptr) return;
243 p_generator->setFrequency(freq);
244 AudioInfo info = audioInfo();
245 if (!info) return;
246 int frames = (int)((uint64_t)info.sample_rate * (uint64_t)msec / 1000);
247 int frame_size = (info.channels * info.bits_per_sample) / 8;
248 int open = frames * frame_size;
249 uint8_t buffer[1024];
250 while (open > 0) {
251 int toCopy = std::min(open, (int)sizeof(buffer));
252 p_generator->readBytes(buffer, toCopy);
253 p_print->write(buffer, toCopy);
254 open -= toCopy;
255 delay(1);
256 }
257 }
258
259 char next_char(bool convertToLower = true) {
260 uint8_t c;
261 if (!ring_buffer.read(c)) {
262 c = 0;
263 }
264 m_prec = m_actual;
265 m_actual = convertToLower ? tolower(c) : c;
266 return m_actual;
267 }
268
269 void parse_title() {
270 next_char(false);
271 m_title = "";
272 for (; m_actual != ':' && m_actual != '\0'; next_char(false)) {
273 m_title += m_actual;
274 }
275 if (!m_title.isEmpty()) LOGI("title: %s", m_title.c_str());
276 }
277
278 int parse_num() {
279 int val = 0;
280 do {
281 val *= 10;
282 val += m_actual - '0';
283 } while (isdigit(next_char()));
284
285 return val;
286 }
287
288 void parse_defaults() {
289 char id{' '};
290
291 while (' ' == next_char());
292 do {
293 if (isdigit(m_actual)) {
294 switch (id) {
295 case 'o':
296 m_octave = parse_num();
297 LOGI("default octave: %d", m_octave);
298 break;
299 case 'd':
300 m_duration = parse_num();
301 LOGI("default duration: %d", m_duration);
302 break;
303 case 'b':
304 m_bpm = parse_num();
305 LOGI("default bpm: %d", m_bpm);
306 break;
307 }
308 continue;
309 } else if (isalpha(m_actual)) {
310 id = m_actual;
311 }
312 next_char();
313 } while (':' != m_actual);
314
315 if (m_bpm != 0)
316 m_msec_semi = 240000.0 / m_bpm;
317 else
318 m_msec_semi = 750.0;
319
320 LOGI("msec per semi: %.2f", m_msec_semi);
321 }
322
323 float transpose(float frequency, int8_t octaves) {
324 if (octaves == 0) return frequency;
325 return frequency * powf(2.0f, octaves);
326 }
327
328 void parse_notes() {
329 // Ensure we start reading after the defaults
330 // section
331 if (m_actual == ':') next_char();
332
333 while (m_actual != 0) {
334 // skip separators
335 while (m_actual == ' ' || m_actual == ',') {
336 if (next_char() == 0) return;
337 }
338
339 if (m_actual == 0) break;
340
341 // optional duration (number)
342 int duration = m_duration;
343 if (isdigit(m_actual)) {
344 duration = parse_num();
345 if (m_actual == 0) break;
346 }
347
348 // note letter or rest
349 char name = m_actual;
350 if (name == 'p' || name == 'r') { // pause/rest
351 next_char();
352 double mult = 1.0;
353 if (m_actual == '.') {
354 mult = 1.5;
355 next_char();
356 }
357 int msec = (int)((m_msec_semi / duration) * mult);
358 play_note(0.0f, msec, -1);
359 continue;
360 }
361
362 // map letter to MusicalNotes enum
363 MusicalNotes::MusicalNotesEnum noteEnum = MusicalNotes::C;
364 switch (name) {
365 case 'c':
366 noteEnum = MusicalNotes::C;
367 break;
368 case 'd':
369 noteEnum = MusicalNotes::D;
370 break;
371 case 'e':
372 noteEnum = MusicalNotes::E;
373 break;
374 case 'f':
375 noteEnum = MusicalNotes::F;
376 break;
377 case 'g':
378 noteEnum = MusicalNotes::G;
379 break;
380 case 'a':
381 noteEnum = MusicalNotes::A;
382 break;
383 case 'b':
384 case 'h':
385 noteEnum = MusicalNotes::B;
386 break;
387 default:
388 // unknown symbol -> skip
389 next_char();
390 continue;
391 }
392
393 // advance to next char to inspect
394 // accidental/octave/dot
395 if (next_char() == 0) break;
396
397 // accidental
398 int semitone = (int)noteEnum;
399 if (m_actual == '#') {
400 semitone++;
401 if (semitone > 11) {
402 semitone = 0;
403 m_octave += 1;
404 }
405 noteEnum = (MusicalNotes::MusicalNotesEnum)semitone;
406 if (next_char() == 0) break;
407 }
408
409 // octave
410 int octave = m_octave;
411 if (m_actual >= '0' && m_actual <= '9') {
412 octave = m_actual - '0';
413 if (next_char() == 0) {
414 // compute and play
415 float freq = m_notes.frequency(noteEnum, (uint8_t)octave);
416 freq = transpose(freq, m_tranpose_octaves);
417 int msec = (int)((m_msec_semi / duration) * 1.0);
418 int midi = m_notes.frequencyToMidiNote(freq);
419 play_note(freq, msec, midi);
420 break;
421 }
422 }
423
424 // dotted duration
425 double mult_duration = 1.0;
426 if (m_actual == '.') {
427 mult_duration = 1.5;
428 next_char();
429 }
430
431 float freq = m_notes.frequency(noteEnum, (uint8_t)octave);
432 freq = transpose(freq, m_tranpose_octaves);
433 int msec = (int)((m_msec_semi / duration) * mult_duration);
434 int midi = m_notes.frequencyToMidiNote(freq);
435 play_note(freq, msec, midi);
436 }
437 }
438};
439
440} // 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:210
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:199
const char * getTitle()
Definition RTTTLOutput.h:156
void setReference(void *ref)
Provide reference for callback.
Definition RTTTLOutput.h:206
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 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 bool resize(int len)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:418
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