How the Caroler Works

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.