#include <SD.h>
#include <LiquidCrystal.h>
#include <TMRpcm.h>
TMRpcm player;
LiquidCrystal lcd_1(2, 3, 4, 5, 6, 7);
// queue config
const int PROGMEM maxQueueDepth = 4;
unsigned int beatQueue[maxQueueDepth][3];
unsigned int beatDisplay[32];
// constant configured environmentals
const int PROGMEM speakerPin = 9;
const int PROGMEM knobPin = A1;
const int PROGMEM buttonPin = A0;
const int PROGMEM clockPin = A5;
const int PROGMEM SD_CS_PIN = 10;
const int PROGMEM commonFPS = 10;
// WOKWI/DEBUG controls
const bool WOKWI = true;
const bool debugEndingScreen = false; // dies aat 30 sec
// defaults for where to get check
const String songCountFileName = "songcont.txt";
const String songNamesFileName = "songindx.txt";
// file reading global
File workingFile;
// beatMap variables
String songName; // retrieved song name
int songIndex = -1; // song index
// file structure
// songcont.txt == count of songs available
// songname.txt == index of songs available
// beat structure -- stored in progmem
// sample song : 1.b8t
// 100, 4, 100 // start ms, display position, expire ms
// 55555, 55555, 55555 // end signal
long initTimeStamp = 0;
const int shiftDelayTime = 125;
/*
void setup()
- the Arduino setup function that runs once. it’s responsible for initializing inputs and setting up libraries
- does basic IO declaration
- sets up the LCD, SD card and TMRpcm libraries
- offloads other configuration options to getSongsSelection()
- starts playback of the song at the end
*/
void setup() {
// setup in/outs
pinMode(buttonPin, INPUT);
pinMode(clockPin, INPUT);
pinMode(knobPin, INPUT);
Serial.begin(115200);
// wait for usb serial to populate
while (!Serial) {
yield();
}
lcd_1.begin(16, 2); // setup the LCD to display
lcd_1.clear();
// splash screen
lcd_1.print(F("RhythmBeep-su!"));
lcd_1.setCursor(0, 1);
lcd_1.print(F("The Game that Beats Back!"));
delay(500);
for (int x = 0; x < 64; x++) {
delay(shiftDelayTime);
lcd_1.scrollDisplayLeft();
}
lcd_1.clear();
// https://docs.wokwi.com/parts/wokwi-microsd-card
if (!SD.begin(SD_CS_PIN)) {
Serial.println(F("SD initialization failed."));
lcd_1.print("Er: SD Init Fail");
while (1);
}
Serial.println(F("SD initialized"));
// setup tmrpcm
player.speakerPin = speakerPin;
player.quality(1);
Serial.println(F("Files in the card:"));
workingFile = SD.open("/");
printDirectory(workingFile, 0);
Serial.println();
getSongSelection();
String songFilename = String(songIndex) + ".WAV";
player.play(songFilename.c_str());
lcd_1.clear();
initTimeStamp = millis();
}
/*
void clearLCDLine()
- responsible for clearing a line on the LCD
- prints a 16 space string to the line
- sourced from // https://forum.arduino.cc/t/how-to-delete-line-on-lcd/206905/12
*/
void clearLCDLine(int line) {
lcd_1.setCursor(0, line);
lcd_1.print(F(" "));
lcd_1.setCursor(0, line);
}
/*
void printDirectory(File dir, int numTabs)
- DEBUG RELIC: responsible for printing to serial the files within a directory.
// sourced from // https://forum.arduino.cc/t/error-when-sd-card-contains-more-than-10-files/629753
*/
void printDirectory(File dir, int numTabs) {
while (true) {
File entry = dir.openNextFile();
if (! entry) {
// no more files
break;
}
for (uint8_t i = 0; i < numTabs; i++) {
Serial.print('\t');
}
Serial.print(entry.name());
if (entry.isDirectory()) {
Serial.println("/");
printDirectory(entry, numTabs + 1);
} else {
// files have sizes, directories do not
Serial.print("\t\t");
Serial.println(entry.size(), DEC);
}
entry.close();
}
}
// inputLoop variables
bool clockState = HIGH;
int frameInput = 0;
int cursorPos = 0;
/*
void inputLoop()
- responsible for all input functions
- checks if button have been pressed regardless of frame rate, ensuring that inputs are timed with the clock (redundancy to catch d-latch failure events)
- if an button press has been pressed, increases the current frame's input counter
- checks the potentiometer and updates the cursor position if it has changed
*/
void inputLoop() {
clockState = digitalRead(A5);
// wokwi
if ((clockState == HIGH || WOKWI) && analogRead(A0) > 50) {
frameInput++;
}
// read and set cursor position
int newPosition = map(analogRead(knobPin), 680, 0, 0, 31);
if (cursorPos != newPosition) {
cursorPos = newPosition;
updateCursorPosition(cursorPos);
}
}
/*
void getSongSelection()
- large function responsible for getting the choice of song from the user
- requires index files to properly load (count and actual directory of songs) -- function reports error and hangs if not present
- retrieves and displays a songName from the SD card onto the LCD, giving the user an interval to confirm the song.
- if the user does not confirm in time, the next songName is retrieved and displayed
- if the user does confirm the song, the function exits.
- the list loops over itself if the end of index has been reached.
- if two minutes have passed without a selection, the function hangs. // workaround to fix bug -- bad fix we know...
*/
void getSongSelection() {
workingFile.close();
// ensure index files exist
if (SD.exists(songCountFileName) && SD.exists(songNamesFileName)) {
// open the count
workingFile = SD.open(songCountFileName);
Serial.print(songCountFileName + ": "); // logging
while (!workingFile.available()) {}; // wait until file is ready to be read
int numberOfLines = workingFile.readStringUntil(F("\n")).toInt(); // get the actual value
Serial.println(numberOfLines); // logging
workingFile.close();
// open the names
workingFile = SD.open(songNamesFileName, FILE_READ);
// update lcd
lcd_1.setCursor(0, 1);
lcd_1.print(F("Play? or wait."));
lcd_1.setCursor(0, 0);
bool flag = true;
unsigned long futureTimeStamp;
unsigned long animateTimeStamp;
unsigned long dieTimeStamp = millis() + 120000; // exit a minute into the future
while (flag) {
songIndex++;
Serial.print(songNamesFileName + ": "); // logging
delay(100);
while (!workingFile.available()) {};
songName = workingFile.readStringUntil('\n');
Serial.println(songName); // logging
lcd_1.home();
clearLCDLine(0);
lcd_1.print(songName);
long animateTimeStamp = millis();
long futureTimeStamp = animateTimeStamp + 5000;
while (millis() < futureTimeStamp) {
inputLoop();
if (millis() - animateTimeStamp > shiftDelayTime) {
lcd_1.scrollDisplayLeft();
animateTimeStamp = millis();
}
if (frameInput != 0) {
flag = false;
break;
}
}
if (songIndex >= numberOfLines) {
songIndex = -1;
workingFile.seek(0);
}
if (millis() > dieTimeStamp) {
lcd_1.clear();
lcd_1.print(F("Sleeping..."));
lcd_1.setCursor(0,1);
lcd_1.print(F("Reset to wake..."));
// do nothing
while (1) {yield();};
}
}
workingFile.close();
Serial.print(songName + " : songIndex : ");
Serial.println(songIndex);
// setup music file
setupSongFile();
lcd_1.blink();
}
else {
lcd_1.clear();
lcd_1.print(F("Er:No Indx Exists."));
lcd_1.setCursor(0, 1);
lcd_1.print(F("Need restore SD."));
while (1) {
yield();
};
}
}
/*
void setupSongFile()
- responsible for intitalizing the beatDisplay and prefetching the first 5 notes
- resets the display data structure representation by inputting -1
- prefeteches the first five beats
*/
void setupSongFile() {
// clear display
for (int i = 0; i < 32; i++) {
beatDisplay[i] = -1;
}
// pre-fill 5 notes
for (int i = 0; i < maxQueueDepth; i++) {
queueBeat();
}
}
// actual beats
const int beatLength[] = {370, 370, 341, 0, 0, 0};
const unsigned int beatMaps[][380][3] PROGMEM = {
{ // beatMap 0
{479,1,694},
{587,3,824},
{703,5,963},
{824,7,1108},
{941,9,1249},
{1062,11,1394},
{1183,13,1539},
{1301,15,1681},
{1416,2,1909},
{1538,4,2025},
{1657,6,2138},
{1775,8,2250},
{1891,10,2389},
{2007,12,2528},
{2132,14,2678},
{2248,1,2817},
{2607,2,3188},
{2650,3,3240},
{2687,4,3284},
{2957,5,3608},
{2991,6,3649},
{3023,7,3687},
{3274,9,4048},
{3306,11,4027},
{3335,12,4062},
{3564,14,4336},
{3596,15,4375},
{3625,1,4590},
{3714,2,4516},
{3738,3,4545},
{3777,4,4592},
{3849,1,4858},
{4025,2,4890},
{4060,3,4932},
{4092,4,4970},
{4127,1,5192},
{4189,2,5146},
{4217,3,5180},
{4250,4,5220},
{4307,5,5288},
{4514,6,5536},
{4721,7,5725},
{4771,8,5785},
{4985,9,6102},
{5017,13,6080},
{5050,14,6120},
{5082,15,6158},
{5196,15,6355},
{5256,17,6367},
{5464,19,6676},
{5489,20,6646},
{5525,21,6690},
{5553,22,6723},
{5584,23,6760},
{5614,24,6796},
{5646,25,6835},
{5677,26,6872},
{5735,28,6942},
{6171,1,7645},
{6288,3,7695},
{6409,5,7840},
{6528,7,7983},
{6648,9,8127},
{6759,11,8260},
{6881,13,8407},
{7000,15,8550},
{7121,17,8695},
{7243,19,8841},
{7362,21,8984},
{7478,23,9123},
{7598,24,9267},
{7715,25,9408},
{7828,26,9543},
{7953,27,9693},
{7993,28,9651},
{8021,29,9685},
{8168,1,9951},
{8250,3,9960},
{8284,4,10000},
{8318,5,10041},
{8437,1,10274},
{8534,3,10300},
{8565,4,10338},
{8599,5,10378},
{8723,7,10617},
{8787,9,10604},
{8812,11,10634},
{8850,13,10680},
{8906,15,10747},
{8968,17,10821},
{8996,19,10855},
{9025,21,10890},
{9312,22,11324},
{9340,25,11268},
{9375,26,11310},
{9406,27,11347},
{9546,1,11695},
{9631,2,11617},
{9737,5,11924},
{9850,7,12060},
{9971,7,12115},
{10003,9,12063},
{10040,11,12108},
{10102,13,12182},
{10134,15,12220},
{10212,17,12314},
{10243,19,12351},
{10287,21,12404},
{10362,23,12494},
{10478,24,12723},
{10518,25,12681},
{10687,1,13064},
{10802,3,13082},
{10924,5,13348},
{10975,6,13230},
{11043,7,13311},
{11078,8,13353},
{11159,9,13450},
{11196,10,13495},
{11249,11,13558},
{11337,12,13664},
{11368,13,13701},
{11400,14,13740},
{11431,15,13777},
{11465,16,13818},
{11499,17,13858},
{11528,18,13893},
{11562,19,13934},
{11590,20,13968},
{11618,21,14001},
{11652,22,14042},
{11677,23,14072},
{11712,24,14114},
{11743,25,14151},
{11775,26,14190},
{11800,27,14220},
{11824,28,14248},
{11853,29,14283},
{11887,30,14324},
{11940,31,14388},
{11999,1,14638},
{12062,2,14594},
{12115,3,14658},
{12176,4,14731},
{12234,5,14740},
{12293,6,14811},
{12350,7,14880},
{12412,8,14954},
{12470,9,15024},
{12534,10,15100},
{12595,11,15174},
{12653,12,15243},
{12712,13,15314},
{12768,14,15381},
{12831,15,15457},
{12890,16,15528},
{12952,17,15602},
{12987,18,15644},
{13065,19,15738},
{13096,20,15775},
{13180,17,15876},
{13218,18,15921},
{13275,19,15990},
{13333,20,16059},
{13396,17,16135},
{13446,18,16195},
{13481,19,16237},
{13506,20,16267},
{13543,17,16311},
{13599,18,16378},
{13628,19,16413},
{13655,20,16446},
{13687,17,16484},
{13718,18,16521},
{13750,19,16560},
{13781,20,16597},
{13812,20,16634},
{13840,19,16668},
{13874,18,16708},
{13906,17,16747},
{13937,16,16784},
{13968,15,16821},
{13999,14,16858},
{14031,13,16897},
{14062,12,16934},
{14096,11,16975},
{14130,10,17016},
{14162,9,17054},
{14193,8,17091},
{14227,7,17132},
{14259,6,17170},
{14290,5,17208},
{14321,4,17245},
{14352,3,17282},
{14418,2,17361},
{14490,1,17448},
{14555,2,17526},
{14611,3,17593},
{14671,4,17665},
{14725,5,17730},
{14818,6,17841},
{14852,7,17882},
{14874,8,17908},
{14912,9,17954},
{14984,10,18040},
{15052,11,18122},
{15112,12,18194},
{15221,13,18505},
{15671,1,19045},
{15740,2,18948},
{15800,3,19020},
{15850,4,19260},
{15912,5,19154},
{15968,6,19221},
{16025,7,19470},
{16084,8,19360},
{16140,9,19428},
{16206,10,19687},
{16268,11,19581},
{16331,12,19657},
{16390,13,19908},
{16450,14,19800},
{16509,15,19870},
{16571,16,20125},
{16628,17,20013},
{16687,18,20084},
{16718,19,20301},
{16762,20,20174},
{16862,21,20354},
{16915,20,20358},
{16978,19,20433},
{17018,18,20481},
{17068,17,20541},
{17156,16,20647},
{17218,15,20721},
{17275,14,20790},
{17334,13,20860},
{17396,12,20935},
{17456,11,21007},
{17506,10,21067},
{17556,9,21127},
{17621,8,21205},
{17659,7,21250},
{17715,6,21318},
{17815,5,21618},
{17856,6,21487},
{17899,7,21538},
{17990,8,21708},
{18018,9,21681},
{18056,10,21727},
{18087,11,21764},
{18115,12,21798},
{18150,13,21840},
{18181,14,21877},
{18209,15,21910},
{18243,16,21951},
{18265,17,21978},
{18303,18,22023},
{18328,19,22053},
{18415,20,22218},
{18474,21,22258},
{18531,22,22327},
{18593,23,22401},
{18650,24,22470},
{18709,25,22540},
{18765,26,22608},
{18796,27,22615},
{18825,28,22650},
{18856,29,22687},
{18887,30,22724},
{18915,31,22758},
{18946,30,22795},
{18974,29,22828},
{19006,28,22867},
{19037,27,22904},
{19068,26,22941},
{19096,25,22975},
{19128,24,23013},
{19156,23,23047},
{19184,22,23080},
{19215,21,23118},
{19246,20,23155},
{19275,19,23190},
{19303,18,23223},
{19334,17,23260},
{19362,16,23294},
{19393,15,23331},
{19424,14,23368},
{19462,13,23414},
{19534,12,23500},
{19596,1,23755},
{19659,3,23710},
{19721,5,23785},
{19784,7,23860},
{19843,9,23931},
{19903,11,24003},
{19962,13,24074},
{20024,15,24148},
{20081,17,24217},
{20131,19,24277},
{20193,21,24351},
{20253,23,24423},
{20312,25,24494},
{20349,27,24478},
{20396,29,24535},
{20434,30,24580},
{20462,31,24614},
{20490,30,24648},
{20521,29,24685},
{20581,28,24757},
{20643,27,24831},
{20681,26,24877},
{20709,25,24910},
{20734,24,24940},
{20762,23,24974},
{20790,22,25008},
{20818,21,25041},
{20846,20,25075},
{20878,19,25113},
{20909,18,25150},
{20940,17,25188},
{20965,16,25218},
{20996,15,25255},
{21021,14,25285},
{21056,13,25327},
{21084,12,25360},
{21121,11,25405},
{21146,10,25435},
{21178,9,25473},
{21206,8,25507},
{21237,7,25544},
{21265,6,25578},
{21293,5,25611},
{21325,4,25650},
{21381,3,25777},
{21440,2,25848},
{21500,1,25920},
{21556,3,25987},
{21621,5,26065},
{21678,7,26133},
{21737,9,26204},
{21796,11,26275},
{21856,13,26347},
{21915,15,26418},
{21975,17,26490},
{22031,19,26557},
{22090,21,26628},
{22155,23,26706},
{22215,25,26778},
{22271,27,26845},
{22328,29,26913},
{22381,31,26977},
{22409,30,26950},
{22437,29,26984},
{22465,28,27018},
{22487,27,27044},
{22512,26,27074},
{22540,25,27108},
{22571,24,27145},
{22596,23,27175},
{22625,22,27210},
{22656,21,27247},
{22728,19,27393},
{22799,17,27478},
{22834,16,27460},
{22915,14,27618},
{22956,13,27607},
{23028,10,27753},
{23196,4,28075},
{23246,2,28015}
},
{
{361,1,553},
{678,3,1053},
{1008,5,1449},
{1343,7,1851},
{1384,9,1780},
{1428,11,1833},
{1471,13,1885},
{1515,15,1938},
{1558,17,1989},
{1599,19,2038},
{1637,21,2084},
{1684,23,2140},
{1725,25,2190},
{1767,27,2240},
{1809,29,2290},
{1850,31,2340},
{1887,29,2384},
{1930,27,2436},
{1952,26,2402},
{1975,25,2430},
{2012,24,2534},
{2055,23,2586},
{2102,21,2642},
{2137,19,2684},
{2180,17,2736},
{2222,15,2786},
{2259,13,2830},
{2280,12,2796},
{2305,11,2826},
{2345,10,2934},
{2387,8,2984},
{2431,6,3037},
{2472,4,3086},
{2514,2,3136},
{2556,4,3187},
{2597,6,3236},
{2640,8,3288},
{2686,10,3343},
{2756,11,3427},
{2843,13,3531},
{2927,15,3632},
{3006,17,3727},
{3089,19,3826},
{3178,21,3933},
{3252,23,4022},
{3340,25,4128},
{3418,27,4221},
{3502,29,4322},
{3587,31,4424},
{3668,29,4521},
{3690,28,4488},
{3715,27,4518},
{3758,26,4629},
{3815,24,4698},
{3875,22,4770},
{3947,20,4856},
{4011,18,4933},
{4052,16,4982},
{4099,14,5038},
{4143,12,5091},
{4187,10,5144},
{4237,8,5204},
{4277,6,5252},
{4322,4,5306},
{4339,3,5266},
{4378,2,5313},
{4396,1,5335},
{4414,2,5356},
{4431,3,5377},
{4468,5,5421},
{4511,7,5473},
{4525,8,5490},
{4540,9,5508},
{4561,10,5533},
{4578,11,5553},
{4599,12,5578},
{4637,13,5684},
{4681,15,5737},
{4742,17,5810},
{4803,19,5883},
{4861,21,5953},
{4928,23,6033},
{4997,25,6116},
{5012,24,6074},
{5049,23,6118},
{5068,22,6141},
{5097,21,6176},
{5137,19,6224},
{5180,17,6276},
{5195,16,6294},
{5217,15,6320},
{5249,14,6358},
{5293,13,6411},
{5340,11,6468},
{5380,9,6516},
{5430,7,6576},
{5474,5,6628},
{5518,3,6681},
{5561,1,6733},
{5599,3,6778},
{5614,4,6796},
{5640,5,6828},
{5681,6,6877},
{5722,7,6926},
{5764,9,6976},
{5806,11,7027},
{5849,13,7078},
{5890,15,7128},
{5933,17,7179},
{5949,18,7198},
{5974,19,7228},
{6011,20,7273},
{6058,22,7329},
{6099,24,7378},
{6142,26,7430},
{6186,28,7483},
{6225,30,7530},
{6262,28,7574},
{6278,26,7593},
{6305,25,7626},
{6340,24,7668},
{6386,23,7723},
{6430,21,7776},
{6468,19,7821},
{6508,17,7869},
{6550,15,7920},
{6593,13,7971},
{6611,14,7993},
{6631,15,8017},
{6680,16,8076},
{6758,18,8289},
{6849,20,8398},
{6930,24,8496},
{7022,28,8606},
{7103,24,8703},
{7186,20,8803},
{7265,16,8898},
{7342,12,8990},
{7420,8,9084},
{7503,4,9183},
{7580,8,9276},
{7665,12,9378},
{7747,16,9476},
{7827,20,9572},
{7903,24,9663},
{8012,28,9794},
{8058,30,9729},
{8100,28,9780},
{8147,26,9836},
{8190,24,9888},
{8240,22,9948},
{8283,20,9999},
{8324,18,10048},
{8339,17,10042},
{8355,16,10062},
{8386,15,10123},
{8428,13,10173},
{8465,11,10218},
{8508,9,10269},
{8524,8,10264},
{8543,7,10287},
{8564,6,10312},
{8602,5,10382},
{8634,3,10420},
{8677,1,10472},
{8743,3,10551},
{8800,5,10620},
{8861,7,10693},
{8922,9,10766},
{8987,11,10844},
{9003,12,10839},
{9047,13,10892},
{9062,14,10910},
{9097,15,10952},
{9134,17,10996},
{9175,18,11046},
{9190,19,11064},
{9217,20,11096},
{9237,21,11120},
{9256,22,11143},
{9300,24,11220},
{9342,26,11270},
{9384,28,11320},
{9428,30,11373},
{9468,28,11421},
{9511,26,11473},
{9553,24,11523},
{9596,22,11575},
{9611,21,11593},
{9637,20,11624},
{9683,19,11679},
{9722,17,11726},
{9767,15,11780},
{9811,13,11833},
{9852,11,11882},
{9890,9,11928},
{9928,7,11973},
{9946,6,11995},
{9962,5,12014},
{10008,4,12069},
{10050,2,12120},
{10092,4,12170},
{10137,6,12224},
{10180,8,12276},
{10222,10,12326},
{10262,12,12374},
{10280,13,12396},
{10305,14,12426},
{10347,15,12476},
{10389,17,12526},
{10434,19,12580},
{10474,21,12628},
{10515,23,12678},
{10556,25,12727},
{10597,27,12776},
{10614,28,12796},
{10637,29,12824},
{10681,30,12877},
{10743,28,12951},
{10802,26,13022},
{10859,24,13090},
{10924,22,13168},
{11006,20,13267},
{11049,18,13318},
{11064,17,13336},
{11093,16,13371},
{11134,15,13420},
{11177,13,13472},
{11199,12,13498},
{11215,11,13518},
{11230,10,13536},
{11262,9,13574},
{11300,8,13620},
{11343,7,13671},
{11403,5,13743},
{11464,3,13816},
{11518,1,13881},
{11586,3,13963},
{11671,5,14065},
{11686,6,14083},
{11711,7,14113},
{11736,8,14143},
{11755,9,14166},
{11800,10,14220},
{11842,13,14270},
{11858,14,14289},
{11887,15,14324},
{11909,16,14350},
{11930,17,14376},
{11970,18,14424},
{12009,20,14470},
{12055,22,14526},
{12097,24,14576},
{12146,26,14635},
{12190,28,14688},
{12349,30,14938},
{12505,26,15126},
{12674,22,15328},
{12836,18,15523},
{13139,10,16006},
{13500,2,16440},
{13543,3,16371},
{13678,5,16533},
{13722,7,16586},
{13847,9,16736},
{13889,11,16786},
{14017,13,16940},
{14058,15,16989},
{14183,17,17139},
{14221,19,17185},
{14353,21,17343},
{14390,23,17388},
{14517,25,17540},
{14558,27,17529},
{14602,29,17582},
{14640,31,17628},
{14680,29,17676},
{14722,27,17726},
{14759,25,17770},
{14800,23,17820},
{14931,21,17977},
{14967,19,18020},
{15090,17,18168},
{15128,15,18213},
{15259,13,18370},
{15277,12,18392},
{15302,11,18422},
{15425,10,18570},
{15464,9,18616},
{15603,8,18783},
{15636,7,18823},
{15683,6,18879},
{15805,5,19026},
{15843,4,19071},
{15887,3,19124},
{15930,2,19176},
{15971,1,19225},
{16011,2,19273},
{16028,3,19293},
{16053,4,19323},
{16071,5,19345},
{16086,6,19363},
{16131,8,19417},
{16175,10,19470},
{16217,12,19520},
{16264,14,19576},
{16305,16,19626},
{16342,18,19670},
{16387,20,19724},
{16428,22,19773},
{16449,23,19798},
{16472,24,19826},
{16512,25,19874},
{16555,27,19926},
{16600,29,19980},
{16639,27,20026},
{16684,25,20080},
{16724,23,20128},
{16762,21,20174},
{16781,19,20197},
{16802,20,20222},
{16847,18,20276},
{16890,16,20328},
{16933,14,20379},
{16975,12,20430},
{17018,10,20481},
{17056,8,20527},
{17096,6,20575},
{17114,5,20596},
{17140,4,20628},
{17178,3,20673},
{17221,5,20725},
{17265,7,20778},
{17306,9,20827},
{17349,11,20878},
{17392,13,20930},
{17433,15,20979},
{17449,16,20998},
{17478,17,21033},
{17522,19,21086},
{17584,17,21160},
{17649,15,21238},
{17703,13,21303},
{17758,11,21369},
{17852,9,21482},
{17883,8,21519},
{17902,7,21542},
{17933,8,21579},
{17972,9,21626},
{18009,8,21670},
{18025,7,21690},
{18050,6,21720},
{18081,7,21757},
{18133,8,21819},
{18177,9,21872},
{18242,11,21950},
{18306,13,22027},
{18367,15,22100},
{18422,17,22166},
{18489,19,22246},
{18508,20,22269},
{18552,19,22322},
{18597,18,22376},
{18636,20,22423},
{18677,24,22472},
{18692,23,22490},
{18712,22,22514},
{18759,20,22570},
{18805,25,22626},
{18847,15,22676}
},
{
{150,1,300},
{282,3,458},
{422,5,626},
{566,7,799},
{707,9,968},
{742,11,950},
{775,13,990},
{807,15,1028},
{845,17,1074},
{880,19,1116},
{916,21,1159},
{951,23,1201},
{991,25,1249},
{1060,27,1332},
{1133,29,1419},
{1201,31,1501},
{1273,29,1587},
{1342,27,1670},
{1411,25,1753},
{1442,23,1790},
{1478,21,1833},
{1511,19,1873},
{1551,17,1921},
{1626,15,2011},
{1698,13,2097},
{1772,11,2186},
{1841,9,2269},
{1911,7,2353},
{1976,5,2431},
{2011,3,2473},
{2044,1,2512},
{2082,3,2558},
{2117,5,2600},
{2189,7,2686},
{2222,9,2726},
{2258,11,2769},
{2325,13,2850},
{2403,15,2943},
{2475,17,3030},
{2545,19,3114},
{2614,21,3196},
{2682,23,3278},
{2728,25,3333},
{2786,27,3403},
{2891,29,3589},
{2930,31,3576},
{2964,29,3616},
{2998,27,3657},
{3032,25,3698},
{3072,23,3746},
{3107,21,3788},
{3139,19,3826},
{3173,17,3867},
{3205,15,3906},
{3247,13,3956},
{3319,11,4042},
{3388,9,4125},
{3455,7,4206},
{3491,5,4249},
{3526,3,4291},
{3560,1,4332},
{3595,3,4374},
{3632,5,4418},
{3667,7,4460},
{3697,9,4496},
{3738,11,4545},
{3772,13,4586},
{3810,15,4632},
{3841,17,4669},
{3885,19,4722},
{3955,21,4806},
{4030,23,4896},
{4100,25,4980},
{4170,27,5064},
{4238,29,5145},
{4273,31,5187},
{4311,29,5233},
{4342,27,5270},
{4383,25,5319},
{4451,23,5401},
{4526,21,5491},
{4593,19,5571},
{4623,17,5607},
{4661,15,5653},
{4719,13,5722},
{4735,11,5742},
{4763,9,5775},
{4825,7,5850},
{4944,5,6052},
{5017,3,6080},
{5086,1,6163},
{5151,3,6241},
{5217,5,6320},
{5288,7,6405},
{5323,9,6447},
{5361,11,6493},
{5433,13,6579},
{5510,15,6672},
{5544,17,6712},
{5582,19,6758},
{5611,21,6793},
{5648,23,6837},
{5744,25,6952},
{5853,27,7143},
{5930,29,7176},
{5998,31,7257},
{6069,29,7342},
{6136,27,7423},
{6210,25,7512},
{6364,23,7756},
{6497,21,7916},
{6638,19,8085},
{6672,17,8066},
{6705,15,8106},
{6738,13,8145},
{6776,11,8191},
{6883,9,8379},
{6988,7,8505},
{7023,5,8487},
{7060,3,8532},
{7128,1,8613},
{7201,3,8701},
{7238,5,8745},
{7270,7,8784},
{7307,9,8828},
{7342,11,8870},
{7445,13,9054},
{7553,15,9183},
{7603,17,9183},
{7635,19,9222},
{7698,21,9297},
{7761,23,9373},
{7826,25,9451},
{7857,27,9488},
{7900,29,9540},
{7980,31,9636},
{8011,29,9673},
{8048,27,9717},
{8085,25,9762},
{8120,23,9804},
{8153,21,9843},
{8191,19,9889},
{8225,17,9930},
{8261,15,9973},
{8294,13,10012},
{8335,11,10062},
{8403,9,10143},
{8475,7,10230},
{8544,5,10312},
{8576,3,10351},
{8614,1,10396},
{8648,3,10437},
{8685,5,10482},
{8717,7,10520},
{8753,9,10563},
{8791,11,10609},
{8828,13,10653},
{8863,15,10695},
{8895,17,10734},
{8969,19,10822},
{9039,21,10906},
{9110,23,10992},
{9144,25,11032},
{9180,27,11076},
{9210,29,11112},
{9248,31,11157},
{9282,29,11198},
{9319,27,11242},
{9348,25,11277},
{9386,23,11323},
{9411,21,11353},
{9448,19,11397},
{9528,17,11493},
{9563,15,11535},
{9600,13,11580},
{9683,11,11679},
{9745,9,11754},
{9814,7,11836},
{9882,5,11918},
{9948,3,11997},
{10019,1,12082},
{10070,3,12144},
{10116,5,12199},
{10268,7,12621},
{10301,9,12661},
{10441,11,12829},
{10473,13,12867},
{10592,15,13010},
{10661,17,13093},
{10730,19,13176},
{10882,21,13358},
{10908,23,13389},
{11067,25,13580},
{11100,27,13620},
{11223,29,13767},
{11255,31,13806},
{11289,29,13846},
{11441,27,14029},
{11569,25,14182},
{11716,23,14359},
{11857,21,14528},
{11889,19,14326},
{11923,17,14367},
{11961,15,14413},
{11997,13,14456},
{12030,11,14496},
{12066,9,14539},
{12103,7,14583},
{12141,5,14629},
{12155,3,14646},
{12167,1,14660},
{12214,3,14716},
{12250,5,14760},
{12286,7,14803},
{12300,9,14820},
{12311,11,14833},
{12364,13,14896},
{12417,15,14960},
{12605,17,15246},
{12663,19,15315},
{12850,21,15540},
{12880,23,15516},
{12919,25,15562},
{12955,27,15606},
{12991,29,15649},
{13076,31,15751},
{13188,29,15945},
{13244,27,15952},
{13273,25,15987},
{13341,23,16069},
{13405,21,16146},
{13450,19,16200},
{13510,17,16272},
{13803,15,16683},
{13832,13,16658},
{13914,11,16756},
{13973,9,16827},
{14010,7,16872},
{14045,5,16914},
{14078,3,16953},
{14116,1,16999},
{14205,3,17106},
{14320,5,17304},
{14370,7,17304},
{14398,9,17337},
{14464,11,17416},
{14544,13,17512},
{14580,15,17556},
{14613,17,17595},
{14642,19,17630},
{14678,21,17673},
{14783,23,17859},
{14888,25,17985},
{14970,27,18084},
{15048,29,18177},
{15138,31,18285},
{15211,29,18373},
{15323,27,18507},
{15394,25,18592},
{15458,23,18669},
{15489,21,18676},
{15532,19,18728},
{15603,17,18813},
{15678,15,18903},
{15708,13,18939},
{15745,11,18984},
{15772,9,19016},
{15813,7,19065},
{15908,5,19179},
{16007,3,19298},
{16058,1,19359},
{16094,3,19402},
{16160,5,19482},
{16232,7,19568},
{16269,9,19612},
{16303,11,19653},
{16336,13,19693},
{16369,15,19732},
{16405,17,19776},
{16441,19,19819},
{16473,21,19857},
{16510,23,19902},
{16548,25,19947},
{16585,27,19992},
{16619,29,20032},
{16657,31,20078},
{16692,29,20120},
{16728,27,20163},
{16766,25,20209},
{16805,23,20256},
{16836,21,20293},
{16873,19,20337},
{16903,17,20373},
{16947,15,20426},
{17047,13,20576},
{17160,11,20712},
{17241,9,20809},
{17361,7,20953},
{17400,5,20940},
{17438,3,20985},
{17469,1,21022},
{17510,3,21072},
{17610,5,21252},
{17716,7,21379},
{17750,9,21360},
{17788,11,21405},
{17851,13,21481},
{17923,15,21567},
{17961,17,21613},
{17994,19,21652},
{18033,21,21699},
{18070,23,21744},
{18173,25,21927},
{18273,27,22047},
{18332,29,22058},
{18363,31,22095},
{18426,29,22171},
{18497,27,22256},
{18535,25,22302},
{18566,23,22339},
{18598,21,22377},
{18636,19,22423},
{18733,17,22599},
{18844,15,22732},
{18908,13,22809},
{18989,11,22906},
{19053,9,22983},
{19111,7,23053},
{19178,5,23133},
{19273,3,23247},
{19341,1,23329},
{19410,3,23412},
{19488,5,23505},
{19548,7,23577},
{19626,9,23671},
{19695,11,23754},
{19764,13,23836},
{19833,15,23919},
{19910,17,24012},
{19976,19,24091}
}
};
// queueBeat variables
unsigned int currentBeat[3];
int retrieveBeatCursor = 0;
int setBeatQueueCursor = 0;
/*
void queueBeat()
- responsible for retrieving beat information from progmem to the in-memory beat queue
- copies beat information from progmem to the beatQueue
- keeps track of cursor position within both the beatQueue and PROGMEM
- exits when PROGMEM has been exhausted (requires a exit beat to be reached)
- debug information: prints beat that was retrieved from progmem
*/
void queueBeat() {
// check that retrieval hasn't been turned off
if (retrieveBeatCursor == -1 || beatQueue[setBeatQueueCursor][0] != -1) {
return;
}
// copy beat from progmem to memory
memcpy_P(currentBeat, beatMaps[songIndex][retrieveBeatCursor], sizeof(currentBeat));
// access beat information information
unsigned int beatStartMs = currentBeat[0];
unsigned int beatDisplayPosition = currentBeat[1];
unsigned int beatExpiryMs = currentBeat[2];
// logging
Serial.print(F("retrieved beat: "));
Serial.print(beatStartMs);
Serial.print(F(" / "));
Serial.print(beatDisplayPosition);
Serial.print(F(" / "));
Serial.println(beatExpiryMs);
// check that ending beat hasn't been reached
if (retrieveBeatCursor > beatLength[songIndex]) {
retrieveBeatCursor = -1; // signal to exit
return;
}
retrieveBeatCursor++;
// queue information
beatQueue[setBeatQueueCursor][0] = beatStartMs;
beatQueue[setBeatQueueCursor][1] = beatDisplayPosition;
beatQueue[setBeatQueueCursor][2] = beatExpiryMs;
setBeatQueueCursor++;
// reset cursor if at end of queue
if (setBeatQueueCursor >= maxQueueDepth) {
setBeatQueueCursor = 0;
}
}
// beatLoop variables
int dequeueBeatCursor = 0;
int totalBeats = -1;
int expiredBeats = 0;
/*
void beatLoopProgmem()
- responsible for managing the display for beats
- checks if the currently displayed beats has expired and removes them from the display
- checks the beat queue to determine if its time to display a new beat
- debug information: prints position that beats were displayed or removed to serial
*/
void beatLoopProgmem() {
// loop through beatDisplay and check for expired beats
for (int beatIndex = 0; beatIndex < 32; beatIndex++) {
if (beatDisplay[beatIndex] == -1) {
continue;
}
if (millis() > (((long) beatDisplay[beatIndex]) * 10 + initTimeStamp)) {
updateDisplay(beatIndex, " ");
Serial.print("deleted beat ");
Serial.print(beatDisplay[beatIndex]);
Serial.print(" at ");
Serial.println(beatIndex);
Serial.print("currentTime ");
Serial.print(millis());
Serial.print(" and beatTime ");
Serial.println(beatDisplay[beatIndex]);
beatDisplay[beatIndex] = -1;
expiredBeats++;
}
}
if (beatQueue[dequeueBeatCursor][0] == -1) {
return;
}
// grab note from queue
unsigned int startMs = abs(beatQueue[dequeueBeatCursor][0]);
int displayPosition = abs(beatQueue[dequeueBeatCursor][1]);
unsigned int expiryMs = abs(beatQueue[dequeueBeatCursor][2]);
// check if time to play next beat and that beat is not empty
if (millis() > (((long) startMs) * 10 + initTimeStamp)) {
// free note queue
beatQueue[dequeueBeatCursor][0] = -1;
beatQueue[dequeueBeatCursor][1] = -1;
beatQueue[dequeueBeatCursor][2] = -1;
dequeueBeatCursor++;
// reset cursor if at end of queue
if (dequeueBeatCursor >= maxQueueDepth) {
dequeueBeatCursor = 0;
}
beatDisplay[displayPosition] = expiryMs;
updateDisplay(displayPosition, "X");
totalBeats++;
Serial.print(F("display x at "));
Serial.println(displayPosition);
}
}
int inTimeBeats;
/*
void gameLoop()
- responsible for game loop logic tied to the frame counter
- we force ourselves to use a frame counter
- DEBUG OPTION: die early at 30000ms total runtime
- handles input, clears the input counter every time it runs (it only runs every frame or so.)
*/
void gameLoop() {
if (debugEndingScreen && millis() > 30000) {
player.stopPlayback();
}
if (frameInput != 0) {
Serial.println(F("frame detected input"));
}
if (frameInput != 0) {
// check that there exists a beat
if (beatDisplay[cursorPos] != -1) {
// must be displayed beat
// update display
updateCursorPosition(cursorPos); // ensure that its there
lcd_1.print(" ");
updateCursorPosition(cursorPos); // ENSURE THAT ITS THERE
// remove from display queue
beatDisplay[cursorPos] = -1;
// increment beat score
inTimeBeats++;
}
}
}
const int frameTime = 1000 / commonFPS;
long lastFrameTimestamp = 0;
long frames = 0;
int beatAccuracy = 0;
/*
void loop()
- arduino function that is responsible for main looping operations
- calls other functions such as queueBeat(), beatLoopProgmem(), inputLoop(), and gameLoop()
- checks the time elapsed since the last frame and updates the game state
- exits game when song has stopped playing
*/
void loop() {
queueBeat();
beatLoopProgmem();
inputLoop();
long currentTime = millis();
if ((currentTime - lastFrameTimestamp) > frameTime) {
// Serial.println(frames);
lastFrameTimestamp = millis();
frames++;
gameLoop();
frameInput = 0;
}
if (!player.isPlaying()) {
beatAccuracy = ceil((double) inTimeBeats / (double) totalBeats * 100);
while (1) {
exitMenu();
};
}
}
// exitMenu variables
const char ranks[] PROGMEM = {'F', 'D', 'C', 'B', 'A', 'S'};
int exitMenuCounter = 0;
/*
void exitMenu()
- responsible for displaying the statistics of the player at the end of the game
- uses a switch and counter statement to determine which statistic to display
- each screen is displayed for two seconds
- ensures that counter does not point to an non existent command
*/
void exitMenu() {
lcd_1.clear();
// sort of unnecessary switch statement, but imagine it's just like a dictionary to do actions
// i hope its more readable
switch (exitMenuCounter) {
case 0:
lcd_1.print(F("Beats Hit: "));
break;
case 1:
lcd_1.println(F("Total Beats: "));
break;
case 2:
lcd_1.println(F("Missed Beats: "));
break;
case 3:
lcd_1.print(F("Accuracy: "));
break;
case 4:
lcd_1.print(F("Rank: "));
break;
case 5:
lcd_1.print(F("Play again by"));
break;
}
lcd_1.setCursor(0, 1);
switch (exitMenuCounter) {
case 0:
lcd_1.print(inTimeBeats);
break;
case 1:
lcd_1.print(totalBeats);
break;
case 2:
lcd_1.print(expiredBeats);
break;
case 3:
lcd_1.print(beatAccuracy);
break;
case 4:
lcd_1.print(ranks[map(beatAccuracy, 0, 100, 0, 5)]);
break;
case 5:
lcd_1.print(F("resetting game!"));
break;
}
delay(2000);
exitMenuCounter++;
if (exitMenuCounter > 5) {
exitMenuCounter = 0;
}
}
/*
void updateDisplay(int displayIndex, String displayValue)
- combines two commands into one function
- asks later defined function to update the cursor position
- prints the displayValue to the lcd display
*/
void updateDisplay(int displayIndex, String displayString) {
updateCursorPosition(displayIndex);
lcd_1.print(displayString);
}
/*
void updateCursorPosition(int displayIndex)
- responsible for moving the cursor to the correct position on the lcd display when provided with a its index
- calculates the row by dividing by 16
- calculates the column by subtracting the row from the displayIndex
- updates the cursor position
*/
void updateCursorPosition(int displayIndex) {
// integer divide to find column
int lcdRow = displayIndex / 16;
int lcdCol = displayIndex - (lcdRow * 16);
lcd_1.setCursor(lcdCol, lcdRow);
}