/* AudioGeneratorRTTTL Audio output generator that plays RTTTL (Nokia ringtone) Based on the Rtttl Arduino library by James BM, https://github.com/spicajames/Rtttl Based on the gist from Daniel Hall https://gist.github.com/smarthall/1618800 Copyright (C) 2018 Earle F. Philhower, III This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "AudioGeneratorRTTTL.h" AudioGeneratorRTTTL::AudioGeneratorRTTTL() { running = false; file = NULL; output = NULL; rate = 22050; buff = nullptr; ptr = 0; } AudioGeneratorRTTTL::~AudioGeneratorRTTTL() { free(buff); } bool AudioGeneratorRTTTL::stop() { if (!file || !output) { return false; } running = false; output->stop(); return file->close(); } bool AudioGeneratorRTTTL::isRunning() { return running; } bool AudioGeneratorRTTTL::loop() { if (!running) goto done; // Nothing to do here! // Load the next note, if we've hit the end of the last one if (samplesSent == ttlSamples) { if (!GetNextNote()) { running = false; goto done; } samplesSent = 0; } // Try and send out the remainder of the existing note, one per loop() if (ttlSamplesPerWaveFP10 == 0) { // Mute int16_t mute[2] = {0, 0}; while ((samplesSent < ttlSamples) && output->ConsumeSample(mute)) { samplesSent++; } } else { while (samplesSent < ttlSamples) { int samplesSentFP10 = samplesSent << 10; int rem = samplesSentFP10 % ttlSamplesPerWaveFP10; int16_t val = (rem > ttlSamplesPerWaveFP10/2) ? 8192:-8192; int16_t s[2] = { val, val }; if (!output->ConsumeSample(s)) goto done; samplesSent++; } } done: file->loop(); output->loop(); return running; } bool AudioGeneratorRTTTL::SkipWhitespace() { while ((ptr < len) && (buff[ptr] == ' ')) ptr++; return ptr < len; } bool AudioGeneratorRTTTL::ReadInt(int *dest) { if (ptr >= len) return false; SkipWhitespace(); if (ptr >= len) return false; if ((buff[ptr] <'0') || (buff[ptr] > '9')) return false; int t = 0; while ((buff[ptr] >= '0') && (buff[ptr] <='9')) { t = (t * 10) + (buff[ptr] - '0'); ptr++; } *dest = t; return true; } bool AudioGeneratorRTTTL::ParseHeader() { // Skip the title while ((ptr < len) && (buff[ptr] != ':')) ptr++; if (ptr >= len) return false; if (buff[ptr++] != ':') return false; if (!SkipWhitespace()) return false; if ((buff[ptr] != 'd') && (buff[ptr] != 'D')) return false; ptr++; if (!SkipWhitespace()) return false; if (buff[ptr++] != '=') return false; if (!ReadInt(&defaultDuration)) return false; if (!SkipWhitespace()) return false; if (buff[ptr++] != ',') return false; if (!SkipWhitespace()) return false; if ((buff[ptr] != 'o') && (buff[ptr] != 'O')) return false; ptr++; if (!SkipWhitespace()) return false; if (buff[ptr++] != '=') return false; if (!ReadInt(&defaultOctave)) return false; if (!SkipWhitespace()) return false; if (buff[ptr++] != ',') return false; int bpm; if (!SkipWhitespace()) return false; if ((buff[ptr] != 'b') && (buff[ptr] != 'B')) return false; ptr++; if (!SkipWhitespace()) return false; if (buff[ptr++] != '=') return false; if (!ReadInt(&bpm)) return false; if (!SkipWhitespace()) return false; if (buff[ptr++] != ':') return false; wholeNoteMS = (60 * 1000 * 4) / bpm; return true; } #define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_CS5 554 #define NOTE_D5 587 #define NOTE_DS5 622 #define NOTE_E5 659 #define NOTE_F5 698 #define NOTE_FS5 740 #define NOTE_G5 784 #define NOTE_GS5 831 #define NOTE_A5 880 #define NOTE_AS5 932 #define NOTE_B5 988 #define NOTE_C6 1047 #define NOTE_CS6 1109 #define NOTE_D6 1175 #define NOTE_DS6 1245 #define NOTE_E6 1319 #define NOTE_F6 1397 #define NOTE_FS6 1480 #define NOTE_G6 1568 #define NOTE_GS6 1661 #define NOTE_A6 1760 #define NOTE_AS6 1865 #define NOTE_B6 1976 #define NOTE_C7 2093 #define NOTE_CS7 2217 #define NOTE_D7 2349 #define NOTE_DS7 2489 #define NOTE_E7 2637 #define NOTE_F7 2794 #define NOTE_FS7 2960 #define NOTE_G7 3136 #define NOTE_GS7 3322 #define NOTE_A7 3520 #define NOTE_AS7 3729 #define NOTE_B7 3951 static int notes[49] = { 0, NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6, NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7 }; bool AudioGeneratorRTTTL::GetNextNote() { int dur, note, scale; if (ptr >= len) return false; if (!ReadInt(&dur)) { dur = defaultDuration; } dur = wholeNoteMS / dur; if (ptr >= len) return false; note = 0; switch (buff[ptr++]) { case 'c': case 'C': note = 1; break; case 'd': case 'D': note = 3; break; case 'e': case 'E': note = 5; break; case 'f': case 'F': note = 6; break; case 'g': case 'G': note = 8; break; case 'a': case 'A': note = 10; break; case 'b': case 'B': note = 12; break; case 'p': case 'P': note = 0; break; default: return false; } if ((ptr < len) && (buff[ptr] == '#')) { ptr++; note++; } if (!ReadInt(&scale)) { scale = defaultOctave; } if ((ptr < len) && (buff[ptr] == '.')) { ptr++; dur += dur / 2; } // Eat any trailing whitespace and comma SkipWhitespace(); if ((ptr < len) && (buff[ptr]==',')) { ptr++; } if (scale < 4) scale = 4; if (scale > 7) scale = 7; if (note) { int freq = notes[(scale - 4) * 12 + note]; // Convert from frequency in Hz to high and low samples in fixed point ttlSamplesPerWaveFP10 = (rate << 10) / freq; } else { ttlSamplesPerWaveFP10 = 0; } ttlSamples = (rate * dur ) / 1000; //audioLogger->printf("%d %d %d %d %d\n", dur, note, scale, ttlSamplesPerWaveFP10, ttlSamples ); return true; } bool AudioGeneratorRTTTL::begin(AudioFileSource *source, AudioOutput *output) { if (!source) return false; file = source; if (!output) return false; this->output = output; if (!file->isOpen()) return false; // Error len = file->getSize(); buff = (char *)malloc(len); if (!buff) return false; if (file->read(buff, len) != (uint32_t)len) return false; ptr = 0; samplesSent = 0; ttlSamples = 0; if (!ParseHeader()) return false; if (!output->SetRate( rate )) return false; if (!output->SetBitsPerSample( 16 )) return false; if (!output->SetChannels( 2 )) return false; if (!output->begin()) return false; running = true; return true; }