ESP8266Audio/src/AudioGeneratorRTTTL.cpp

296 lines
7.4 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#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;
}