rMP3 Audio with Synchronized Lyrics
Here is an example Arduino sketch that utilizes both the playback and SD card functions of the rMP3.
The sketch plays a song, and displays lyrics.
The lyrics are read from a standard LRC file, which you either already have, or you can easily download (MiniLyrics is a great app for lyrics). LRC files have a time stamp and a lyric per each line. The time stamps are synchronized using a 1/100th second timer (Timer2) on the Arduino.
rMP3 Playback and Lyrics Example from Rogue Robotics on Vimeo.
Sketch
Here is the source code:
rMP3_Lyrics.pde
#include <NewSoftSerial.h>
#include <RogueMP3.h>
#include <RogueSD.h>
#include <avr/io.h>
#include <avr/interrupt.h>
NewSoftSerial rmp3_s(6,7);
RogueSD filecommands(rmp3_s);
RogueMP3 rmp3(rmp3_s);
volatile uint32_t <u>h</u>;
const char P_song[] PROGMEM = "/Daft Punk - Technologic.mp3";
const char P_lyricfile[] PROGMEM = "/Daft Punk - Technologic.lrc";
#define LRCHANDLE 1
#define MAX_LYRIC_LINE 96
char lyricline[MAX_LYRIC_LINE];
int32_t getnumber(char **str, uint8_t base)
{
uint8_t c, neg = 0;
uint32_t val;
val = 0;
c = **str;
if(c == '-')
{
neg = 1;
(*str)++;
c = **str;
}
while(((c >= 'A') && (c <= 'Z'))
|| ((c >= 'a') && (c <= 'z'))
|| ((c >= '0') && (c <= '9')))
{
if(c >= 'a') c -= 0x57; // c = c - 'a' + 0x0a, c = c - ('a' - 0x0a)
else if(c >= 'A') c -= 0x37; // c = c - 'A' + 0x0A
else c -= '0';
if(c >= base) break;
val *= base;
val += c;
(*str)++;
c = **str;
}
return neg ? -val : val;
}
boolean open_lyricfile(const char *lrcfile)
{
if (filecommands.open_P(LRCHANDLE, P_lyricfile, OPEN_READ) > 0)
return true;
else
return false;
}
// return value:
// < 0 EOF
// 0 bad read, no data - skip
// > 0 good read
int8_t read_lyric(uint32_t *nexttime, char **nextlyric)
{
char *p;
char *f;
uint32_t time, time_fraction;
if (filecommands.readln(LRCHANDLE, MAX_LYRIC_LINE, lyricline) >= 0)
{
// parse values
// format: [mm:ss.xxx] lyric text
// mm: minutes
// ss.xxx: seconds and fractions of seconds
// xxx is optional, and can be 2 or 3 decimal places
p = lyricline;
if (*p == '[')
{
// good start
// get minutes
time = getnumber(&(++p), 10) * 60 * 100;
if (*p == ':')
{
// get seconds
time += getnumber(&(++p), 10) * 100;
if (*p == '.')
{
// we have optional fractions of seconds
f = ++p; // for later (number of decimal places)
time_fraction = getnumber(&p, 10);
if ((p - f) == 2)
{
time += time_fraction;
}
else if ((p - f) == 3)
{
time += time_fraction/10;
}
else
{
// what the heck?
return 0;
}
// we now have the time in hundredths of seconds in "time"
}
if (*p == ']')
{
// got closing
// drop any spaces between
while (*(++p) == ' ');
*nextlyric = p;
*nexttime = time;
return 1;
}
else
{
// what the heck? no . or ] - whacky stuff
return 0;
}
}
else
{
// missing : and seconds... strange
return 0;
}
}
else
{
// bail out, no [ at the start
return 0;
}
}
else
{
// EOF
return -1;
}
}
void setup()
{
Serial.begin(9600);
rmp3_s.begin(9600);
rmp3.sync();
filecommands.sync();
TCCR2A = 0b0000010;
OCR2A = (F_CPU/1024/100)-1;
TIMSK2 = 0b0000010;
TCCR2B = 0b0000111; // ck/1024 prescalar
}
ISR(TIMER2_COMPA_vect)
{
<u>h</u>++;
}
void loop()
{
// POC - single run on a single file
uint32_t starttime, nexttime;
char *nextlyric;
boolean playing;
int8_t lyricstatus;
// nexttime = milliseconds into playback to set pattern
// start
playing = (rmp3.playfile_P(P_song) == 0) ? true : false;
if (playing)
{
if (open_lyricfile(P_lyricfile))
{
starttime = <u>h</u>;
lyricstatus = 1;
while (playing && lyricstatus >= 0)
{
lyricstatus = read_lyric(&nexttime, &nextlyric);
// if lyricstatus == 0, we just skip - it's a bad line or info line
if (lyricstatus > 0)
{
Serial.print("Waiting for: ");
Serial.print(nexttime, DEC);
// just wait here until we hit the next time (if we do!)
// while ((millis() - starttime) < (nexttime + t_offset))
// millis() is seriously hobbled - let's use our own
while((<u>h</u> - starttime) < nexttime)
{
// if we are still playing
if (rmp3.getplaybackstatus() == 'P')
{
// do whatever
}
else
{
// get outta here!
playing = false;
Serial.println(); Serial.println("Playback stopped!");
break;
}
}
// ok, hit pattern time - set the pattern
if (playing)
{
Serial.print(" - ");
Serial.println(nextlyric);
}
}
else if (lyricstatus < 0)
{
// no more lyrics!!
filecommands.close(LRCHANDLE);
Serial.println("Out of lyrics!");
}
// otherwise, just get the next lyric
}
}
else
{
// couldn't open lyrics file
Serial.println("Couldn't open lyrics file.");
}
}
else
{
// couldn't play the song
Serial.println("Couldn't play the song");
}
// DONE... for now.
Serial.println("DONE");
for (;;);
}
If you need it, here is the settings file (either UMP3.CFG or RMP3.CFG, depending on which module your using):
D0