Prerequisite Modules
- Development Software Suite
- Version Control
- Embedded C and IAR Primer
- Firmware System
- LED Basic
- Button Interface
- Debug Interface (for Module Exercise)
In this module we build an application that will let us encode notes from sheet music to an array. The purpose is to show how to incorporate both audio and visual output in a more complicated program. Though you need to know very little about music for this exercise, the mathematical and sensory beauty of music might drive you to at least look at the basics of reading sheet music HERE. For this exercise, all you need to know is that music boils down to frequencies (notes) and the combination and duration of those notes.
To enable a reasonable solution to coding music, the application must allow us to define a note and the duration for which that note is played. This is very similar (if not identical) to the MIDI standard for encoding music. These will be captured in an array and then sequenced out to the buzzer. Since the EiE dev board has two buzzers each on their own channel, we can code two streams of notes (the left hand and right hand of the song if it were played on a piano). Our goal is to code the classic song “Heart and Soul.” The implementation is based on a simple version of the song shown here:
Coding the Sound
All code is written in UserApp1SM_Idle(). Start with the latest Master branch and create a new branch. Take a moment to open and examine music.h in the “Application>Include” section of the project workspace. This file contains several octaves of note definitions that you will use in your song. It also has note durations and some other definitions that are needed to create a song. Each note must have a frequency, a duration, and a type. There are short-hand notations for all of the values to minimize the size of the array text and also make it easy to line up note values with their corresponding duration. A sample including 1 octave is shown below.
Note that “Middle C” is the de-facto reference on a piano keyboard. It happens to have a frequency of 261.6Hz which we round up to 262Hz. Since a full piano has 8 octaves, middle C is considered “C4” and is exactly 2x the frequency of the C note in the octave below (C3 shown above at 131 HZ).
To code the song, start by defining three arrays corresponding to the frequency, duration and type of the notes to be played. The word “Right” refers to the “Right hand” that these notes would be played with if on a piano.
Copy in the three sets of data below that make up the melody of the song.
Notes: F5, F5, F5, F5, F5, E5, D5, E5, F5, G5, A5, A5, A5, A5, A5, G5, F5, G5, A5, A5S, C6, F5, F5, D6, C6, A5S, A5, G5, F5, NO, NO
Durations: QN, QN, HN, EN, EN, EN, EN, EN, EN, QN, QN, QN, HN, EN, EN, EN, EN, EN, EN, QN, HN, HN, EN, EN, EN, EN, QN, QN, HN, HN, FN
Note types: RT, RT, HT, RT, RT, RT, RT, RT, RT, RT, RT, RT, HT, RT, RT, RT, RT, RT, RT, RT, RT, HT, RT, RT, RT, RT, RT, RT, RT, HT, HT
You can see why it is important that we gave short symbol names to all of the elements so that they do not extend too far on the screen but also line up visually (notice the extra spaces added to keep things lined up).
The algorithm must index the arrays to set the frequency and then play that note for the duration corresponding to the note. Two additional variables will be used to track whether the note is playing given its current interval and the duration (if any) of silence. In most cases, there is a brief period of silence between notes — if not, the notes “run in to” each other. Sometimes we want this, other times we do not. This is determined by the NoteType. To check quickly if a note is currently active, a boolean is TRUE when the note is active. We will also need a working variable for the the index. Therefore, six variables are needed:
The algorithm we will code is quite simple especially in pseudo code:
Implement the pseudo code piece by piece. First add in the basic structure which is the main if() frame that runs when a note has finished playing. The code must determine if it is time to start a new note or if its at the silent part of the current note. If a new note is to be played, the note type (RT, ST, or HT) must be handled and then the buzzer set up). Note there is a utility function called “IsTimeUp()” that might be useful. Try to code this yourself before looking at the EiE implementation below.
The note adjustments are coded as follows:
- RT: Regular notes have a short silent period (REGULAR_NOTE_ADJUSTMENT). So u16CurrentDurationRight is au16DurationRight[u8CurrentIndex] – REGULAR_NOTE_ADJUSTMENT and u16NoteSilentDurationRight is REGULAR_NOTE_ADJUSTMENT. Since there is a silent period after the note is played, bNoteActiveNextRight is FALSE.
- ST: Staccatto notes are brief so they have a fixed duration of STACCATO_NOTE_TIME. That leaves au16DurationRight[u8CurrentIndex] – STACCATO_NOTE_TIME for u16NoteSilentDurationRight. Since there is a silent period after the note is played, bNoteActiveNextRight is FALSE.
- HT: Hold notes do not have any silent period, so u16CurrentDurationRight is the full duration, u16NoteSilentDurationRight is 0 and the next event is the next note so bNoteActiveNextRight is TRUE. We must therefore increment the note index here, too.
Once the duration values have been set, we can make the tone active by setting the frequency and activating the buzzer. A “NO” note (i.e. no sound is played) is a special case so it is handled separately and just turns off the buzzer. It still must be treated like any other note to fit properly in the algorithm.
We still have to handle the “non-active” note case which is the silent part of the duration. The code above was conditioned on bNoteActiveRight being TRUE, so if it is FALSE we have to update the parameters to produce the silent period and get ready for the next note.
Build the code and run it — you should hear the melody of the song playing.
Adding in the “left hand” music is essentially identical to the right hand with all the variables named “left” instead of “right.” The variables and music are provided here:
Notes: F4, F4, A4, A4, D4, D4, F4, F4, A3S, A3S, D4, D4, C4, C4, E4, E4
Durations: EN, EN, EN, EN, EN, EN, EN, EN, EN, EN, EN, EN, EN, EN, EN, EN
Note types: RT, RT, RT, RT, RT, RT, RT, RT, RT, RT, RT, RT, RT, RT, RT, RT
The music is shorter because it is a repetitive sequence. We do not need to code anything that repeats exactly, though care must be taken to ensure that timing still works out precisely so that the right hand and left hand do not get out of sync. The code is shown here without any explanation since it is identical to what was detailed above.
Build the code and notice how much better the song sounds with the two different channels working together. Your ear is the mixer that combines the sounds. This is what happens on a regular speaker, but mixing audio on a piezoelectric buzzer where we only have a single driving amplitude signal is simply not possible (although someone once said it was possible to do it in firmware…). A great project would be an eight-channel piezoelectric board so you could add up to six additional channels with harmonies, beats, and anything else you could imagine! Or, take the next step to run analog audio as we will do in a future module.
Coding the Lights
While the sound is cool, we can also add some visual feedback to the system by turning on LEDs in sequence to the music. It so happens we have just enough lights for the number of notes used in the song. We will code them like this:
This allows us to use a simple switch statement to select the LED for the note that is playing, rather than coding another array to hold LED selections that correspond to the notes. While there is nothing stopping you from doing that, we will use the switch statement to add some variety to the example. Update the section of code on the right hand where the buzzer is turned on or off depending on the note. The switch statement is conditioned on au16NotesRight[u8CurrentIndex] to set the correct LED on. Of course code is required to turn the LED off when no note is playing. A neat effect would be to have the LED fade out.
Turning off the LEDs must also be done in the main “silent” state.
For the left had, the LCD backlight could be used for cycles of the left hand (or individual notes if you wanted). The last thing we will show here is adding the title to the LCD in UserAppInitialize()
One of the things to be very careful with in a program like this is that the overall timing does not skew over time. Even a delay of a few microseconds every note will add up to be a noticeable delay over time. During development we had a slight timing error between the right and left algorithms that was noticeable even after just five repetitions of the song. The key is to sequence everything to a stable reference and then of course make sure that all calculations are balanced. Using the system tick to control timing is essential because regardless of what else is happening in the system, the system tick period is always accurate. Even though processing each note takes slightly different amounts of time depending on what type of note it is, the error created by that processing is zeroed every loop iteration so it is effectively 0.
As you code more embedded firmware, understanding and managing timing issues will be paramount to a successful system. The consequences of not doing so could be much worse than a song sounding bad!
[LAST UPDATE: 2023-NOV-09]