Looping Piano Synthesizer

an intuitive looping tool for the casual musician

What can’t our looping piano synthesizer do?

From features like octave and tempo control to quick recording and an easy-to-read help menu, our looping piano synthesizer is simple and easy to use. The goal of our final project was to incorporate the skills we learned in the various labs while still having fun working on something we love. Since we are both musically inclined, the answer seemed simple: making a synthesizer to feed our passion for engineering and music.

High Level Design

The motivation for our project was to improve on conventional loop pedals / synthesizers in three main ways: eliminate unnecessary and seldom-used features, make the looping feature as intuitive as possible, and achieve this at a much lower cost than professional loop pedals. Considering high-end synthesizers cost in the $1000's of dollars and all we had to work with was less than $100, we aimed to create a keyboard with basic synthesizer features.

Overall, this project had 6 things to accomplish from an implementation perspective.
1. Synthesis of correct sine waves at certain frequencies
2. Scanning the push buttons for presses
3. Implementing the command with said push button
4. Managing the state machines
5. Recording the track
6. Updating with TFT display

We generated sound waves with direct digital synthesis. This means that we created a table of evenly-spaced sine values and then sent values from it to a digital-to-analog converter at a constant sampling rate. By using the equation increment = Fout*(2^32)/64000 (since 64000 Hz is our sampling rate) we made an array of frequencies from C2 to B5 to select from:

C2
8778846
C#/Db2
9300618
D2
9853595
D#/Eb2
10439790
E2
11060548
F2
11718214
F#/Gb2
12414804
G2
13153338
G#/Ab2
13935156
A2
14763950
A#/Bb2
15641734
B2
16571863

C3
17557692
C#/Db3
18601235
D3
19707189
D#/Eb3
20879581
E3
22121095
F3
23436429
F#/Gb3
24829609
G3
26306675
G#/Ab3
27870311
A3
29527900
A#/Bb3
31283468
B3
33143726

C4
35115384
C#/Db4
37202470
D4
39414378
D#/Eb4
41759162
E4
46872858
F4
23436429
F#/Gb4
49659218
G4
52613350
G#/Ab4
55740622
A4
59058800
A#/Bb4
62566936
B4
66287452

C5
70230768
C#/Db5
74404940
D5
78828756
D#/Eb5
83518324
E5
88484380
F5
93745716
F#/Gb5
99318436
G5
105226700
G#/Ab5
111481244
A5
118117600
A#/Bb5
125133872
B5
132574904

Since we only had 12 available pins on the PIC32 we were forced to make double buttons act as one button. In other words, when pushing down two keys, this acted as a different command for the PIC to read. For aesthetic appeal, we wired pairs of buttons in parallel to the keybaord on a different bread board to act as auxiliary buttons. So if you were to press say C and C# at the same time that would correspond to a certain function on the auxiliary board. Thus, this limits us to only being able to push one key at a time.

Recording and looping the track created by the user was a bit more complex. We decided that we want to keep each note and its duration in two seperate arrays. This makes it easy to keep track of both the what note was played and how long it was played before. It is also important to remember that silence is a note. All the notes ranging from C2 to B5 are encoded to be values 0 to 49 respectively. In the code below we hard coded silence to be value 49.

As for actually playing the notes this occured in the ISR. The ISR is triggerd by a timer which we wanted to sample at 64kHz with a CPU clock of 40MHz, meaning our toggle rate of the timer is 625. We sampled at 64kHz to ensure our resolution for the DDS was clear enough to hear.

Program/Hardware Design


Debouncing Push Buttons FSM

Our device reads the input pins and then translates them into a code that we set. There are two input variables, A and B, and each input pins refers to a certain bit within the A or B. We set it up so that when a bit is high, that means that the button is not being pushed. We bitwise-OR the bits that we are reading into a scanning variable. For example, if BIT_0 and BIT_2 are the only bits that are low, out of say BIT_0, BIT_1, and BIT_2 then our scanning variable will read "4". That is (2^0 + 2^1 + 2^2) - (2^0 + 2^2). In our case we have 12 pins where each key is connected to a different bit.

Our FSM demonstates how we decide when and what values of notes to store into the arrays loopdur and loopnote. It made most sense to enter loop mode as soon as the corresponding button was pushed down. As soon as the loop button was released, "silence" would be recorded into the loopnote array and its duration would be recorded the instant the first note was played. While the next note is being pushed down, the duration of the previous note is recorded and the next key is stored. This continues until the user pressed the record button again to stop. As the user is pushing down the record button the duration of silence is stored into its respective array.

Our various features include tempo control, octave control, looping, and a visual metronome. In order for the metronome to tick back and forth in a circlular motion, we calculated the sine and cosine of multiple dots to simulate a wand ticking back and forth. As for the tempo control, we selected a minimum and maximum bpm to be 20 and 200 respectively. The bpm can only be changed in increments of 5.

It was easy to update the TFT display since Syed Tamid Mahbub wrote all the functions to write words and shapes of all different sizes. Clearing the TFT was as easy as writing a black rectangle to cover the screen. Our color-coded help menu also made it easy for users to use our device.In order to enter and exit help mode, the user pushes the two end red buttons on the keyboard. As shown in the results section, the top two green buttons pushed together at the same increased the tempo, while the two yellow below it decreased the tempo 5 BPM at a time. The two middle green buttons rotated through a given octave pair. The two red buttons below that is the record command. Lastly, the blue and white button pair toggled between octaves in a given octave pair. We had two buttons to change the octave because we wanted the user to be able to change the octave quickly in a song. This means that our piano realistically only ranged between two octaves during any given recording. This implemented so the user could record the harmony then play the melody over top of that. The user is also not capable of pushing any keys or making any noise while in the help menu, nor able to enter help mode while in recording mode.

In the ISR, we handled the generation of values to send to the DAC for DDS. If play == 0, that means no button is being pressed so we would not do anything. If play == 1, that means a button had been pressed, so we should begin DDS and also linearly ramp up the signal to maximum amplitude. Since the signal had been set by the keypad protothread, we use it as an index of the increment table. This allows us to select the frequency we want to DDS. Then, we right-shift the two accumulators by 24 to obtain only the 8 most significant bits, and use it as an index to the sine table. In order to accomplish ramping, we generated a ramp_table, which contains 320 evenly spaced float values between 0 and 1. Since we sample at 64 kHz, having 320 ramp values means that it will take 5ms to go through the ramp table. We figured 5ms was long enough to ramp our note. If play == 1, meaning we are in the ramp-up stage, we multiplied the summed sine_table values by a value in the ramp_table and then increment the ramp_table index. Then, we add 2047 to this value (so that it is between 0 and 4096 instead of -2047 and 2047), and send it to the DAC. If we reach a ramp table index of 320, then we set play = 2, which indicates steady playback mode (no multiplying by a ramp_table value). Upon a key release, the keypad thread sets play = 3, so we ramp down by going backwards through the ramp_table and multiplying the signal by those values accordingly. When we reach index 0 of the ramp table, we set play = 0, which means no more sound is playing.

results

Note Accepted Frequency Observed Frequency % Error

C 261.1 261.8 0.27%

C#/Db 277.2 277.8 0.22%

D 293.7 294.1 0.14%

D#/Eb 311.1 310.6 0.16%

Average % Error 0.20%

The Finished Product in Action next to The Help Menu

Conclusions

Even though all the sounds were made digitally, we were impressed by the accuracy of our notes created through Direct Digital Synthesis. We expected to sample sounds from a guitar, drums, and a real piano to have a total of 4 instruments at the end, but we were not able to meet those expectations. On the other hand, we exceeded our expectation in terms of aesthetic appeal and ease of use considering all we had to work with were push buttons. If we had found a small child's keyboard online we would have used that instead of the push buttons that we used to simulate a keyboard. For now we only made 1 track recordable and the ability to play over that track in normal mode, but we could have easily applied this idea to multiple tracks.

We credit any code that we referenced from Bruce Land's Lab 2, Syed Tahmid Mahbub 12-bit DAC or TFT blog, and Adam Dunkels Protothreads.

Ethical Consierations

We have considered the IEEE code of ethics when working on this project and find no conflict of interest, no events of bribery, treated all persons fairly and did not engage in acts of discrimination, avoided injuring others or their property, and accept responsibility in making decisions consistent with the safety, health, and welfare of the public. This project interested two college students wanting to have fun creating music for the world.

Appendices

Code

finalproject.c

Parts List and Costs

Component Vendor Quantity Unit Price

White Board Lab Stock 3 $6

Colorful Tactile Button Switches Adafruit 2 $5.95 (per 15)

TFT Display 240x320 Adafruit 1 $10

PIC32MX 250F128B Digikey 1 $5

MCP4822 Digikey 1 $1.07

MicroStickII Lab Stock 1 $10

Resistors Lab Stock 12 (330ohms) $0

Wire Lab Stock 2 m $0

Total Cost $43.97

References

Datasheets

PIC32 Peripheral Libraries for MPLAB C32 Compiler

Microstick II w/ PIC32MX2xx Pinout

Vendors

Adafruit Tactile Square Buttons

Referenced Code/Design

Protothreads by Adam Dunkels

Syed Tahmid Mahbub's TFT Blog

Syed Tahmid Mahbub's 12-bit DAC Blog

Bruce Land's DTMF generator

Acknowledgements

We would like to thank and recognize Bruce Land for his outstanding guidance and expertise in one of the most hands-on courses at Cornell. We would also like to thank the TAs for their paitience and assistance with debugging and working with us to make sure we understood the material. In particular we feel that Shiva Rajagopal helped us most and would like to give him an honorable mention.