boothifier/lib/AnyRtttl/src/anyrtttl.cpp

673 lines
17 KiB
C++

// ---------------------------------------------------------------------------
// AUTHOR/LICENSE:
// The following code was written by Antoine Beauchamp. For other authors, see AUTHORS file.
// The code & updates for the library can be found at https://github.com/end2endzone/AnyRtttl
// MIT License: http://www.opensource.org/licenses/mit-license.php
// ---------------------------------------------------------------------------
#include "Arduino.h"
#include "anyrtttl.h"
#include "binrtttl.h"
/*********************************************************
* RTTTL Library data
*********************************************************/
namespace anyrtttl
{
const uint16_t notes[] = { NOTE_SILENT,
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
};
#define isdigit(n) (n >= '0' && n <= '9')
typedef uint16_t TONE_DURATION;
static const byte NOTES_PER_OCTAVE = 12;
const char * buffer = "";
ReadCharFuncPtr readCharFunc = &readChar;
int bufferIndex = -32760;
byte default_dur = 4;
byte default_oct = 5;
RTTTL_BPM bpm = 63;
RTTTL_DURATION wholenote;
byte pin = -1;
unsigned long delayToNextNote = 0; //milliseconds before playing the next note
bool playing = false;
TONE_DURATION duration;
byte noteOffset;
RTTTL_OCTAVE_VALUE scale;
int tmpNumber;
const char * readNumber(const char * iBuffer, int & oValue, ReadCharFuncPtr iReadCharFunc)
{
oValue = 0;
while(isdigit(iReadCharFunc(iBuffer)))
{
oValue = (oValue * 10) + (readCharFunc(buffer) - '0');
iBuffer++;
}
return iBuffer;
}
void serialPrint(const char * iBuffer, ReadCharFuncPtr iReadCharFunc)
{
char c = readCharFunc(iBuffer);
while(c) {
Serial.print(c);
iBuffer++;
c = readCharFunc(iBuffer);
}
}
/****************************************************************************
* Custom functions
****************************************************************************/
ToneFuncPtr _tone = &tone;
NoToneFuncPtr _noTone = &noTone;
DelayFuncPtr _delay = &delay;
MillisFuncPtr _millis = &millis;
void setToneFunction(ToneFuncPtr iFunc) {
_tone = iFunc;
}
void setNoToneFunction(NoToneFuncPtr iFunc) {
_noTone = iFunc;
}
void setDelayFunction(DelayFuncPtr iFunc) {
_delay = iFunc;
}
void setMillisFunction(MillisFuncPtr iFunc) {
_millis = iFunc;
}
char readChar(const char * iBuffer) {
return *iBuffer;
}
char readChar_P(const char * iBuffer) {
return pgm_read_byte_near(iBuffer);
}
/****************************************************************************
* Blocking API
****************************************************************************/
namespace blocking
{
void play(byte iPin, const char * iBuffer, ReadCharFuncPtr iReadCharFunc) {
// Absolutely no error checking in here
default_dur = 4;
default_oct = 6;
bpm = 63;
buffer = iBuffer;
readCharFunc = iReadCharFunc;
#ifdef ANY_RTTTL_DEBUG
Serial.print("playing: ");
serialPrint(buffer, readCharFunc);
Serial.println();
#endif
// format: d=N,o=N,b=NNN:
// find the start (skip name, etc)
while(readCharFunc(buffer) != ':') buffer++; // ignore name
buffer++; // skip ':'
// get default duration
if(readCharFunc(buffer) == 'd')
{
buffer++; buffer++; // skip "d="
buffer = readNumber(buffer, tmpNumber, readCharFunc);
if(tmpNumber > 0)
default_dur = tmpNumber;
buffer++; // skip comma
}
#ifdef ANY_RTTTL_INFO
Serial.print("ddur: "); Serial.println(default_dur, 10);
#endif
// get default octave
if(readCharFunc(buffer) == 'o')
{
buffer++; buffer++; // skip "o="
buffer = readNumber(buffer, tmpNumber, readCharFunc);
if(tmpNumber >= 3 && tmpNumber <= 7)
default_oct = tmpNumber;
buffer++; // skip comma
}
#ifdef ANY_RTTTL_INFO
Serial.print("doct: "); Serial.println(default_oct, 10);
#endif
// get BPM
if(readCharFunc(buffer) == 'b')
{
buffer++; buffer++; // skip "b="
buffer = readNumber(buffer, tmpNumber, readCharFunc);
bpm = tmpNumber;
buffer++; // skip colon
}
#ifdef ANY_RTTTL_INFO
Serial.print("bpm: "); Serial.println(bpm, 10);
#endif
// BPM usually expresses the number of quarter notes per minute
wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds)
#ifdef ANY_RTTTL_INFO
Serial.print("wn: "); Serial.println(wholenote, 10);
#endif
// now begin note loop
while(readCharFunc(buffer))
{
// first, get note duration, if available
buffer = readNumber(buffer, tmpNumber, readCharFunc);
if(tmpNumber)
duration = wholenote / tmpNumber;
else
duration = wholenote / default_dur; // we will need to check if we are a dotted noteOffset after
// now get the note
noteOffset = getNoteOffsetFromLetter(readCharFunc(buffer));
buffer++;
// now, get optional '#' sharp
if(readCharFunc(buffer) == '#')
{
noteOffset++;
buffer++;
}
// now, get optional '.' dotted note
if(readCharFunc(buffer) == '.')
{
duration += duration/2;
buffer++;
}
// now, get scale
if(isdigit(readCharFunc(buffer)))
{
scale = readCharFunc(buffer) - '0';
buffer++;
}
else
{
scale = default_oct;
}
if(readCharFunc(buffer) == ',')
buffer++; // skip comma for next note (or we may be at the end)
// now play the note
if(noteOffset)
{
uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset];
#ifdef ANY_RTTTL_INFO
Serial.print("Playing: ");
Serial.print(scale, 10); Serial.print(' ');
Serial.print(noteOffset, 10); Serial.print(" (");
Serial.print(frequency, 10);
Serial.print(") ");
Serial.println(duration, 10);
#endif
_tone(iPin, frequency, duration);
_delay(duration+1);
_noTone(iPin);
}
else
{
#ifdef ANY_RTTTL_INFO
Serial.print("Pausing: ");
Serial.println(duration, 10);
#endif
_delay(duration);
}
}
}
void play(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar); }
void play(byte iPin, const __FlashStringHelper* str) { play(iPin, (const char *)str, &readChar_P); }
void playProgMem(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar_P); }
void play_P(byte iPin, const char * iBuffer) { play(iPin, iBuffer, &readChar_P); }
void play_P(byte iPin, const __FlashStringHelper* str) { play(iPin, (const char *)str, &readChar_P); }
void play16Bits(int iPin, const unsigned char * iBuffer, int iNumNotes) {
// Absolutely no error checking in here
RTTTL_DEFAULT_VALUE_SECTION * defaultSection = (RTTTL_DEFAULT_VALUE_SECTION *)iBuffer;
RTTTL_NOTE * notesBuffer = (RTTTL_NOTE *)iBuffer;
bpm = defaultSection->bpm;
#ifdef ANY_RTTTL_DEBUG
Serial.print("numNotes=");
Serial.println(iNumNotes);
// format: d=N,o=N,b=NNN:
Serial.print("d=");
Serial.print(getNoteDurationFromIndex(defaultSection->durationIdx));
Serial.print(",o=");
Serial.print(getNoteOctaveFromIndex(defaultSection->octaveIdx));
Serial.print(",b=");
Serial.println(bpm);
#endif
// BPM usually expresses the number of quarter notes per minute
wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds)
// now begin note loop
for(int i=0; i<iNumNotes; i++) {
const RTTTL_NOTE & n = notesBuffer[i+1]; //offset by 16 bits for RTTTL_DEFAULT_VALUE_SECTION
// first, get note duration, if available
duration = wholenote / getNoteDurationFromIndex(n.durationIdx);
// now get the note
//noteOffset = noteOffsets[n.noteIdx];
noteOffset = getNoteOffsetFromLetterIndex(n.noteIdx);
// now, get optional '#' sharp
if(n.pound)
{
noteOffset++;
}
// now, get optional '.' dotted note
if(n.dotted)
{
duration += duration/2;
}
// now, get scale
scale = getNoteOctaveFromIndex(n.octaveIdx);
if(noteOffset)
{
#ifdef ANY_RTTTL_DEBUG
Serial.print(getNoteDurationFromIndex(n.durationIdx));
static const char noteCharacterValues[] = {'c','d','e','f','g','a','b','p'};
Serial.print(noteCharacterValues[n.noteIdx]);
Serial.print( (n.pound ? "#" : "") );
Serial.print( (n.dotted ? "." : "") );
Serial.println(getNoteOctaveFromIndex(n.octaveIdx));
#endif
uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset];
_tone(iPin, frequency, duration);
_delay(duration+1);
_noTone(iPin);
}
else
{
#ifdef ANY_RTTTL_DEBUG
Serial.print(getNoteDurationFromIndex(n.durationIdx));
static const char noteCharacterValues[] = {'c','d','e','f','g','a','b','p'};
Serial.print(noteCharacterValues[n.noteIdx]);
Serial.print( (n.pound ? "#" : "") );
Serial.print( (n.dotted ? "." : "") );
Serial.println();
#endif
_delay(duration);
}
}
}
void play10Bits(int iPin, int iNumNotes, BitReadFuncPtr iFuncPtr) {
// Absolutely no error checking in here
//read default section
RTTTL_DEFAULT_VALUE_SECTION defaultSection;
defaultSection.raw = iFuncPtr(16);
bpm = defaultSection.bpm;
#ifdef ANY_RTTTL_DEBUG
Serial.print("numNotes=");
Serial.println(iNumNotes);
// format: d=N,o=N,b=NNN:
Serial.print("d=");
Serial.print(getNoteDurationFromIndex(defaultSection.durationIdx));
Serial.print(",o=");
Serial.print(getNoteOctaveFromIndex(defaultSection.octaveIdx));
Serial.print(",b=");
Serial.println(bpm);
#endif
// BPM usually expresses the number of quarter notes per minute
wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds)
// now begin note loop
for(int i=0; i<iNumNotes; i++) {
RTTTL_NOTE n;
n.raw = iFuncPtr(10);
// first, get note duration, if available
duration = wholenote / getNoteDurationFromIndex(n.durationIdx);
// now get the note
noteOffset = getNoteOffsetFromLetterIndex(n.noteIdx);
// now, get optional '#' sharp
if(n.pound)
{
noteOffset++;
}
// now, get optional '.' dotted note
if(n.dotted)
{
duration += duration/2;
}
// now, get scale
scale = getNoteOctaveFromIndex(n.octaveIdx);
if(noteOffset)
{
#ifdef ANY_RTTTL_DEBUG
Serial.print(getNoteDurationFromIndex(n.durationIdx));
static const char noteCharacterValues[] = {'c','d','e','f','g','a','b','p'};
Serial.print(noteCharacterValues[n.noteIdx]);
Serial.print( (n.pound ? "#" : "") );
Serial.print( (n.dotted ? "." : "") );
Serial.println(getNoteOctaveFromIndex(n.octaveIdx));
#endif
uint16_t frequency = notes[(scale - 4) * 12 + noteOffset];
_tone(iPin, frequency, duration);
_delay(duration+1);
_noTone(iPin);
}
else
{
#ifdef ANY_RTTTL_DEBUG
Serial.print(getNoteDurationFromIndex(n.durationIdx));
static const char noteCharacterValues[] = {'c','d','e','f','g','a','b','p'};
Serial.print(noteCharacterValues[n.noteIdx]);
Serial.print( (n.pound ? "#" : "") );
Serial.print( (n.dotted ? "." : "") );
Serial.println();
#endif
_delay(duration);
}
}
}
}; //blocking namespace
/****************************************************************************
* Non-blocking API
****************************************************************************/
namespace nonblocking
{
//pre-declaration
void nextnote();
void begin(byte iPin, const char * iBuffer, ReadCharFuncPtr iReadCharFunc)
{
//init values
pin = iPin;
buffer = iBuffer;
bufferIndex = 0;
default_dur = 4;
default_oct = 6;
bpm=63;
playing = true;
delayToNextNote = 0;
readCharFunc = iReadCharFunc;
#ifdef ANY_RTTTL_DEBUG
Serial.print("playing: ");
serialPrint(buffer, readCharFunc);
Serial.println();
#endif
//stop current note
noTone(pin);
// format: d=N,o=N,b=NNN:
// find the start (skip name, etc)
//read buffer until first note
while(readCharFunc(buffer) != ':') buffer++; // ignore name
buffer++; // skip ':'
// get default duration
if(readCharFunc(buffer) == 'd')
{
buffer++; buffer++; // skip "d="
buffer = readNumber(buffer, tmpNumber, readCharFunc);
if(tmpNumber > 0)
default_dur = tmpNumber;
buffer++; // skip comma
}
#ifdef ANY_RTTTL_INFO
Serial.print("ddur: "); Serial.println(default_dur, 10);
#endif
// get default octave
if(readCharFunc(buffer) == 'o')
{
buffer++; buffer++; // skip "o="
buffer = readNumber(buffer, tmpNumber, readCharFunc);
if(tmpNumber >= 3 && tmpNumber <= 7)
default_oct = tmpNumber;
buffer++; // skip comma
}
#ifdef ANY_RTTTL_INFO
Serial.print("doct: "); Serial.println(default_oct, 10);
#endif
// get BPM
if(readCharFunc(buffer) == 'b')
{
buffer++; buffer++; // skip "b="
buffer = readNumber(buffer, tmpNumber, readCharFunc);
bpm = tmpNumber;
buffer++; // skip colon
}
#ifdef ANY_RTTTL_INFO
Serial.print("bpm: "); Serial.println(bpm, 10);
#endif
// BPM usually expresses the number of quarter notes per minute
wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole noteOffset (in milliseconds)
#ifdef ANY_RTTTL_INFO
Serial.print("wn: "); Serial.println(wholenote, 10);
#endif
}
void begin(byte iPin, const char * iBuffer) { begin(iPin, iBuffer, &readChar); }
void begin(byte iPin, const __FlashStringHelper* str) { begin(iPin, (const char *)str, &readChar_P); }
void beginProgMem(byte iPin, const char * iBuffer) { begin(iPin, iBuffer, &readChar_P); }
void begin_P(byte iPin, const char * iBuffer) { begin(iPin, iBuffer, &readChar_P); }
void begin_P(byte iPin, const __FlashStringHelper* str) { begin(iPin, (const char *)str, &readChar_P); }
void nextnote()
{
//stop current note
_noTone(pin);
// first, get note duration, if available
buffer = readNumber(buffer, tmpNumber, readCharFunc);
if(tmpNumber)
duration = wholenote / tmpNumber;
else
duration = wholenote / default_dur; // we will need to check if we are a dotted noteOffset after
// now get the note
noteOffset = getNoteOffsetFromLetter(readCharFunc(buffer));
buffer++;
// now, get optional '#' sharp
if(readCharFunc(buffer) == '#')
{
noteOffset++;
buffer++;
}
// now, get optional '.' dotted note
if(readCharFunc(buffer) == '.')
{
duration += duration/2;
buffer++;
}
// now, get scale
if(isdigit(readCharFunc(buffer)))
{
scale = readCharFunc(buffer) - '0';
buffer++;
}
else
{
scale = default_oct;
}
if(readCharFunc(buffer) == ',')
buffer++; // skip comma for next note (or we may be at the end)
// now play the note
if(noteOffset)
{
#ifdef ANY_RTTTL_INFO
Serial.print("Playing: ");
Serial.print(scale, 10); Serial.print(' ');
Serial.print(noteOffset, 10); Serial.print(" (");
Serial.print(notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset], 10);
Serial.print(") ");
Serial.println(duration, 10);
#endif
uint16_t frequency = notes[(scale - 4) * NOTES_PER_OCTAVE + noteOffset];
_tone(pin, frequency, duration);
delayToNextNote = _millis() + (duration+1);
}
else
{
#ifdef ANY_RTTTL_INFO
Serial.print("Pausing: ");
Serial.println(duration, 10);
#endif
delayToNextNote = _millis() + (duration);
}
}
void play()
{
//if done playing the song, return
if (!playing)
{
#ifdef ANY_RTTTL_DEBUG
Serial.println("done playing...");
#endif
return;
}
//are we still playing a note ?
unsigned long m = _millis();
if (m < delayToNextNote)
{
#ifdef ANY_RTTTL_DEBUG
Serial.println("still playing a note...");
#endif
//wait until the note is completed
return;
}
//ready to play the next note
if (readCharFunc(buffer) == '\0')
{
//no more notes. Reached the end of the last note
#ifdef ANY_RTTTL_DEBUG
Serial.println("end of note...");
#endif
playing = false;
//stop current note (if any)
_noTone(pin);
return; //end of the song
}
else
{
//more notes to play...
#ifdef ANY_RTTTL_DEBUG
Serial.println("next note...");
#endif
nextnote();
}
}
void stop()
{
if (playing)
{
//increase song buffer until the end
while (readCharFunc(buffer) != '\0')
{
buffer++;
}
}
playing = false;
//stop current note (if any)
_noTone(pin);
}
bool done()
{
return !playing;
}
bool isPlaying()
{
return playing;
}
}; //nonblocking namespace
}; //anyrtttl namespace