/*
@name: Jessie Finley
@description: Write a game that challenges the user to guess a song played using a buzzer and lightshow - Level 5
@date: November 3rd, 2022
*/
// Macros
#define ArraySize(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
//Constants
#define NUM_SONGS 5
// Constants: pins
#define PIN_RED_RGB_1 9
#define PIN_GREEN_RGB_1 10
#define PIN_BLUE_RGB_1 11
#define PIN_RED_RGB_2 4
#define PIN_GREEN_RGB_2 5
#define PIN_BLUE_RGB_2 6
#define PIN_WHITE_1 8
#define PIN_WHITE_2 12
#define PIN_YELLOW 13
#define PIN_ORANGE 7
#define PIN_BUTTON 2
#define PIN_BUZZER 3
// Constants: Music notes
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_DF1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_EF1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_GF1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_AF1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_BF1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_DF2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_EF2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_GF2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_AF2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_BF2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_DF3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_EF3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_GF3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_AF3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_BF3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_DF4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_EF4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_GF4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_AF4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_BF4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_DF5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_EF5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_GF5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_AF5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_BF5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_DF6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_EF6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_GF6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_AF6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_BF6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_DF7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_EF7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_GF7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_AF7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_BF7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_DF8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define NOTE_EF8 4978
#define MIN_NOTE NOTE_C4
#define MAX_NOTE NOTE_B5
// Music ratios
#define NOTE_REST 0.00
#define NOTE_WHOLE 4.00
#define NOTE_HALF 2.00
#define NOTE_QUARTER 1.00
#define NOTE_EIGHTH 0.50
#define NOTE_SIXTEENTH 0.25
// A header definition to evaluate how much memory is remaining; checked during runtime when called
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
namespace Memory
{
/**
@name Free
@description Finds the end of the stack and the start of the heap and calculates the difference in their memory addresses
*/
word Free ()
{
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
return &top - __brkval;
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
};
// A header definition for a specialized LinkedList library to enable the SongList header definition below
#ifndef List
#define List
/**
@name Node
@description Creates a space to store pointers and data for a node in a linked list tree
@property data, data2 - Primary and secondary storage for each node
@property next, previous - Pointers that specify the where the next and previous neods in a linked list are located
@construct Sets defaults for all properties and allows for external input at creation
*/
struct Node
{
word data, data2;
Node* next;
Node* previous;
Node (word value = 0, word value2 = 0) { data = value; data2 = value2; next = nullptr; previous = nullptr; };
};
/**
@name LinkedList
@description Simple 2-storage linked list that allows for nodes to be added, returned, and set values
(Tail is currently not utilised in implentation.)
@property private head, tail - The beginning and end of the list, entry points into the list
@property provate buffer - A class-wide buffer pointer to help control pointer management
@construct Sets default values for list pointers
@destruct Deletes head, tail and buffer pointers
*/
class LinkedList
{
private:
// Properties
Node* head;
Node* buffer;
Node* tail;
/**
@name Search
@description Finds a node based on the position from the head pointer and reports if it exists
@param position - The position of the node in the list
@return bool - TRUE if the node existss and FALSE if it doesn't
*/
bool Search (byte position)
{
buffer = head;
byte Counter = 0;
while (buffer != nullptr)
{
if (Counter == position) { return true; }
buffer = buffer -> next;
Counter = Counter + 1;
}
return false;
}
public:
// Constructor and destructor
LinkedList () { head = nullptr; tail = nullptr; buffer = nullptr; }
//~LinkedList () { delete head; delete tail; delete buffer; }
/**
@name Add
@description Creates a new node with specified values and inserts it at the end of the list
@param value, value2 - The two main data points for the node set to data, data2
*/
void Add (word value, word value2)
{
buffer = new Node (value, value2);
if (head == nullptr) // If list is empty
{
head -> previous = nullptr;
head = buffer;
}
else if (tail == nullptr) // If list has 1 node
{
buffer -> previous = head;
tail = buffer;
head -> next = tail;
tail -> next = nullptr;
}
else // If list has 2 or more nodes
{
tail -> next = buffer;
buffer -> previous = tail;
tail = buffer;
tail -> next = nullptr;
}
buffer = nullptr;
}
/**
@name Value
@description Words the value of the specified node at provided position in the list
@param position - The index in the list to retrieve data from
@param datanum - The specified data otore to retrieve. 1 for data, 2 for data2
@return word - Returns the requested data in a 2 byte number
*/
word Value (byte position, byte datanum)
{
if (datanum == 1) { return Search (position) ? buffer -> data : pow (2, 16) - 1; }
else if (datanum == 2) { return Search (position) ? buffer -> data2 : pow (2, 16) - 1; }
}
/**
@name SetValue
@description Sets the specified data storage with the provided value in the provided index
@param position - The index of the node to alter
@param setdata - The value to set in the node
@param datanum - The specific data storage to set, 1 for data, 2 for data2
*/
void SetValue (byte position, word setdata, byte datanum)
{
if (datanum == 1 && Search (position)) { buffer -> data = setdata; buffer = nullptr; }
else if (datanum == 2 && Search (position)) { buffer -> data2 = setdata; buffer = nullptr; }
}
};
#endif // List
// A header definition containing a LED controller to setup a lightshow and a music player to play specific songs
#ifndef SongLights
#define SongLights
/**
@name Interrupter
@description A super class meeant to be used in conjuction with another class. Allows access to boolean flag to signal when to interrupt operation of an object
@property private interrupted - The boolean flag. TRUE to interrupt and FALSE to run
@construct Sets the flag to FALSE
*/
class Interrupter
{
private:
// Properties
bool interrupted;
public:
// Constructor
Interrupter () { interrupted = false; }
/**
@name Intrrupt
@description Signal the class to set the interrupted flag to TRUE
*/
void Interrupt () { interrupted = true; }
/**
@name IntrruptReset
@description Resets the interrupted flag back to a running state (FALSE)
*/
void InterruptReset () { interrupted = false; }
/**
@name Intrrupted
@description Reports if an object should be inerrupted
@return bool TRUE if the object should be interrupted and FALSE if it should be allowed to run
*/
bool Interrupted () { return interrupted; }
};
/**
@name Timer
@description A super class meant to setup an internal timer structure for objects to use to determine states.
Could also be used independently
@property start - The initial value of the timer when started record in milliseconds
@property stop - A snapshot taken of current milliseconds to be used later in the class to determine state
@construct Initialised start and stop to 0 to prevent false state reporting
*/
class Timer
{
protected:
// Properties
unsigned long start;
unsigned long stop;
public:
// Constructor
Timer () { start = 0; stop = 0; }
/**
@name StartTimer
@description Records the current runtime in milliseconds to start and sets stop to start to prevent false state reporting
*/
void StartTimer () { start = millis (); stop = start; }
/**
@name StopTimer
@description Records a snapshot in milliseconds of the current runtime and assigns it to stop
*/
void StopTimer() { stop = millis (); }
/**
@name ResetTimer
@description Resets start and/or stop to 0 based on provided flags
@param resetstart, resetstop - Boolean flags for start and stop reset actions
*/
void ResetTimer (bool resetstart, bool resetstop) { if (resetstart) { start = 0; } if (resetstop) { stop = 0; } }
/**
@name ElaspedTimer
@description Returns the difference in milliseconds from the stop snapshot and the record start
@return unsigned long Returns the difference between stop and start as a positive 8 byte number
*/
unsigned long ElaspedTimer () { return stop - start; }
};
/**
@name Song
@description Stores and allows for control of notes to a monotone song using Arduino's tone() library
@inherit Interrupter, Timer - Used to help controol interruption and provide access to a millisecond timer
@param title - The title of the song
@param note - A linked list with 2-storage for data to store the notes of the song. data is used for frequency, and data2 for duration.
@param tempo - Beats per minute for each note
@param transition - A small beats per minute interval to pause between notes to help prevent slurring
@param notenum - The total number of notes store in the song object incremented when a new note is added to the note list
@param noteplaed - Boolean flag to report if the current note has been played. TRUE if the note is done playing and FALSE if the song should move on to the next note
@constructor Without any initializers provided sets the basic properties to default values to prevent errors. Else, setup the song with provided initializers.
*/
class Song : public Interrupter, public Timer
{
private:
// Properties
String title;
LinkedList note;
word tempo;
byte transition;
byte notenum;
bool noteplayed;
public:
// Constructors
Song () { title = "Placeholder"; notenum = 0; noteplayed = true; tempo - 0; transition = 0; }
Song (String settitle, word settempo, byte settransition) { title = settitle; SetTempo (settempo); transition = settransition; }
/**
@name Title
@description Returns the title of the song as an Arduino String object
@return String
*/
String Title () { return title; }
/**
@name SetTempo
@description Sets the tempo of the song with the specified value in beats per minute and converts it to milliseconds
@param value - The beats per minute value to set the tempo as
*/
void SetTempo (word value) { tempo = 60 * 1000 / value; }
/**
@name Tempo
@description Returns the tempo of the song as a 2 byte number
@return word
*/
word Tempo () { return tempo; }
/**
@name SetTransition
@description Sets the transition of the song with the specified value in beats per minute and converts it to milliseconds
@param value - The beats per minute value to set the transition as
*/
void SetTransition (word value) { transition = value; }
/**
@name Transition
@description Returns the transition of the song as a 2 byte number
@return word
*/
word Transition () { return transition; }
/**
@name AddNote
@description Create a new node node in the note list and sets its frequency and duration
@param frequency - The frequency of the note provided in hertz
@param duration - The duration of the note provided as a float between 0 and 4 inclusive. Multiplied by the tempo of the song and offset by the note transition
*/
void AddNote (word frequency, float duration)
{
note.Add (frequency, duration * Tempo () - transition);
notenum = notenum + 1;
}
/**
@name NextNote
@description Signals the Song object to play the next note in the list and resets internal timers
*/
void NextNote () { ResetTimer (true, true); noteplayed = false; }
/**
@name NoteFrequency
@description Returns the frequency of a specified note at the provided node index
@param position - The index of the node for the requested note
@return word The frequency of the note provided as a 2 byte number (data)
*/
word NoteFrequency (byte position) { return note.Value (position, 1); }
/**
@name NoteDuration
@description Returns the duration of a specified note at the provided node index
@param position - The index of the node for the requested note
@return word The duration of the note provided as a 2 byte number (data2)
*/
word NoteDuration (byte position) { return note.Value (position, 2); }
/**
@name NoteNum
@description Returns the total number of notes in the song
@return byte The total number of notes
*/
byte NoteNum () { return notenum; }
/**
@name NotePlay
@description Main procedure that handles the playing of the song. Handles interrupts and internal timers.
@param buzzer - The speaker pin to play song notes on using Arduinos's tone() library. Provided per note to allow for dynamic speaker changing.
@param position - The index in the note list to find the note to play provided as a 1 byte number
*/
void NotePlay (byte buzzer, byte position)
{
// Varriables: buffers
word BufferFrequency = NoteFrequency (position);
word BufferDuration = NoteDuration (position);
// Intrrupt check
if (!Interrupted ())
{
if (stop == 0)
{
if (BufferFrequency != NOTE_REST) { tone (buzzer, BufferFrequency); }
StartTimer ();
}
// Internal timer control
StopTimer ();
if (ElaspedTimer () >= BufferDuration) { noTone (buzzer); }
if (ElaspedTimer () >= BufferDuration + Transition ()) { ResetTimer (true, true); noteplayed = true; }
}
}
/**
@name NotePlayed
@description Reports if the current notes has been played
@return bool TRUE if the song has been played and FALSE if the next note can be played
*/
bool NotePlayed () { return noteplayed; }
};
/**
@name LED
@description Controls LED states and data using Arduino's analogWrite() to provided pins. Supports single LED's, RGB LED's, or a list and combination of both.
Oragnization of LED list needs to be implemented externally, currently.
@inherit Interrupter, Timer - Used to help controol interruption and provide access to a millisecond timer
@param pins - A list of 2-storage data nodes used to store the pin values (data) and light values (data2) for the LED
@param duration - The duration that the LED should stay on in milliseconds for before it stays off unlessed told otherwise
@param blinkon, blinkoff - The on and off intervals to blink the LED on or off for
@param lit - Boolean flag to determine if the LED is emitting light
@param changed - Boolean flag to determine whether to perform a write to the LED pin
@constructor Without any initializers provided sets the basic properties to default values to prevent errors. Else, setup the song with user defined list of pins to use
*/
class LED : public Timer, public Interrupter
{
private:
// Properties
LinkedList pins;
word duration;
word blinkon, blinkoff;
bool lit;
bool changed;
/**
@name Write
@description A general write procedure that uses the objects properties to determine what to send the LED pin. Controls LED interrupts.
LED pin will be written to when the Update() procedure detects a change in LED state and data.
*/
void Write ()
{
// Variables
byte Counter = 0; // Used to search for pins
while (!Interrupted () && pins.Value (Counter, 1) != pow (2, 16) - 1)
{
if (Lit ()) { analogWrite (pins.Value (Counter, 1), pins.Value (Counter, 2)); }
else { analogWrite (pins.Value (Counter, 1), 0); }
Counter = Counter + 1;
}
}
public:
// Constructors
LED () { lit = false; duration = 0; changed = false; }
// @param num - The number of LED pins that are about to be passed. Used for arguement retrieval
LED (byte num,...)
{
va_list pinlist;
va_start (pinlist, num);
for (byte i = 0; i < num; i++)
{
pins.Add (va_arg (pinlist, word), 0);
pinMode (pins.Value (i, 1), OUTPUT);
}
va_end (pinlist);
}
/**
@name On
@description Sets the LED object state to on and starts the internal timer. Changed is flipped to TRUE to help with write updates
*/
void On () { lit = true; changed = true; StartTimer (); }
/**
@name Off
@description Sets the LED object state to off and resets the internal timer. Changed is flipped to TRUE to help with write updates
*/
void Off ()
{
lit = false;
changed = true;
ResetTimer (true, true);
}
/**
@name On
@description Reports if the LED is currently emitting light
@return bool - TRUE if the LED is emitting light and FALSE if it is not
*/
bool Lit () { return lit; }
/**
@name Duration
@description Returns the duration that the LED should stay on for in milliseconds
@return word - 2 byte number
*/
word Duration () { return duration; }
/**
@name SetDuration
@description Sets the dauration that the light should be remain on for in milliseconds. Once this interval has been reached the LED will stay off unless told otherwise
@param value - The value to set the duration as provided by a 2 byte number. Flips changed to TRUE to help with write updates
*/
void SetDuration (word value) { duration = value; changed = true; }
/**
@name Clear
@description Sets the light value of the LED pin(s) to 0 and flips changed to TRUE to help with write updates
*/
void Clear ()
{
// Varriables
byte Counter = 0; // Used to access pin nodes
while (pins.Value (Counter, 2) != pow (2, 16) - 1)
{
pins.SetValue (Counter, 0, 2);
Counter = Counter + 1;
}
changed = true;
}
/**
@name Light
@description Sets the individual light levels for each LED pin in a user provided list. List order should be the same as when constructed.
Flips changed to TRUE to help with write updates
@param num - The number of LED pins that are about to be passed. Used for arguement retrieval
*/
void Light (byte num,...)
{
va_list channellist;
va_start (channellist, num);
for (byte i = 0; i < num; i++)
{
pins.SetValue (i, va_arg (channellist, word), 2);
}
va_end (channellist);
changed = true;
}
/**
@name SetBlink
@description Setup a blinking interval for the LED to turn on and off periodically. Flips changed to TRUE to help with write updates
@param oninterval - How link the LED should be on for during a blink as a 2 byte number in milliseconds
@param offinterval - How link the LED should be off for during a blink as a 2 byte number in milliseconds
*/
void SetBlink (word oninterval, word offinterval) { blinkon = oninterval; blinkoff = offinterval; changed = true; }
/**
@name Update
@description Main control procedure of the LED object. Controls pin writes, interrupts and internal timers
*/
void Update ()
{
// Controls when new data needs to be written to the LED pin(s)
if (changed) { Write (); changed = false; return; }
// Interrupt check
if (!Interrupted ())
{
// Timer control based on provided duration or blink intervals
if (blinkon > 0 && blinkoff > 0) // Blink check (dynamic)
{
StopTimer ();
if (Lit () & ElaspedTimer () >= blinkon) { Off (); StartTimer (); }
else if (!Lit () & ElaspedTimer () >= blinkoff) { On (); }
}
else if (Duration () > 0 && Lit ()) // Amount of time the LED should be on for (static)
{
StopTimer ();
if (ElaspedTimer () >= Duration ()) { Off (); }
}
}
}
};
#endif // End Songlights
// Isolate variables and methods to allow for alternate lightshows and playlists (future proof)
namespace SongPlayer
{
// Variables
String UserResponse;
//Variables: song playing
Song PLAYLIST[NUM_SONGS];
Song* RequestedSong;
byte NotePosition;
bool PlayingFlag; // Song play flag
word ToneColour[3]; // Conversion from frequency to RGB colour
// Variables: guess game
byte SongOrder[NUM_SONGS];
byte RandomSong;
byte GuessScore = 0;
bool QuitFlag; // Button interrupt
// Objects
Timer GuessTime;
LED RGBLED1;
LED RGBLED2;
LED WhiteLED1;
LED WhiteLED2;
LED YellowLED;
LED OrangeLED;
// Interrupt LED's and set buzzer to output no tone
void Guess ()
{
QuitFlag = true;
RGBLED1.Interrupt ();
RGBLED2.Interrupt ();
WhiteLED1.Interrupt ();
WhiteLED2.Interrupt ();
YellowLED.Interrupt ();
OrangeLED.Interrupt ();
RequestedSong -> Interrupt ();
noTone (PIN_BUZZER);
}
// Reset all LED's and volatile variables to initial values
void Reset ()
{
QuitFlag = false;
PlayingFlag - false;
RGBLED1.InterruptReset ();
RGBLED2.InterruptReset ();
WhiteLED1.InterruptReset ();
WhiteLED2.InterruptReset ();
YellowLED.InterruptReset ();
OrangeLED.InterruptReset ();
RequestedSong -> InterruptReset ();
RGBLED1.Off ();
RGBLED2.Off ();
WhiteLED1.Off ();
WhiteLED2.Off ();
YellowLED.Off ();
OrangeLED.Off ();
RGBLED1.Update ();
RGBLED2.Update ();
WhiteLED1.Update ();
WhiteLED2.Update ();
YellowLED.Update ();
OrangeLED.Update ();
YellowLED.On ();
UserResponse = "";
RequestedSong = nullptr;
NotePosition = 0;
noTone (PIN_BUZZER);
}
// Initialize and setup SongPlayer variables and objects
void Setup ()
{
// Initialize variables
UserResponse = "";
QuitFlag = false;
PlayingFlag = false;
RequestedSong = nullptr;
NotePosition = 0;
for (byte i = 0; i < ArraySize (SongOrder); i++) { SongOrder[i] = 255; }
// Initialize objects
GuessTime = Timer ();
RGBLED1 = LED (3, PIN_RED_RGB_1, PIN_GREEN_RGB_1, PIN_BLUE_RGB_1);
RGBLED2 = LED (3, PIN_RED_RGB_2, PIN_GREEN_RGB_2, PIN_BLUE_RGB_2);
WhiteLED1 = LED (1, PIN_WHITE_1);
WhiteLED2 = LED (1, PIN_WHITE_2);
YellowLED = LED (1, PIN_YELLOW);
OrangeLED = LED (1, PIN_ORANGE);
// Setup LED's
WhiteLED1.Light (1, 255);
WhiteLED2.Light (1, 255);
YellowLED.Light (1, 255);
OrangeLED.Light (1, 255);
//Serial.print ("Young Glass: "); Serial.println(Memory::Free ());
// Implement Young Glass notes
PLAYLIST[0] = Song ("Young Glass", 280, 80);
PLAYLIST[0].AddNote (NOTE_GS4, NOTE_EIGHTH);
PLAYLIST[0].AddNote (NOTE_FS4, NOTE_EIGHTH);
PLAYLIST[0].AddNote (NOTE_GS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_GS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_B4, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_CS4, NOTE_WHOLE);
PLAYLIST[0].AddNote (NOTE_REST, NOTE_WHOLE);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_CS5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_REST, NOTE_HALF + NOTE_EIGHTH);
PLAYLIST[0].AddNote (NOTE_E5, NOTE_SIXTEENTH);
PLAYLIST[0].AddNote (NOTE_FS5, NOTE_SIXTEENTH);
PLAYLIST[0].AddNote (NOTE_GS5, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_E5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_CS5, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_GS4, NOTE_EIGHTH);
PLAYLIST[0].AddNote (NOTE_FS4, NOTE_EIGHTH);
PLAYLIST[0].AddNote (NOTE_GS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_GS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_B4, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_CS4, NOTE_WHOLE);
PLAYLIST[0].AddNote (NOTE_REST, NOTE_HALF + NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_FS5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_REST, NOTE_WHOLE + NOTE_HALF + NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_B4, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_GS5, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_CS5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E5, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_CS5, NOTE_HALF);
PLAYLIST[0].AddNote (NOTE_REST, NOTE_HALF + NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_E4, NOTE_QUARTER);
PLAYLIST[0].AddNote (NOTE_DS4, NOTE_HALF);
//Serial.print ("Say Hey: "); Serial.println(Memory::Free ());
// Implement Say Hey notes
PLAYLIST[1] = Song ("Say Hey", 90, 100);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_HALF + NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_G5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_REST, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_HALF);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_G5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_G5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_G5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_REST, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_G5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_C4, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_AS4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_AS5, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_C4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_REST, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_C4, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_AS4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_REST, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F4, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_F4, NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_E5, NOTE_QUARTER);
PLAYLIST[1].AddNote (NOTE_F5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[1].AddNote (NOTE_REST, NOTE_HALF);
//Serial.print ("Cherry Bomb: "); Serial.println(Memory::Free ());
// Implement Cherry Bomb notes
PLAYLIST[2] = Song ("Cherry Bomb", 100, 80);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_A4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_A4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_A4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_FS4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_A4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_A4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH + NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH + NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH + NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_D4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_C4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH + NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH + NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH + NOTE_SIXTEENTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_C4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B5, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[2].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[2].AddNote (NOTE_REST, NOTE_QUARTER);
//Serial.print ("Top of the World: "); Serial.println(Memory::Free ());
// Implement Top of the World notes
PLAYLIST[3] = Song ("Top of the World", 160, 80);
PLAYLIST[3].AddNote (NOTE_A4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_C4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_D4, NOTE_HALF);
PLAYLIST[3].AddNote (NOTE_D4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_D4, NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_DS4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_D4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_C4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_G4, NOTE_HALF);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_C4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_G4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_F4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_D4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_C4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_A4, NOTE_HALF);
PLAYLIST[3].AddNote (NOTE_G4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_A4, NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_C4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_REST, NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_FS4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[3].AddNote (NOTE_FS4, NOTE_EIGHTH + NOTE_HALF);
PLAYLIST[3].AddNote (NOTE_C4, NOTE_HALF);
PLAYLIST[3].AddNote (NOTE_AS4, NOTE_WHOLE * 3 + NOTE_QUARTER);
PLAYLIST[3].AddNote (NOTE_REST, NOTE_QUARTER + NOTE_HALF);
//Serial.print ("Lanterns: "); Serial.println(Memory::Free ());
// Implement Lanterns notes
PLAYLIST[4] = Song ("Lanterns", 84 * 2, 80);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_A4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_A4, NOTE_HALF + NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_A4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_A4, NOTE_HALF + NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_G4, NOTE_WHOLE);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_HALF);
PLAYLIST[4].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_E5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER + NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_REST, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_E5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_D5, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_B4, NOTE_EIGHTH);
PLAYLIST[4].AddNote (NOTE_A4, NOTE_QUARTER);
PLAYLIST[4].AddNote (NOTE_REST, NOTE_QUARTER);
//Serial.print ("After Setup: "); Serial.println(Memory::Free ());
}
// Squish a frequency into a specific wave length and analyze it to convert it to a RGB colour
void ToneToColour (word frequency)
{
float Factor;
signed char Red, Green, Blue; // Color values in the range -1 to 1
word WaveLength = map (frequency, MIN_NOTE, MAX_NOTE, 350, 780);
if (WaveLength >= 350 && WaveLength < 440)
{
// From Purple (1, 0, 1) to Blue (0, 0, 1), with increasing intensity (set below)
Red = -(WaveLength - 440) / (440 - 350);
Green = 0.0;
Blue = 1.0;
}
else if (WaveLength >= 440 && WaveLength < 490)
{
// From Blue (0, 0, 1) to Cyan (0, 1, 1)
Red = 0.0;
Green = (WaveLength - 440) / (490 - 440);
Blue = 1.0;
}
else if (WaveLength >= 490 && WaveLength < 510)
{
// From Cyan (0, 1, 1) to Green (0, 1, 0)
Red = 0.0;
Green = 1.0;
Blue = -(WaveLength - 510) / (510 - 490);
}
else if (WaveLength >= 510 && WaveLength < 580)
{
// From Green (0, 1, 0) to Yellow (1, 1, 0)
Red = (WaveLength - 510) / (580 - 510);
Green = 1.0;
Blue = 0.0;
}
else if (WaveLength >= 580 && WaveLength < 645)
{
// From Yellow (1, 1, 0) to Red (1, 0, 0)
Red = 1.0;
Green = -(WaveLength - 645) / (645 - 580);
Blue = 0.0;
}
else if (WaveLength >= 645 && WaveLength <= 780)
{
// Solid Red (1, 0, 0), with decreasing intensity (set below)
Red = 1.0;
Green = 0.0;
Blue = 0.0;
}
else
{
Red = 0.0;
Green = 0.0;
Blue = 0.0;
}
// Intensity factor goes through the range:
// 0.1 (350-420 nm) 1.0 (420-645 nm) 1.0 (645-780 nm) 0.2
if (WaveLength >= 350 && WaveLength < 420) { Factor = 0.1 + 0.9*(WaveLength - 350) / (420 - 350); }
else if (WaveLength >= 420 && WaveLength < 645) { Factor = 1.0; }
else if (WaveLength >= 645 && WaveLength <= 780) { Factor = 0.2 + 0.8*(780 - WaveLength) / (780 - 645); }
else { Factor = 0.0; }
ToneColour[0] = Red == 0 ? 0 : round (255 * Red * Factor);
ToneColour[1] = Green == 0 ? 0 : round (255 * Green * Factor);
ToneColour[2] = Blue == 0 ? 0 : round (255 * Blue * Factor);
}
// Get a response from user and wait for it
String GetResponse ()
{
String Buffer = "";
while (Buffer == "")
{
while (Serial.available ())
{
delay (1);
if (Serial.available () > 0)
{
Buffer = Buffer + (char) Serial.read ();
}
}
}
Buffer.trim ();
Buffer.toLowerCase ();
return Buffer;
}
// Select a specific song to play through buzzer and RGB light
Song* SelectSong (String title)
{
for (byte i = 0; i < ArraySize (PLAYLIST); i++)
{
if (title.equalsIgnoreCase (PLAYLIST[i].Title ())) { return &PLAYLIST[i]; }
}
return nullptr;
}
// Play a specific Song object on a specified buzzer pin while updating lightshow to reflect the note
void PlaySong (Song* playing, byte buzzer)
{
if (!PlayingFlag) { return; }
if (playing -> NotePlayed ())
{
playing -> NextNote ();
NotePosition = NotePosition + 1;
if (NotePosition > playing -> NoteNum ()) { PlayingFlag = false; return; }
if (YellowLED.Lit ()) { YellowLED.Off (); OrangeLED.On (); }
else if (OrangeLED.Lit ()) { OrangeLED.Off (); YellowLED.On (); }
YellowLED.Update ();
OrangeLED.Update ();
WhiteLED1.Light (1, map (playing -> NoteFrequency (NotePosition), MIN_NOTE, MAX_NOTE, 0, 255));
WhiteLED2.Light (1, map (playing -> NoteFrequency (NotePosition), MIN_NOTE, MAX_NOTE, 255, 0));
ToneToColour (playing -> NoteFrequency(NotePosition));
RGBLED1.Light (3, ToneColour[0], ToneColour[1], ToneColour[2]);
RGBLED2.Light (3, ToneColour[2], ToneColour[0], ToneColour[1]);
RGBLED1.Update ();
RGBLED2.Update ();
}
WhiteLED1.Update ();
WhiteLED2.Update ();
playing -> NotePlay (PIN_BUZZER, NotePosition);
}
// Ask the user for the song they want to play and indicate that the song should be played
void StartSong ()
{
while (RequestedSong == nullptr)
{
Serial.println ("Please select a song to play.");
for (byte i = 0; i < ArraySize (PLAYLIST); i++)
{
Serial.println ("::: " + PLAYLIST[i].Title ());
}
UserResponse = GetResponse ();
RequestedSong = SelectSong (UserResponse);
if (RequestedSong == nullptr) { Serial.println ("Please select a valid song."); }
else { Serial.println ("Playing: " + RequestedSong -> Title ()); }
}
PlayingFlag = true;
WhiteLED1.SetBlink (RequestedSong -> Tempo () * 0.5, RequestedSong -> Tempo () * 1.5);
WhiteLED2.SetBlink (RequestedSong -> Tempo () * 0.5, RequestedSong -> Tempo () * 1.5);
WhiteLED1.On ();
WhiteLED2.On ();
RGBLED1.On ();
RGBLED2.On ();
YellowLED.On ();
}
// Start a guessing game with ther user and score them based on their guess
void GuessSong ()
{
// Randomly select a unique entry song order
for (byte i = 0; i < ArraySize (PLAYLIST); i++)
{
RandomSong = 255;
while (RandomSong == 255)
{
RandomSong = random (-1, ArraySize (PLAYLIST));
for (byte j = 0; j < ArraySize (PLAYLIST); j++)
{
if (RandomSong == SongOrder[j]) { RandomSong = 255; }
}
}
SongOrder[i] = RandomSong;
}
// Notify user of the guessing game
Serial.println ("All 5 songs in the playlist will be played in random order one after the other. Try to guess the name of the song and when you want to make a guess press the button.");
// Start guessing game
for (byte i = 0; i < ArraySize (SongOrder); i++)
{
Reset ();
// Notify user when the song will start
Serial.print ("Playing song "); Serial.print (i + 1); Serial.println (" in 5 seconds.");
delay (4800);
RequestedSong = SelectSong (PLAYLIST[SongOrder[i]].Title ());
PlayingFlag = true;
WhiteLED1.SetBlink (RequestedSong -> Tempo () * 2, RequestedSong -> Tempo ());
WhiteLED2.SetBlink (RequestedSong -> Tempo () * 2, RequestedSong -> Tempo ());
Serial.println ("Playing song...");
GuessTime.StartTimer ();
// Play song repeatedly until the user wants to guess
while (!QuitFlag)
{
if (PlayingFlag) { PlaySong (RequestedSong, PIN_BUZZER); }
else
{
GuessTime.StopTimer ();
Serial.print (GuessTime.ElaspedTimer () / 1000.00);
Serial.println ( " seconds has elasped. Playing song again...");
delay (150);
PlayingFlag = true;
NotePosition = 0;
}
}
// Evaluate users guess and display results
GuessTime.StopTimer ();
Serial.println ("What is your guess?");
UserResponse = GetResponse ();
Serial.print (RequestedSong -> Title ().equalsIgnoreCase (UserResponse) ? "Correct!" : "Incorrect.");
Serial.print (" You took "); Serial.print (GuessTime.ElaspedTimer () / 1000.00); Serial.println (" seconds to attempt a guess.");
GuessScore = GuessScore + (RequestedSong -> Title ().equalsIgnoreCase (UserResponse) ? 1 : 0);
PlayingFlag = false;
}
// Notify user of their score
Serial.print ("You scored "); Serial.print (GuessScore); Serial.println (" out of 5");
}
// Play a guess the song game with user
void Run ()
{
if (PlayingFlag) { PlaySong (RequestedSong, PIN_BUZZER); }
else
{
Reset ();
Serial.println ("Please select a mode.");
Serial.println ("::: Play Song");
Serial.println ("::: Guess Song");
UserResponse = GetResponse ();
if (UserResponse == "play song") { UserResponse = ""; StartSong (); }
else if (UserResponse == "guess song") { UserResponse = ""; GuessSong (); }
}
}
}
// Button interrupt
void Exit ()
{
SongPlayer::Guess ();
}
// Setup pins and begin serial
void setup ()
{
// Setup serial monitor and odd pins
Serial.begin (9600);
pinMode (PIN_BUZZER, OUTPUT);
pinMode (PIN_BUTTON, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), Exit, FALLING);
randomSeed(analogRead(0));
SongPlayer::Setup ();
}
// Play a song guessing game with the user
void loop ()
{
SongPlayer::Run ();
//delay (1000000);
}