At this point, we’ve got our songs fully coded, our circuit works, and everything is put together into one master program, CarolingDeviceWithLights.bs2. Here's a description of how the master program was built to use the PBASIC code for the songs with code for reading the 5-position switch, and code to blink the LEDs. You can follow this same process to customize your Caroler to play different songs.
Optimizing Space for the Music Code
By now you may have guessed that this program is rather large, and takes up a lot of EEPROM space. So it’s our job to make sure we make the most of what we have.
The first EEPROM-Eater is the space required to store the information needed to play all four songs. This requires four DATA directives for each song: one to store the notes, one to store the octaves, one to store the durations, and to store the dotted notes. Is there anything we can do to eliminate some of this space?
Why yes, there is! When looking at the sheet music for Jingle Bells and We Wish You a Merry Christmas, I noted that there were very few to no dotted notes. So instead of taking up EEPROM space to save 1-2 dotted notes in a whole song, why not just increase the value in the Durations DATA directive? It isn’t as accurate, but as long as it sounds OK, that’s what counts!
In order to further save memory, we extensively used subroutines so we’re not repeating the same lines of code unnecessarily. The main part of the program branches off to four separate subroutines to play four separate songs, based on which position on the 5-Position Switch is selected:
DO IF (IN0 = 0) THEN GOSUB Deck_The_Halls ELSEIF (IN1 = 0) THEN GOSUB Jingle_Bells ELSEIF (IN2 = 0) THEN GOSUB Merry_Christmas ELSEIF (IN4 = 0) THEN GOSUB Christmas_Tree ELSE PAUSE 20 ENDIF LOOP
Each song’s subroutine then has the same basic structure: READ the note letter, octave, duration, and dot notation from its appropriate location in EEPROM and then calculate the values needed for the BASIC Stamp’s FREQOUT command. Most of these calculations are carried out through the use of more subroutines to keep on savin’ that memory. For example, the LOOKUP and LOOKDOWN tables for calculating each note’s frequency are the same for each song, and therefore are placed in a subroutine named Get_Frequency:
Get_Frequency: LOOKDOWN noteLetter, [ "C", "d", "D", "e", "E", "F", "g", "G", "a", "A", "b", "B", "R", "Q" ], offset LOOKUP offset, [ 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902, 0, 0 ], noteFreq RETURN
Likewise, re-calculating a note's frequency so that it is in the proper octave is placed in a subroutine, named Get_Octave:
Get_Octave: noteOctave = 8 - noteOctave noteFreq = noteFreq / (DCD noteOctave) RETURN
Light Patterns
Next, we determined what the light pattern will be displayed while each song is playing. An easy way to do this is simply to assign certain LEDs to turn on when certain notes are being played. Since the notes in each song differ from one to the other, we can ensure that the pattern of lights will be different for each song all while calling the same subroutine, Display_Color. You may notice that the first part of this subroutine turns all of the LEDs off. This ensures that any LED that was on for the last note will be off for current note, creating a “blinking” effect each time!
Display_Color: ' Subroutine label LOW 15: LOW 14: LOW 13: LOW 12: LOW 11 ' Turn off all LEDs SELECT noteLetter CASE "A", "a" HIGH 15 CASE "B", "b" HIGH 14 CASE "C", "c" HIGH 13 CASE "D", "d" HIGH 12 CASE "E", "e" HIGH 11 CASE "F", "f" HIGH 15 HIGH 13 HIGH 11 CASE "G", "g" HIGH 14 HIGH 12 ENDSELECT RETURN
Multiple commands on the same line
Remember that a colon can replace a carriage return when coding in PBASIC. For example, in the subroutine above, we condense what would normally need five lines into one by using colons instead of carriage returns. So, we get the much neater:
LOW 15: LOW 14: LOW 13: LOW 12: LOW 11:
instead of:
LOW 15
LOW 14
LOW 13
LOW 12
LOW 11
When integrating song to the master program, there are two last things to consider: First, we reset the index variable to 0 before playing any song. This variable acts as a pointer to specific address locations in EEPROM, and if it wasn’t reset before each song was played, some songs might start playing from the middle, or some not at all!
Lastly, at the end of playing each song, we need to reset the value of noteLetter. This is because our songs will only play if noteLetter doesn’t equal “Q” or if the center position on the 5-Position Switch is not pressed. BUT every string of notes saved to EEPROM ends with “Q”, and if this is not reset, then the songs won’t play a second time. In the program CarolingDeviceWithLights.bs2, noteLetter is simply set to “R” at the end of each subroutine to prevent this from happening.