Software

We built the software to be as modular as possible, letting us switch pins, mappings, and display constants quickly so we could adjust our project quickly. Because of that, we think that others could use our code to reproduce this project with some necessary but minimal modifications.

Keyboard Input

This part of the project was a major roadblock for a while, as we could not get the PIC32 to reliably read data from the keyboard matrix, a critical component of our concept. Our original idea was to set up a change notification interrupt for the column pins. It would read the change, then read the rows, and then determine which keys had been pressed. This proved to be ridiculously sketchy, as key presses were sometimes recognized, but there were also many phantom presses, and the real key presses often appeared as the wrong key or with other garbage data.

After many, many hours of debugging and trying different methods, we came up with a reliable solution. The interrupts were not working since the time between a change, the context switch, and then reading the rows from the port expander was too long. We switched to a polling method, where we kept reading the column and row inputs until the timing was right and we received a valid column input. We also had to turn on pull up resistors for both inputs, as we did not realize they were both active low. After these changes, we could read key presses very well, but not perfectly. Sometimes keys that weren't pressed flickered on, and other times keys that were pressed turned off for a fraction of a second. We solved this issue by implementing a debouncing buffer. With it, 4 or more of the last 10 readings for each key have to be high for the key to be considered high.

We also made keyboard_wait_for_release, a function which sets all currently pressed keys off and requires them to be released before counting as on again. This was very useful for the menu and song player, as we didn't want a single key press to count as multiple very quickly repeated key presses.

Translating Songs into Data Structures

We set up data structures to contain songs as described in the high level design. However, it would have been extremely tedious to create songs by manually constructing C structs of the correct notes and quantas. Therefore, we made a Python program that translates from a rudimentary song language into a C file which we could then compile with no extra effort. The song files have a bit of metadata at the top, such as song title, beats per minute, and starting octave for the sheet music. Afterwards, each line is a quanta, with information about the time, the chord (optional), and the melody (optional). This made transcribing songs much easier.

Playing Songs and Displaying Sheet Music

Playing songs is made easy by the modularity of our other components. The game loop is simply to poll the keyboard and check that the keys pressed are a superset of the keys needed to be pressed this quanta, determined by inspecting the chord and melody of the current quanta. If so, advance to the next quanta, updating the LEDs and sheet music. If the song is over, turn off the LEDs and return a flag signifying that the song is over.

Displaying the sheet music turned out to be somewhat more of a challenge. We defined various layout parameters which we then used to calculate the x and y positions of each note in each quanta within two measures of the left side of the sheet music. We displayed the chord name above the sheet music. For each note, we performed a few checks to determine if it needed to be solid, hollow, with a line or without, with a ‘wing' or two at the top or none, and with an accidental drawn. After a bit of tinkering around with the parameters, we settled on one configuration which we were pretty happy with as it looked similar to real sheet music.

Main Menu and State Machine

The last piece to tie all of the software together is the main menu. From there, you can scroll and select through all loaded songs, as well a freeplay mode where the LEDs light up to match the user's key presses. A state machine with states for MENU, PLAYING, and FREEPLAY controls this logic.