PIC32 BoomBox

Project Introduction

hi

For our project we decided that we would build off of lab 1 and create a Boom Box. Specifically, we wanted to create a device that would allow the user to create multilayered beats with more interesting sounds than in lab 1. Unlike lab 1 we also wanted to add on a visual interface so that the user could see a visual representation of each of the notes. Though it was built off of lab 1 there were alot of independent design choices we had to make which were not simple. However the end result was very fulfilling and cool.

Design and Testing Methods

The Idea

In order to be different from lab 1 we also wanted to add in some new sounds, so we would implement methods of creating different sounds such as FM Synthesis to make futuristic sounds and the Karplus-Strong algorithm to produce string-like sounds. We unfortunately did not end up having enough time to do Karplus-Strong but did FM Synthesis quite successfully. Something else important to us was that users could conceivably create any song they wanted. To do this, we would give them access to the keys C-G of a traditional keyboard, as well as the black keys. We would also provide a way for the user to change the octave of the notes and get higher tones than just middle C.

The user would input the controls through a 16 key keypad much like the ones on old payphones. Ours specifically looked like this: hi

keys 1-9 would be tones, A-D would be options to change sound type and the bottom keys would be to change record mode.

Visuals would be displayed on the TFT screen that came with the PIC32. We would have small squares which correspond to each note. Each square would have its own coloring which corresponded to a different tone for each sound. The end result would look like a grid with each square being a tone. This would make it much easier for the user to develop songs.

Basic Sound Synthtesis

To produce the sound, we would have to send special electrical signals to the speaker in order to let the user hear things. The signals would be sent from the digital to analog converter (DAC) on the PIC32, but what specifically would these signals be? Like in lab 1, we employed the use of the Direct Digital Synthesis Algorithm in order to produce sounds of any frequency. The algorithm takes the idea of a basic sine wave and transforms it into digital logic that we can manipulate how we want. It works like this:

A sound is simply a sine wave with a set frequency. In DDS, we represent a sine wave as an integer and increasing this integear moves us along the sine wave, just like an angle. 0 corresponds to the beginning of the wave while in our case, 2^32 is the end of it (a full rotation). The overflow of the integer called the accumulator signifies the repetition of a cycle. You can see that in the pictures below, where each frame has the number increasing and corresponding to a different point on the wave:

hi hi hi

By how much should we increment the accumulator? We can figure out specifically through some dimensional analysis. Here j = N = the number of points we need to add given the desired output frequency and the sample frequency Fs.

hi

Solving for N we find that:

hi

All of our manipulation of the frequency centers around this equation. Changing the Fout in the equation is how we manipulate the frequency. We choose a sample frequency of 44kHz (which is what we used in lab 1). Now that we have a way to mathematically describe how we will use the sine function, we need a way to reference it. The most obvious way to do this would be to make a table with 2^32 values sampled from a sine table and reference that, but we actually do not need to use that many. Our sound doesn’t need to be supremely accurate, just good enough for the human ear. So it turns out a simple sine table with 246 values actually gives us the best balance between hearing distortion and saving memory. This is a value which has also been carried over from lab 1. With all of these things together, we have all we need to generate a sound of any frequency.

Special Sounds

As stated before we wanted to add some new sounds so we had to change DDS a little bit. Specifically we wanted to add FM modulation, in which the frequency of the sounds changes as the note goes on. Since the increment amount affects frequency, then to change the frequency as we play we just needed to actively change the increment amount. We did this by making a second DDS accumulator variable used to change the primary frequency, and added that onto the first accumulator.

Modulation by itself does create an interesting sound but to produce sounds that do not sound boring an envelope on the sound is also required. This basically just consisted of a careful manipulation of the amplitude. In lab 1 we used a linear envelope around our sounds but here in order to get cool sounds an exponential envelope was needed. Thankfully, most of that complex math was done in the example code we found so we did not have to mess with it alot, but it consisted of an approximation of an exponential rise and fall using a differential equation. The amplitude looked something like this:

hi

Hardware Design

The only hardware additions we made to the provided board was a 16-key keypad, which was connected similarly to the 12-key keypad we used for Lab 1. This keypad is connected to port Y on the PIC, and how it operates is that each pin is crossed in an array setup like so:

hi

The column pins which are connected to Y4, Y5, and Y6 (and in our case Y7) on the board are all pulled high, while the row pins (connected to Y0 through Y3) are wired with pulldown resistors. When a key is pressed, it is shorted internally:

hi

To read this keypress, each row is set low sequentially, and the 4-bit output of the rows is read. The overall output on Port Y is shifted left 4 bits and the newly read value becomes the 4 least significant bits in this overall output. This process is repeated until one of the column pins are read low due to the short from the pressed key (see below), in which case we pull the 8-bit value from port Y and compare it to a pre-calculated keytable within our keypad thread.

hi

Basic Software Setup

The basic setup to produce sound was very much modeled after lab 1. An interrupt was set up which ran 44000 times per second. In this ISR we would add the increment to the accumulator, and then send it off to the DAC for it to go to the speaker.

Because we had different modes which we wanted to be able to switch between, we had a need of creating a state machine to be able to switch between them. In order to encapsulate each of the states we initially placed each of them into their own threads. We had threads for keypad pressing which allowed for free play, a thread for recording, and a thread for playing notes (directing the ISR). The program would start in the free play state in the keypad thread, which was in charge of logging key presses and telling the ISR to play the single notes that corresponded to them. The record state lived in the record thread, which would store information about the desired note such as its incrementer value (frequency) and its sound type. The record state would move to the playback state when either the transition state button was pressed or all the notes for the song were filled. The playback state told the ISR which exact notes to play and when to move on to the next one. You could then cycle back to the initial state by pressing the state transition button.

We realized we needed an efficient way to store information about the notes. In order to do this we created a struct called sound_struct. This struct saved the sound mode, sound type, main sound increment value and the sound increment for the modulation. With a data structure to manage all of the notes, we organized all the notes into one big matrix called melody. Each row was a melody for a particular layer of the song. This allowed for very simple indexing and referencing of notes. This was constructed with a 2D array in C.

Sound Variation

As stated before we wanted to allow for variability in the different sounds you can make. We allow for the user to change sound mode, sound type and octave. Sound mode was meant to change a note from being FM synthesis to Karplus-Strong producing string-like sounds. We did not have time to implement the second mode. Sound type changed some parameters of the envelope for the note. By changing things like the rate of decay, rate of increase and amplitude, changing the sound type would produce sounds ranging from normal FM sounds to ones which sounded more futuristic and synthesised. The sound types were a,b,c and d each one sounding a little different from the next. The user could also raise the octave of the note, by default the tones were all in around middle c. The octave would be raised by multiplying the base frequency by 2,3 or 4 in order to get higher octaves.

Testing and Debugging

When we started the project, we were told we could use some of the old code from the 4760 website in order to produce FM sounds. This was old code written by Professor Land. We initially tried running it, but then found that it would not compile due to errors. The errors in the end were because in the past the class used different circuit boards for the PIC, so we could not use this code. With some guidance from the professor, we instead made our own simple work around with two accumulators and just added the FM accumulator to the main one. This provided the beginning to the sound we wanted. We still however needed to put in a specialized envelope for the sounds for it to sound interesting. This part we did reference directly from the code as most of it involved variable declarations addition, which worked fine on our current board version.

When we began recording and playing different beats we immediately discovered we had a problem with our playback. Initially the playback thread directed the ISR onto which note it should play and when it should start. We did this by making the playback thread block itself until the song ended (it would know because we had a flag called done) and then direct the ISR onto its next action. The issue we discovered however was that the song would play but then produce strange delays between notes of random length and frequency. This was because the ISR would work many times faster than the playback thread, so the note would end and it would sit waiting for direction until the playback thread had its turn. This all happened because our threads are non preemptive, so when they reappear is not predetermined, causing awkward silences. The solution to this was to give the ISR full control over the playing of songs so that there would be no delay. So in the end, we removed the need for the playback thread, and signaled the ISR to play the recorded song on repeat whenever it was in the playback state.

The cool thing about working on a sound based project was that mistakes in development often lead to sounds which still sounded interesting. A prime example is when we first tested out regular free play. In the beginning, when we tried playing a sound, we discovered that it was not working properly and produced a very harsh and complicated robotic sound which was constantly growing in complexity. As it is difficult to describe and we don’t have it in the final version to display, the best description for this mode is “like a nuke going off”, sounding both very interesting but awful. We did a lot of debugging to figure out why this effect was happening. Eventually we discovered it was because we did not debounce our buttons yet, so we would press a button to play a sound and the board would think we pressed it twice and try to restart the sound. Since at the time it did not zero all of the accumulation variables it would essentially add onto old data, leading to a very grindy complicated sound. We actually planned to add this as a sound mode type later in the project but did not have the time to do so.

One very real problem was efficiency. In lab 1 we only had to compute values for a singularly layered song of a basic frequency. This time however, we had to compute accumulation values for FM modulation, and give our notes more complicated envelopes in order to sound right, and we had to do this for multiple layers. This greatly increased the computational load and made the PIC spend more and more time in the ISR. We did try some optimizations to speed things up. First we removed any redundant loops in the code and made all layer related duties happen in one loop in the ISR. We then removed the need for the ISR to calculate the increment amount by precomputing it in the record state and having each sound struct save it. This was definitely an improvement but sadly was not enough to make it perfect. The biggest improvement was increasing the optimization level of the PIC to 1 instead of zero. This brought huge computational gains to the program but was not enough to prevent a layer limit Through testing we discovered that our layer limit was 3 layers. After that the PIC would still play the song perfectly but all the visuals on the screen would not move as the ISR was taking up too much time.

Results

Boom Boox Playing Chopsticks

On startup, the final version of our project draws a series of white squares to the TFT corresponding to the length of the song and the number of layers. When in the record state each keypress fills in its respective square (i.e square[current_layer][current_note]) with a predefined color that matches the tone of the sound played. When the pattern repetition key is pressed, each square in the layer is filled in as if the keys were pressed individually. We wanted to have the squares black out during the playback state to represent progress into the recorded song, but we did not have enough time to fully implement this. Our first attempt at this implementation resulted in columns being blanked out seemingly at random. This occurred because our ISR was running much faster than the thread we used to draw over the squares, so we could not easily have our drawing keep up with the pace of the song. The easiest solution to this would be to also draw over the columns in the ISR, but we did not want to slow down the main functionality of our project for this bonus feature. We instead decided to focus on other key features and return to this if we had the time.

When we mapped out our project, we planned to implement fully fleshed-out FM synthesis, with our stretch goal being a Karplus-Strong mode for string instruments. While we did not have the time to add Karplus-Strong functionality, we are quite satisfied with our FM outcome. As mentioned in the sound variation section, we offered four different sound types, with each supporting an expansive frequency range. These sound types could easily be configured within the code by adjusting increment and decay constants. The number of sound types could have been increased quite easily, but we were restricted by the limited number of keys on the keypad. We felt that any method of changing sound type other than a single key that cycles through them would cannibalize our ability to use the keypad for other valuable functions. Unfortunately, cycling through a large number of sound types in one direction quickly becomes quite a hassle while recording. We felt supporting four types struck the best balance of variability and ease of use.

As previously mentioned, the size of the keypad was a very limiting factor. While we managed to squeeze in quite a bit of functionality, there were a fair number of functions we could not include such as: bidirectional scrolling through sound types, support for drastically different modes (such as one that does not clear the accumulator values, creating very jumbled sounds, and more.) Ultimately, we needed to compromise and focus on the features that were most core to the project. Our final keyset consisted of:

While the 8 key was open, we felt that any function we could add to it would be unintuitive to use given its placement.

IP Considerations

A large majority of this product was fully original work. We did, however, reference instructor-provided code for some of the base functionality like reading from the keypad and setting up decay parameters for the FM synthesis. We also used some of our direct digital synthesis code from lab 1 as a starting point.

Apendix A

This group approves this report for inclusion on the course website and the youtube channel

Code


  ////////////////////////////////////
// clock AND protoThreads configure!
// You MUST check this file!
#include "config_1_3_2.h"
// threading library
#include "pt_cornell_1_3_2.h"
// yup, the expander
#include "port_expander_brl4.h"

////////////////////////////////////
// graphics libraries
// SPI channel 1 connections to TFT
#include "tft_master.h"
#include "tft_gfx.h"
// need for rand function
#include 

#include 
// The fixed point types
#include 
////////////////////////////////////

// lock out timer interrupt during spi comm to port expander
// This is necessary if you use the SPI2 channel in an ISR
#define start_spi2_critical_section INTEnable(INT_T2, 0);
#define end_spi2_critical_section INTEnable(INT_T2, 1);

// Predefined colors definitions (from tft_master.h)
//#define	ILI9340_BLACK   0x0000
//#define	ILI9340_BLUE    0x001F
//#define	ILI9340_RED     0xF800
//#define	ILI9340_GREEN   0x07E0
//#define ILI9340_CYAN    0x07FF
//#define ILI9340_MAGENTA 0xF81F
//#define ILI9340_YELLOW  0xFFE0
//#define ILI9340_WHITE   0xFFFF

// === thread structures ============================================
// thread control structs
// note that UART input and output are threads
static struct pt pt_timer, pt_color, pt_anim, pt_key, pt_record, pt_playback;

// order is 0 thru 9 then * ==10 and # ==11
    // no press = -1
    // table is decoded to natural digit order (except for * and #)
    // with shift key codes for each key
    // keys 0-9 return the digit number
    // keys 10 and 11 are * adn # respectively
    // Keys 12 to 21 are the shifted digits
    // keys 22 and 23 are shifted * and # respectively
    volatile int keytable[24]=
    //        0     1      2    3     4     5     6      7    8     9    10-*  11-#
            {0xd7, 0xee, 0xde, 0xbe, 0xed, 0xdd, 0xbd, 0xeb, 0xdb, 0xbb, 0xe7, 0xb7,
    //        A     B      C    D
             0x7e, 0x7d, 0x7b, 0x77};

////////////////////////////////////
// some precise, fixed, short delays
// to use for extending pulse durations on the keypad
// if behavior is erratic
#define NOP asm("nop");
// 20 cycles
#define wait20 NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;NOP;
// 40 cycles
#define wait40 wait20;wait20;
////////////////////////////////////

/* Demo code for interfacing TFT (ILI9340 controller) to PIC32
 * The library has been modified from a similar Adafruit library
 */
// Adafruit data:
/***************************************************
  This is an example sketch for the Adafruit 2.2" SPI display.
  This library works with the Adafruit 2.2" TFT Breakout w/SD card
  ----> http://www.adafruit.com/products/1480

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

// string buffer
char buffer[60];

#define max_layers 3
#define max_length 3
int num_layers = 3;
int song_length = 3;

//_Accum DAC_divide = 1/num_layers;
////////////////////////////////////
// DAC ISR
// A-channel, 1x, active
#define DAC_config_chan_A 0b0011000000000000
// B-channel, 1x, active
#define DAC_config_chan_B 0b1011000000000000
//== Timer 2 interrupt handler ===========================================
volatile unsigned int DAC_data_A, DAC_data_B ;// output values
volatile SpiChannel spiChn = SPI_CHANNEL2 ;	// the SPI channel to use
volatile int spiClkDiv = 4 ; // 10 MHz max speed for port expander!!

/***SOUND SYNTHESIS STUFF***/
// audio sample frequency
#define Fs 44000.0
// need this constant for setting DDS frequency
#define two32 4294967296 // 2^32
#define two32overfs 97613
#define piover 0.000549
// sine lookup table for DDS
#define sine_table_size 256
volatile _Accum sine_table[sine_table_size] ;
int done = 0;
// phase accumulator for DDS
volatile unsigned int DDS_phase ;
// phase increment to set the frequency DDS_increment = Fout*two32/Fs
// For A above middle C DDS_increment =  = 42949673 = 440.0*two32/Fs
#define Fout 440.0
#define swoop(a) -260*sin(-3.14/5720*a)+1740
#define chirp(a) (0.000153)*a*a + 2000
//y=(1.53×10?4)x2+2000
#define freq1 100*two32overfs
#define freq2 freq1*2
#define freq3 freq1*4

//white keys
#define c4 25476993//261hz
#define d4 28600609//293hz
#define e4 32114677//329hz
#define f4 34066937//349hz
#define g4 38264296//392hz
#define a4 42949720//440hz
#define b4 48123209//493hz
//black keys
#define c4sharp 27056371
#define d4sharp 30357643
#define f4sharp 36107048
#define g4sharp 40509395
#define a4sharp 45487658

volatile unsigned int mod_increment1 = 3*freq1;
volatile unsigned int mod_accum1 = 0;//0.7*261.0*two32/Fs;
volatile unsigned int main_increment1 =  freq1;
volatile unsigned int main_accum1 = 0;//261.0*two32/Fs ;

volatile unsigned int mod_increment2 = 3*freq2;
volatile unsigned int mod_accum2 = 0;//0.7*261.0*two32/Fs;
volatile unsigned int main_increment2 =  freq2;
volatile unsigned int main_accum2 = 0;//261.0*two32/Fs ;

//array to store each layer incrments


//factor for modulation
volatile _Accum env_fm[max_layers] = {1};
// waveform amplitude
volatile _Accum max_amplitude=1;

// waveform amplitude envelope parameters
// rise/fall time envelope 44 kHz samples
#define time_scale 0.001;//used to change how long songs are
#define time_scale_rise_fall 0.6//used to change length of rise and fall
volatile unsigned int attack_time=500 *time_scale_rise_fall;
volatile unsigned int decay_time=1000*10*time_scale_rise_fall;
volatile unsigned int sustain_time=3720*10*time_scale;

//enevlope parameters
volatile unsigned int attack_time2=500, decay_time2=1000, sustain_time2=3720*5 ;
//  0<= current_amplitude < 2048
volatile _Accum current_amplitude[max_layers];
volatile _Accum current_amplitude2;
// amplitude change per sample during attack and decay
// no change during sustain
volatile _Accum attack_inc, decay_inc ;
volatile _Accum attack_inc2, decay_inc2 ;

// interrupt ticks since beginning of song or note
volatile unsigned int song_time;
volatile unsigned int note_time = 0;
volatile unsigned int key = 1;

//========Add Above ISR========= (env_fm already present)
// Adjustable Parameters
volatile _Accum wave_fm[max_layers] = {0.8};
volatile _Accum decay_fm[max_layers]= {0.8};
volatile _Accum attack_fm[max_layers] = {0.001};

volatile _Accum env_main[max_layers] = {0.98};
volatile _Accum wave_main[max_layers] = {0.98};
volatile _Accum decay_main[max_layers]= {0.98};
volatile _Accum attack_main[max_layers] = {0.001};
volatile _Accum fm_depth[max_layers] = {2.2};

//Variable declarations
volatile _Accum decay_state_fm[max_layers]={0};
volatile _Accum attack_state_fm[max_layers]={0};
volatile _Accum decay_state_main[max_layers]={0};
volatile _Accum attack_state_main[max_layers]={0};
/*WE WILL UPDATE DECAY INTERVAL BASED ON SOUND TYPE*/
volatile int decay_interval[max_layers]={0}; //Amount of time to wait between decay calculations
volatile _Accum sustain_state[max_layers]={0};
volatile _Accum sustain_interval[max_layers] = {0};
volatile _Accum sustain_constant[max_layers] = {256/10000};
//==============================

/***SOUND SYNTHESIS STUFF END***/

/***SOUND STATEMACHINE START**/

//information for each specific sound

//DDS increments and accumulators for each layer
volatile unsigned int main_increment[max_layers] = {0};
volatile unsigned int mod_increment[max_layers] = {0};
volatile unsigned int mod_accum[max_layers] = {0};
volatile unsigned int main_accum[max_layers] = {0};

//struct to hold sound information
struct sound_struct{
    //0 == fm 1 == strung
    int sound_mode;
    //different variations on sound in a mode
    int sound_type;
    //
    unsigned int sound_main_freq;
    unsigned int sound_mod_freq;
};

//zeros the tone in each note so that they are silent by default
volatile int melody_index[max_layers] = {0};
volatile struct sound_struct melody[max_layers][max_length] = {0};
//function to make all tones default be no sound
void default_melody(){
    int row = 0;
    int col = 0;
    for(col=0;col<song_length;col++){
        for(row=0;row<num_layers;row++){
            //this is the zero key so that every sound defaults to nothing
            melody[row][col].sound_main_freq = 0;
            melody[row][col].sound_mod_freq = 0;
        }
    }
}

volatile int layer = 0;
//frequency associated with each key press.Index zero is skipped
volatile int button_freq[12] = {0,freq1,freq2,freq3,0,0,0,0,0,0,0,0};
/*
 0 = Play key
 */
volatile int play_state = 0;
//mode = 0 is fm 1 is karpl
volatile int mode = 0;
volatile int pressed = 0;
//used for the record thread to decide when to accept key data
//needed because debug solution for keys does not work for record thread
volatile int info_process = 1;

volatile int sound_type = 0;

volatile int new_recording = 0;

volatile int current_note = 0;

//flag to only edit for new sound at the begining of note
volatile int configured_sound = 0;

volatile int test_thing = 0;
//key pad key value
volatile int keypad = 0;

volatile int freq_mult =1;

volatile int play_control = 0;

volatile int black_key = 0;

/***SONG ANIMATION START***/
struct sound_square{
    int color;
    int played;
    int pos_x;
    int pos_y;
};
volatile struct sound_square song_color[max_layers][max_length] = {0};
#define side_buffer 5
#define dist_square 5
#define dist_layers 10
#define layer_sep 5
#define display_start_height 70
#define block_area_height 100
int  block_height = 0;
int square_size = 0;
volatile int square_column = 0;
volatile int column_covered = 0;

void color_square(int square_layer, int square_note, int color){
    tft_fillRoundRect(song_color[square_layer][square_note].pos_x
                        ,song_color[square_layer][square_note].pos_y
                        , square_size
                        , block_height
                        , 0
                        , color);// x,y,w,h,radius,blues
 }
void init_squares(){
    int layer = 0;
    int note = 0;
//    block_height = (block_area_height-(layer_sep*(num_layers-1)))/num_layers;
    square_size = (320-2*side_buffer-dist_square*(song_length-1))/song_length;
    tft_fillRoundRect(side_buffer
                        ,display_start_height
                        , 320-(2*side_buffer)
                        , block_area_height
                        , 0
                        , 0);// x,y,w,h,radius,blues
    for(layer=0;layer<num_layers;layer++){
        for(note=0;note<song_length;note++){
            song_color[layer][note].pos_x = side_buffer+note*(square_size+dist_square);
            song_color[layer][note].pos_y = display_start_height+(layer_sep+block_height)*layer;
            color_square(layer,note,0xFFFF);
        }
    }
}

//
//   volatile int keytable[24]=
//    //        0     1      2    3     4     5     6      7    8     9    10-*  11-#
//            {0xd7, 0xee, 0xde, 0xbe, 0xed, 0xdd, 0xbd, 0xeb, 0xdb, 0xbb, 0xe7, 0xb7,
//    //        A     B      C    D
//             0x7e, 0x7d, 0x7b, 0x77};
int keycolor(int key){
    if(key == keytable[0]){
        return 0x701f;
    }
    else if(key == keytable[1]){
        return 0xf800;
    }
    else if(key == keytable[2]){
       return 0xfe25;
    }
    else if(key == keytable[3]){
       return 0xF97D;
    }
    else if(key == keytable[4]){
       return 0x1BE4;
    }
    else if(key == keytable[5]){
       return 0x2FF3;
    }
    else if(key == keytable[6]){
       return 0x2B3F;
    }
}
/***SONG ANIMATION END***/

/***SOUND STATEMACHINE START**/
void restart_song(){
    note_time = 0;
    done = 0;
    int reset_layer_index = 0;
    for(reset_layer_index=0;reset_layer_index<num_layers;reset_layer_index++){
        main_accum[reset_layer_index] = 0;
        mod_accum[reset_layer_index] = 0;
        if(melody[reset_layer_index][current_note].sound_mode == 0){
            decay_state_fm[reset_layer_index] = fm_depth[reset_layer_index];
            decay_state_main[reset_layer_index] = 1;
            attack_state_fm[reset_layer_index] = fm_depth[reset_layer_index];
            attack_state_main[reset_layer_index] = 1;
            decay_interval[reset_layer_index] = 0;
            sustain_state[reset_layer_index] = 0;
            current_amplitude[reset_layer_index] = 0;
        }
    }
    configured_sound = 0;
//        }
//    if(mode == 0){
//        decay_state_fm = fm_depth;
//        decay_state_main = 1;
//        attack_state_fm = fm_depth;
//        attack_state_main = 1;
//        decay_interval = 0;
//        sustain_state = 0;
//        current_amplitude = 0;
//        configured_sound = 0;
//        //we have to reset the accums for each layer
//        for(reset_layer_index=0;reset_layer_index<num_layers;reset_layer_index++){
//            main_accum[reset_layer_index] = 0;
//            mod_accum[reset_layer_index] = 0;
//        }
//    }
}
//The frequencies are wrong for all of these modes. They require fixed-point multiplication
//so we will have to do some casting. I wanted to keep it simple and focus on base functionality
//so these use placeholder frequency values
void set_string_like(int layer){
    //sound_type = 0;
    mod_increment[layer] = 3*main_increment[layer];
    attack_main[layer] = 0.001;
    attack_fm[layer] = 0.001;
    decay_main[layer] = 0.98;
    decay_fm[layer] = 0.8;
    fm_depth[layer] = 2.2;
    sustain_interval[layer] = 0;
}

void set_drum(int layer){
    //sound_type = 1;
    mod_increment[layer] = 2*main_increment[layer];
    attack_main[layer] = 0.001;
    attack_fm[layer] = 0.001;
    decay_main[layer] = 0.99;
    decay_fm[layer] = 0.9;
    fm_depth[layer] = 1.5;
    sustain_interval[layer] = 0;
}

void set_struck_string(int layer){
    //sound_type = 2;
    mod_increment[layer] = 2*main_increment[layer];
    attack_main[layer] = 0.005;
    attack_fm[layer] = 0.005;
    decay_main[layer] = 0.98;
    decay_fm[layer] = 0.98;
    fm_depth[layer] = 2;
    sustain_interval[layer] = 0;
}

void set_bowed_string(int layer){
    //sound_type = 3;
    mod_increment[layer] = 2*main_increment[layer];
    attack_main[layer] = 0.99;
    attack_fm[layer] = 0.95;
    decay_main[layer] = 0.98;
    decay_fm[layer] = 0.97;
    fm_depth[layer] = 3;
    sustain_interval[layer] = 1;
}

inline void set_sound_type(int sound_type, int layer){
    if(sound_type == 0){
       set_string_like(layer);
    }
    else if(sound_type == 1){
       set_drum(layer);
    }
    else if(sound_type == 2){
        set_struck_string(layer);
    }
    else if(sound_type == 3){
       set_bowed_string(layer);
    }
}

//plays sound on key only if play_state = 0
void play_if_zero(){
    if(play_state == 0){
        //main_increment1 = frequency;
        restart_song();
    }
}

/***IN THE FUTURE YOU NEED TO CHANGE THIS SO THAT IT MAINTAINS CORRECT RATIO
    BETWEEN MOD_INCREMENT AND MAIN INCREMENT*/

inline unsigned int calculate_main_inc(int key){
    if(key == keytable[0]){
        return 0;
    }
    else if(key == keytable[1]){
        if(!black_key){
            return c4*freq_mult;
        }
        else{
           return c4sharp*freq_mult;
        }
    }
    else if(key == keytable[2]){
       if(!black_key){
            return d4*freq_mult;
        }
        else{
           return d4sharp*freq_mult;
        }
    }
    else if(key == keytable[3]){
       if(!black_key){
            return e4*freq_mult;
        }
        else{
           return f4sharp*freq_mult;
        }
    }
    else if(key == keytable[4]){
       if(!black_key){
            return f4*freq_mult;
        }
        else{
           return g4sharp*freq_mult;
        }
    }
    else if(key == keytable[5]){
       if(!black_key){
            return g4*freq_mult;
        }
        else{
           return a4sharp*freq_mult;
        }
    }
    else if(key == keytable[6]){
       return a4*freq_mult;
    }
    else if(key == keytable[7]){
       return b4*freq_mult;
    }
}

unsigned int test_var = 0;

//in the future we will change frequency based on key
inline unsigned int calculate_mod_inc(int key){
    //setting the mod to zero when zero makes it silent
    if(key == keytable[0]){
        return 0;
    }
    else if(key == keytable[1]){
        if(!black_key){
            return c4*freq_mult;
        }
        else{
           return c4sharp*freq_mult;
        }
    }
    else if(key == keytable[2]){
       if(!black_key){
            return d4*freq_mult;
        }
        else{
           return d4sharp*freq_mult;
        }
    }
    else if(key == keytable[3]){
       if(!black_key){
            return e4*freq_mult;
        }
        else{
           return f4sharp*freq_mult;
        }
    }
    else if(key == keytable[4]){
       if(!black_key){
            return f4*freq_mult;
        }
        else{
           return g4sharp*freq_mult;
        }
    }
    else if(key == keytable[5]){
       if(!black_key){
            return g4*freq_mult;
        }
        else{
           return a4sharp*freq_mult;
        }
    }
    else if(key == keytable[6]){
       return a4*freq_mult;
    }
    else if(key == keytable[7]){
       return b4*freq_mult;
    }
//    set_sound_type(type);
}

//sets all needed parameters for the free play mode
void single_note_play_set(keypad){
    melody[0][0].sound_mode = mode;
    melody[0][0].sound_type = sound_type;
    melody[0][0].sound_main_freq = calculate_main_inc(keypad);
    melody[0][0].sound_mod_freq = calculate_mod_inc(keypad);
    play_control = 1;
    current_note=0;
    restart_song();
}

void increment_layers(){
    num_layers++;
    if(num_layers>max_layers){
        num_layers=1;
    }
}
void increment_length(){
    song_length++;
    if(song_length>max_length){
        song_length=2;
    }
}

/***SOUND STATEMACHINE END**/
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void)
{
    int junk;

    mT2ClearIntFlag();
    if(!done && play_control){
        int current_layer = 0;
        //configures the sound for each layer
        if(!configured_sound){
            int layer =0;
            for(layer=0;layer<num_layers;layer++){
                set_sound_type(melody[layer][current_note].sound_type, layer);
            }
            configured_sound = 1;
        }
        DAC_data_A = 0;
        //iterate through sound in sequence
        //iterate through each layer and calculate DDS values

        for(current_layer=0;current_layer<num_layers;current_layer++){
            if ((decay_interval[current_layer]++ & 0xff) == 0){
                decay_state_fm[current_layer] = decay_state_fm[current_layer] * decay_fm[current_layer];
                decay_state_main[current_layer] = decay_state_main[current_layer] * decay_main[current_layer];
                attack_state_fm[current_layer] = attack_state_fm[current_layer] * attack_fm[current_layer];
                attack_state_main[current_layer] = attack_state_main[current_layer] * attack_main[current_layer];
                env_fm[current_layer] = (fm_depth[current_layer]-attack_state_fm[current_layer]) * decay_state_fm[current_layer];
                env_main[current_layer] = (1-attack_state_main[current_layer]) * decay_state_main[current_layer];
                if(sustain_state[current_layer] < sustain_interval[current_layer]) {
                    decay_state_main[current_layer] = 1;
                    sustain_state[current_layer] = sustain_state[current_layer] + sustain_constant[current_layer];
                }
            }
            if (note_time < (attack_time + decay_time + sustain_time)){
                current_amplitude[current_layer] = (note_time <= attack_time)?
                    current_amplitude[current_layer] + attack_inc :
                    (note_time <= attack_time + sustain_time)? current_amplitude[current_layer]:
                        current_amplitude[current_layer] - decay_inc ;
            }

            mod_accum[current_layer] += melody[current_layer][current_note].sound_mod_freq;
            test_var = melody[current_layer][current_note].sound_main_freq;
            main_accum[current_layer] += melody[current_layer][current_note].sound_main_freq + (((unsigned int)(sine_table[mod_accum[current_layer]>>24]* env_fm[current_layer]))<<16 );
            DAC_data_A += ((int)(((current_amplitude[current_layer]*(sine_table[main_accum[current_layer]>>24])) + 2048)))>>2;
        }
    }

//    DAC_data_B = (int) 1000*current_amplitude;
    // test for ready
     while (TxBufFullSPI2());

    // reset spi mode to avoid conflict with expander
    SPI_Mode16();
    // DAC-A CS low to start transaction
    mPORTBClearBits(BIT_4); // start transaction
     // write to spi2
    WriteSPI2(DAC_config_chan_A | (DAC_data_A & 0xfff) );
    // fold a couple of timer updates into the transmit time
    //song_time++ ;
    if (note_time<(attack_time + decay_time + sustain_time)){
        note_time++ ;
    }
    else{
        done = 1;
        if(play_state==2){
            current_note++;
            square_column++;
            column_covered = 0;
            restart_song();
            if(current_note==song_length){
                current_note = 0;
                square_column = 0;
            }
        }
        else if(play_state==0){
            play_control = 0;
        }
    }
    // test for done
    while (SPI2STATbits.SPIBUSY); // wait for end of transaction
    // MUST read to clear buffer for port expander elsewhere in code
    junk = ReadSPI2();
    // CS high
    mPORTBSetBits(BIT_4); // end transaction

//     // DAC-B CS low to start transaction
//    mPORTBClearBits(BIT_4); // start transaction
//     // write to spi2
//    WriteSPI2(DAC_config_chan_B | (DAC_data_B & 0xfff) );
//    // test for done
//    while (SPI2STATbits.SPIBUSY); // wait for end of transaction
//    // MUST read to clear buffer for port expander elsewhere in code
//    junk = ReadSPI2();
//    // CS high
//    mPORTBSetBits(BIT_4); // end transaction
}

// === print a line on TFT =====================================================
// print a line on the TFT
// string buffer
char buffer[60];
void printLine(int line_number, char* print_buffer, short text_color, short back_color){
    // line number 0 to 31
    /// !!! assumes tft_setRotation(0);
    // print_buffer is the string to print
    int v_pos;
    v_pos = line_number * 10 ;
    // erase the pixels
    tft_fillRoundRect(0, v_pos, 239, 8, 1, back_color);// x,y,w,h,radius,color
    tft_setTextColor(text_color);
    tft_setCursor(0, v_pos);
    tft_setTextSize(1);
    tft_writeString(print_buffer);
}

void printLine2(int line_number, char* print_buffer, short text_color, short back_color){
    // line number 0 to 31
    /// !!! assumes tft_setRotation(0);
    // print_buffer is the string to print
    int v_pos;
    v_pos = line_number * 20 ;
    // erase the pixels
    tft_fillRoundRect(0, v_pos, 239, 16, 1, back_color);// x,y,w,h,radius,color
    tft_setTextColor(text_color);
    tft_setCursor(0, v_pos);
    tft_setTextSize(2);
    tft_writeString(print_buffer);
}


// system 1 second interval tick
int sys_time_seconds ;

char sound_type_buffer;
// === Timer Thread =================================================
// update a 1 second tick counter
static PT_THREAD (protothread_timer(struct pt *pt))
{
    //little change
    PT_BEGIN(pt);
     tft_setCursor(0, 0);
     tft_setTextColor(ILI9340_WHITE);  tft_setTextSize(1);
     tft_writeString("Time in seconds since boot\n");
     // set up LED to blink
     mPORTASetBits(BIT_0 );	//Clear bits to ensure light is off.
     mPORTASetPinsDigitalOut(BIT_0 );    //Set port as output
      while(1) {
        // yield time 1 second
        PT_YIELD_TIME_msec(1000) ;
        sys_time_seconds++ ;
        // toggle the LED on the big board
        mPORTAToggleBits(BIT_0);
        // draw sys_time
        tft_fillRoundRect(0,10, 100, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
        tft_setCursor(0, 10);
        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);
        sprintf(buffer,"%d", sys_time_seconds);
        tft_writeString(buffer);
        // NEVER exit while

        //Write Current Sound Mode
        tft_fillRoundRect(0,25, 240, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
        tft_setCursor(0, 25);
        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);
        sprintf(buffer,"current state: %d", play_state);
        tft_writeString(buffer);

        tft_fillRoundRect(0,180, 320, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
        tft_setCursor(0, 180);
        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);
//        sprintf(buffer,"current layer: %d", layer);
        sprintf(buffer,"Octave Multiplier : %d", freq_mult);
        tft_writeString(buffer);

        tft_fillRoundRect(0,195, 320, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
        tft_setCursor(0, 195);
        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);
        sprintf(buffer,"Black Keys: %d", black_key);
        tft_writeString(buffer);

        tft_fillRoundRect(0,220, 240, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
        tft_setCursor(0, 220);
        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);

        switch(sound_type){
            case 0:
                sound_type_buffer = 'a';
                break;
            case 1:
                sound_type_buffer = 'b';
                break;
            case 2:
                sound_type_buffer = 'c';
                break;
            case 3:
                sound_type_buffer = 'd';
                break;
            default:
                sound_type_buffer = 'n';
        }
        sprintf(buffer,"sound type: %c", sound_type_buffer);
        tft_writeString(buffer);

//        tft_fillRoundRect(0,235, 240, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
//        tft_setCursor(0, 235);
//        tft_setTextColor(ILI9340_YELLOW); tft_setTextSize(2);
//        sprintf(buffer,"%d , %d",melody[0][0].sound_mod_freq,melody[0][1].sound_mod_freq);
//        tft_writeString(buffer);

      } // END WHILE(1)
  PT_END(pt);
} // timer thread

// === Color Thread =================================================
// draw 3 color patches for R,G,B from a random number
// And toggles the Z port on the port expander
// Port Expander connections:
// z0 -- set as output -- view on scope
// z1 -- set as output -- view on scope
static int color ;
static int i;
static unsigned char ioZ;


static PT_THREAD (protothread_color(struct pt *pt))
{
    PT_BEGIN(pt);

      while(1) {
        // yield time 1 second
        PT_YIELD_TIME_msec(500) ;

        start_spi2_critical_section;
        setBits(GPIOZ, BIT_0)  ;
        toggleBits(GPIOZ, BIT_1)  ;
        end_spi2_critical_section;

        PT_YIELD_TIME_msec(500) ;

        start_spi2_critical_section;
        clearBits(GPIOZ, BIT_0)  ;
        ioZ = readBits(GPIOZ, BIT_0 | BIT_1)  ;
        end_spi2_critical_section;
//
//        tft_fillRoundRect(0, 170, 150, 20, 1, ILI9340_BLACK);// x,y,w,h,radius,color
//        tft_setCursor(0, 170);
//        tft_setTextColor(ILI9340_WHITE); tft_setTextSize(2);
//        sprintf(buffer,"Port Z %02x", ioZ);
//        tft_writeString(buffer);
//
//        // choose a random color
//        color = rand() & 0xffff ;
//
//        // draw color string
//        tft_fillRoundRect(0,50, 150, 14, 1, ILI9340_BLACK);// x,y,w,h,radius,color
//        tft_setCursor(0, 50);
//        tft_setTextColor(ILI9340_WHITE); tft_setTextSize(1);
//        sprintf(buffer," %04x  %04x  %04x  %04x", color & 0x1f, color & 0x7e0, color & 0xf800, color);
//        tft_writeString(buffer);

        // draw the actual color patches
//        tft_fillRoundRect(5,70, 30, 30, 1, color & 0x1f);// x,y,w,h,radius,blues
//        tft_fillRoundRect(40,70, 30, 30, 1, color & 0x7e0);// x,y,w,h,radius,greens
//        tft_fillRoundRect(75,70, 30, 30, 1, color & 0xf800);// x,y,w,h,radius,reds
//        // now draw the RGB mixed color
//        tft_fillRoundRect(110,70, 30, 30, 1, color);// x,y,w,h,radius,mix color
//        if(!column_covered){
//            column_covered = 1;
//            tft_fillRoundRect(side_buffer+square_column*(square_size+dist_square)
//                             ,display_start_height
//                             ,square_size
//                             ,(square_size+dist_layers)*num_layers //note will cover a little extra
//                             , 0
//                             , 0);
//        }

        // NEVER exit while
      } // END WHILE(1)
  PT_END(pt);
} // color thread

// === Animation Thread =============================================
// update a 1 second tick counter
static int xc=10, yc=150, vxc=2, vyc=0;
static PT_THREAD (protothread_anim(struct pt *pt))
{
    PT_BEGIN(pt);
      while(1) {
        // yield time 1 second
        PT_YIELD_TIME_msec(32);

        // erase disk
         tft_fillCircle(xc, yc, 4, ILI9340_BLACK); //x, y, radius, color
        // compute new position
         xc = xc + vxc;
         if (xc<5 || xc>235) vxc = -vxc;
         //  draw disk
         tft_fillCircle(xc, yc, 4, ILI9340_GREEN); //x, y, radius, color
        // NEVER exit while
      } // END WHILE(1)
  PT_END(pt);
} // animation thread

// === Keypad Thread =============================================
// Port Expander connections:
// y0 -- row 1 -- thru 300 ohm resistor -- avoid short when two buttons pushed
// y1 -- row 2 -- thru 300 ohm resistor
// y2 -- row 3 -- thru 300 ohm resistor
// y3 -- row 4 -- thru 300 ohm resistor
// y4 -- col 1 -- internal pullup resistor -- avoid open circuit input when no button pushed
// y5 -- col 2 -- internal pullup resistor
// y6 -- col 3 -- internal pullup resistor
// y7 -- shift key connection -- internal pullup resistor

static PT_THREAD (protothread_key(struct pt *pt))
{
    PT_BEGIN(pt);
    static int i, pattern;

    // bit pattern for each row of the keypad scan -- active LOW
    // bit zero low is first entry
    static char out_table[4] = {0b1110, 0b1101, 0b1011, 0b0111};
    // init the port expander
    start_spi2_critical_section;
    initPE();
    // PortY on Expander ports as digital outputs
    mPortYSetPinsOut(BIT_0 | BIT_1 | BIT_2 | BIT_3);    //Set port as output
    // PortY as inputs
    // note that bit 7 will be shift key input,
    // separate from keypad
    mPortYSetPinsIn(BIT_4 | BIT_5 | BIT_6 | BIT_7);    //Set port as input
    mPortYEnablePullUp(BIT_4 | BIT_5 | BIT_6 | BIT_7);

    // init the Z port to try other output functions
    mPortZSetPinsOut(BIT_0 | BIT_1);    //Set port as output
    end_spi2_critical_section ;

    // the read-pattern if no button is pulled down by an output
    #define no_button (0xF0)

      while(1) {
        // yield time
        PT_YIELD_TIME_msec(30);

        for (i=0; i<4; i++) {
            start_spi2_critical_section;
            // scan each row active-low
            writePE(GPIOY, out_table[i]);
            //reading the port also reads the outputs
            keypad  = readPE(GPIOY);
            end_spi2_critical_section;
            // was there a keypress?
            if((keypad & no_button) != no_button) { break;}
        }
        //   volatile int keytable[24]=
//    //        0     1      2    3     4     5     6      7    8     9    10-*  11-#
//            {0xd7, 0xee, 0xde, 0xbe, 0xed, 0xdd, 0xbd, 0xeb, 0xdb, 0xbb, 0xe7, 0xb7,
//    //        A     B      C    D
//             0x7e, 0x7d, 0x7b, 0x77};
        // search for keycode
        if (keypad > 0){ // then button is pushed
            for (i=0; i<16; i++){
                if (keytable[i]==keypad) break;
            }
            // if invalid, two button push, set to -1
            if (i==16) i=-1;
        }
        else i = -1; // no button pushed

        if(keypad == keytable[0]){
            if(!pressed){
                main_increment1 = calculate_main_inc(keytable[0]);
                play_if_zero();
                pressed = 1;
            }
        }
        else if(keypad == keytable[1]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[2]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[3]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[4]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[5]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[6]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[7]){
            if(!pressed){
                if(play_state==0){
                    single_note_play_set(keypad);
                }
                pressed = 1;
            }
        }
        else if(keypad == keytable[9]){
            if(!pressed){
                if(play_state<2){
                    if(black_key==0){
                        black_key = 1;
                    }
                    else if(black_key==1){
                        black_key = 0;
                    }
                }
                pressed = 1;
            }
        }
        //increase song length
        else if(keypad == keytable[10]){
            if(!pressed){
                if(play_state==0){
                    increment_length();
                    init_squares();
                }
                pressed = 1;
            }
        }
        //increase layer
        else if(keypad == keytable[11]){
            if(!pressed){
                if(play_state==0){
                    increment_layers();
                    init_squares();
                }
                pressed = 1;
            }
        }
        //going an octave higher
        else if(keypad == keytable[12]){
            if(!pressed){
                if(freq_mult<4){
                    freq_mult++;
                }
                else{
                    freq_mult = 1;
                }
                pressed = 1;
            }
        }

        else if(keypad == keytable[13]){
            if(!pressed){
                pressed = 1;
            }
        }
        //temporaray switch mode to playback
        else if(keypad == keytable[15]){
            if(!pressed){
                if(play_state<2){
                    play_state++;
                    if(play_state == 1){
                        new_recording = 1;
                    }
                }
                else{
                    init_squares();
                    play_state = 0;
                    default_melody();
                }
                if(play_state==2){
                    play_control = 1;
                }
                pressed = 1;
            }
        }

//        volatile int keytable[24]=
//    //        0     1      2    3     4     5     6      7    8     9    10-*  11-#
//            {0xd7, 0xee, 0xde, 0xbe, 0xed, 0xdd, 0xbd, 0xeb, 0xdb, 0xbb, 0xe7, 0xb7,
//    //        A     B      C    D
//             0x7e, 0x7d, 0x7b, 0x77};

        else if(keypad == keytable[14]){
            if(!pressed){
                if (sound_type < 3){
                    sound_type++;
                }
                else{
                    sound_type = 0;
                }
                //if (sound_type == 0) set_drum();
                //else if(sound_type == 1) set_struck_string();
                //else if(sound_type == 2) set_bowed_string();
                //else set_string_like();
                pressed = 1;
            }
        }
        else{
            pressed = 0;
            info_process = 1;
        }
        // draw key number
        if (i>-1 && i<10) sprintf(buffer,"   %x %d", keypad, i);
        if (i==10 ) sprintf(buffer,"   %x *", keypad);
        if (i==11 ) sprintf(buffer,"   %x #", keypad);
        if (i==12 ) sprintf(buffer,"   %x A", keypad);
        if (i==13 ) sprintf(buffer,"   %x B", keypad);
        if (i==14 ) sprintf(buffer,"   %x C", keypad);
        if (i==15 ) sprintf(buffer,"   %x D", keypad);
        if (i>-1 && i<12) printLine2(2, buffer, ILI9340_GREEN, ILI9340_BLACK);
        else if (i>-1) printLine2(2, buffer, ILI9340_RED, ILI9340_BLACK);
        // NEVER exit while
      } // END WHILE(1)
  PT_END(pt);
} // keypad thread

static PT_THREAD (protothread_record(struct pt *pt)){
    PT_BEGIN(pt);
    while(1){
        //if in record mode
        PT_YIELD_TIME_msec(30);
        if(new_recording == 1){
            layer = 0;

            int layer_clear = 0;
            for(layer_clear = 0; layer_clear<num_layers; layer_clear++){
                melody_index[layer_clear] = 0;
            }
            new_recording = 0;
        }
        if(play_state == 1){
            //special protection so that mode changes and sound type changes are not counted as notes
            if(pressed && info_process && keypad != 0xb7 && keypad != 0x77 && keypad != 0x7e && keypad != 0x7d &&  keypad != 0x7b && keypad != 0xe7 && keypad != 0xbb){
                info_process = 0;
                melody[layer][melody_index[layer]].sound_mode = mode;
                melody[layer][melody_index[layer]].sound_type = sound_type;
                melody[layer][melody_index[layer]].sound_main_freq = calculate_main_inc(keypad);
                melody[layer][melody_index[layer]].sound_mod_freq = calculate_mod_inc(keypad);
                test_var = calculate_main_inc(keypad);
                //the shift here is in order to get unique colors
                color_square(layer,melody_index[layer],keycolor(keypad));
                song_color[layer][melody_index[layer]].color = keycolor(keypad);
                if(melody_index[layer]<song_length-1){
                    melody_index[layer]++;
                }
                else{
                    if(layer<num_layers){
                        layer++;
                    }
                }
            }

            else if(pressed && keypad == 0x7d && info_process){
                info_process = 0;
                int repeat_length = melody_index[layer];
                int original_index = 0;
                int paste_index = repeat_length;
                while(paste_index<song_length){
                    melody[layer][paste_index] = melody[layer][original_index];
                    color_square(layer, paste_index, song_color[layer][original_index].color);
                    paste_index++;
                    original_index++;
                    if(original_index>=repeat_length){original_index=0;}
                }
                layer++;
            }
            if(layer>=num_layers){
                play_state=2;
                play_control = 1;
                current_note = 0;
            }
        }
    }
    PT_END(pt);
}

static PT_THREAD (protothread_playback(struct pt *pt)){
    PT_BEGIN(pt);
    while(1){
       PT_YIELD_UNTIL(pt,play_state == 2);
       if(play_state == 2){

            for(current_note = 0; current_note<song_length;current_note++){
                //TODO: set_sound_mode(melody[current_layer][current_sound].sound_mode)
                //set_sound_type(melody[current_layer][current_sound].sound_type);
//                restart_song();
                //do not move onto next song until the song is dune
                PT_YIELD_UNTIL(pt,done);
            }
       }
    }
    PT_END(pt);
}
// === Main  ======================================================
void main(void) {
 //SYSTEMConfigPerformance(PBCLK);
  block_height = (block_area_height-(layer_sep*(num_layers-1)))/num_layers;
  square_size = (320-2*side_buffer-dist_square*(song_length-1))/song_length;
  ANSELA = 0; ANSELB = 0;

  default_melody();

  // set up DAC on big board
  // timer interrupt //////////////////////////
    // Set up timer2 on,  interrupts, internal clock, prescalar 1, toggle rate
    // at 30 MHz PB clock 60 counts is two microsec
    // 400 is 100 ksamples/sec
    // 2000 is 20 ksamp/sec
    OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, 908);

    // set up the timer interrupt with a priority of 2
    ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2);
    mT2ClearIntFlag(); // and clear the interrupt flag

    // SCK2 is pin 26
    // SDO2 (MOSI) is in PPS output group 2, could be connected to RB5 which is pin 14
    PPSOutput(2, RPB5, SDO2);

    // control CS for DAC
    mPORTBSetPinsDigitalOut(BIT_4);
    mPORTBSetBits(BIT_4);

    // divide Fpb by 2, configure the I/O ports. Not using SS in this example
    // 16 bit transfer CKP=1 CKE=1
    // possibles SPI_OPEN_CKP_HIGH;   SPI_OPEN_SMP_END;  SPI_OPEN_CKE_REV
    // For any given peripherial, you will need to match these
    // clk divider set to 4 for 10 MHz
    SpiChnOpen(SPI_CHANNEL2, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV , 4);
  // end DAC setup

   int i;
   for (i = 0; i < sine_table_size; i++){
         sine_table[i] = (_Accum)(2047*sin((float)i*6.283/(float)sine_table_size));
   }

   // build the amplitude envelope parameters
   // bow parameters range check
	if (attack_time < 1) attack_time = 1;
	if (decay_time < 1) decay_time = 1;
	if (sustain_time < 1) sustain_time = 1;
	// set up increments for calculating bow envelope
	attack_inc = max_amplitude/(_Accum)attack_time ;
	decay_inc = max_amplitude/(_Accum)decay_time ;

  // === config threads ==========
  // turns OFF UART support and debugger pin, unless defines are set
  PT_setup();

  // === setup system wide interrupts  ========
  INTEnableSystemMultiVectoredInt();

  // init the threads
  PT_INIT(&pt_timer);
  PT_INIT(&pt_color);
  PT_INIT(&pt_anim);
  PT_INIT(&pt_key);
  PT_INIT(&pt_record);
  PT_INIT(&pt_playback);

  // init the display
  // NOTE that this init assumes SPI channel 1 connections
  tft_init_hw();
  tft_begin();
  tft_fillScreen(ILI9340_BLACK);
  //240x320 vertical display
  tft_setRotation(1); // Use tft_setRotation(1) for 320x240

  init_squares();
  // seed random color
  srand(1);

  // round-robin scheduler for threads
  while (1){
      PT_SCHEDULE(protothread_timer(&pt_timer));
      PT_SCHEDULE(protothread_color(&pt_color));
//      PT_SCHEDULE(protothread_anim(&pt_anim));
      PT_SCHEDULE(protothread_key(&pt_key));
      PT_SCHEDULE(protothread_record(&pt_record));
//      PT_SCHEDULE(protothread_playback(&pt_playback));
 }
} // main

// === end  ======================================================

Cost

We did not buy any extra components not already provided in class

Work Done

Most of the work was done together in lab. Few things were coded independently. There were times when one team member. Some things were developed independently however. Bradford: researched fm synthesis, coded in frequency modulation sound type change, coded keypad buttons for not variation. Nicholas did: Coded logic for repeat button, debugged accum sound glitches, changed ISR to controll note playback

References

The only refernece used was the code made by professor land on FM synthesis found at https://people.ece.cornell.edu/land/courses/ece4760/PIC32/index_sound_synth.html