Drum Master

Ye Kuang (yk749)

Haodong Ping(hp394)

Introduction

This project is a rhythm game consists of software preprocessing and playing MIDI file and running game logic, and hardware which are two homemade drum controllers and a piece of signal conditioning circuit. We proposed and implemented this project completely out of our love for video games and music.

High Level Design

Our design is mostly based on all kinds of popular rhythm game on the market but there is no direct copy of visual design or code. Also the MIDI files we use in this project are all free online samples without copyright concerns. Overall this project is implemented for the purpose of the course and also for fun.

The software part of this project consists of direct digital synthesis, game logic, GUI and MIDI file preprocessing. Direct digital synthesis is used for music notes play back, gamle logic controls the behavior of our rhythm game, GUI is the visual representation of our game and MIDI file preprocessing is a python code piece used for extracting certain channel of notes we want to use for the game.

The hardware part is two drum controllers connected to a piece of signal conditioning circuit. We attached two flex piezo sensors to two plastic bowls in order to use them as drum controller, and we put a piece of signal conditioning circuit between the controllers and PIC32’s ADC to increase the signal to noise ratio and also to enable us to distinguish hard beats from soft beats.

Software Design

Python parser for MIDI files:

We make use of an online open source python program to parse our midi files, with some tweaking we can easily parse a midi file and extracting instrument channels from the files. In our parsing program, we care about the note on information and the note lasting time. The note number go from 0 to 127, so we generate the corresponding frequency table and put it into music.h in PIC32. We print the drum note time information as an integer array from channel 10 and melody info from first nine channels.

Direct Digital Synthesis:

In our program, each song has the following structure. The guitar pointer point to the song melody note number array. The guiter_time pointer point to the melody note lasting time array.

Every time we play the melody of a song, we just traverse guitar array to get the note number and use this number to get the sound frequency from frequency table mentioned above. We also need to traverse the guitar time array to know how long the  note should last. After we know all those music information, we use Direct Digital Synthesis (DDS) to create the guitar sound for the game. To achieve this, we have a phase accumulator and DDS phase accumulator and both of them are increased by one every time an Interrupt Service Routine is called. We set up Timer 2 to trigger this ISR at 50kHz. Within the ISR, we are able to produce the sound. On each call of the ISR, we set the DAC data output to be the sine table indexed at the phase accumulator values shifted by 24. We shift by 24 so that the phase accumulator value is on the scale of 0 to 256. After this, we add 2048 to this value so that the values we output to the DAC range from 0 to 4096, then OR with the DAC control bits. Finally, we send this out over the SPI to the DAC.

Signal sampling:

We read the ADC channel within an Interrupt Service Routine, every time when the ADC reading value is greater than the threshold, we start to find the maximum value in next 500 timer interrupt. We use Timer 3 to trigger this ISR at 10kHz. Every time we get the maximum value, we may get the left or right beat value and according to the beat value to initialize the beat to soft or hard. The beat structure is shown below.

Beat value can be SOFT or HARD. The used indicate if the beat has been used in previous frames of the game.

Game Logic:

The game logic is pretty straightforward. The player needs to beat the correct side of drum with correct strength at the correct time. The correct time is when any part of the falling ball is within the correct area at the bottom of the screen. The bigger ball means we need to beat hard. In our program, we have a circular buffer whose size is 25 to store the current appearing balls on the screen. So we traverse the circular buffer to see if we beat correctly within the correct area. If the player beats correctly, the score on the top of the screen increases by one. Figure 1 is the picture of screen when playing the game.

Figure 1. Game Play

Visual Presentation:

Our game runs on 30 frames per second. We get the start time and end time of our game play thread. With those two values, we have a fixed frame value of our game.

We also have another thread to generate the note balls. Below is the structure of the beat ball. X and y is the position of the beat ball. Appeared flag indicates if the beat ball should be displayed on the screen. Hard indicate the size of the ball and color is the ball color.

We use a circular buffer to store those balls appearing on the screen. We traverse the drum time array to get the thread sleeping time, after that, we create a ball and put it into the circular buffer. The beat ball disappear when player earns one point or it goes off the screen.

We also have a menu to help the player choose a song and change the difficulty setting. The control law is very simple, beating the left side drum softly move the cursor down and beating right side drum move the cursor up. Beat random side of the drum hard can confirm or exit from the current menu. Figure 2 shows our main menu. The red part indicates the cursor position.

Figure 2. Main Menu

Hardware Design

Bongo Set:

Our bongo set contains two plastic bowls each contains a flex sensor, we have tried different materials (paper bowl, plastic cookie can lid, wooden board, ect) but at the end we chose plastic bowl as our bongo due to it has the best bouncing behavior among all the materials and can generate easy to process signal for later use. Figure 2 shows materials we tried to use as bongos.

Figure 3. Materials we tried as bongo

Signal Conditioning Circuit:

We built a signal conditioning circuit to preprocess signals generated from flex sensors, the circuit serves as a signal amplifier and low-pass filter, we made use of the non-inverting voltage amplifier, the voltage gain we used is 5 so even the slightest beat to the bongo can generate voltage around 100mv. Figure x show typical output of the conditioning circuit.

Figure 4. Non-Inverting voltage amplifier

Figure 5. Our signal conditioning circuit

Figure 6. Typical output the conditioning circuit


Result

After weeks of hard work, our game looks great at the end. Our game controllers are very sensitive (it takes 5ms to process each beat, so the maximum bpm is 200) and barely miss a beat, after some parameters tuning, each bongo controller can easily distinguish soft beats from hard beats. The game software features good looking visual presentation with stable 15 frames per second, also the control logic is very intuitive, players can easily navigate the game menu using our bongo controllers. Also with MIDI files paring and direct digital synthesis we can generate songs sound exactly like the original midi files. Our game provides three different difficulties, with each difficulty setting we assigned a probability to each note, for example, under easy difficulty, each note has only 30% chance of being chosen as a playable note, and under hard difficulty the chance is 80%, which will make the game very intense. The link below is our demo video, enjoy it !

https://www.youtube.com/watch?v=6C86kxek2Oo&feature=youtu.be

Figure 7. Our project’s final form

Conclusion

Analyzation

Overall our project meets our proposal specifications, we made full use of PIC32’s hardware to build our game, we used on chip ADC for signal sampling at 10k sample rates, and DAC for music playback using direct digital synthesis technique. Also we made full use of Pthread library to coordinate our threads for different tasks, and it’s very important for our project since we need to provide 15 frames of game graphics per second while keeping a ratherly high sample rate.  We optimized our code so each thread will do its job with minimum computation.

Possible future improvements

There are a few aspects we’d like to improve if we can redo this project. We think it’s better to make use of a higher level platform for music playback, since PIC32 only has two DAC channels, meaning we can only generate two different instruments sound at a time, it’s not enough to reconstruct many modern music pieces since there are always more than two instruments involved in a song. We think it’s a good idea to make use of Raspberry Pi for the music playback, we just need to write an easy communication protocol for PIC32 and Raspberry Pi and each time we play the game, PIC32 will send a command to Raspberry Pi to play the song, and with Raspberry Pi’s well built sound driver, the job can be done perfectly. Also by doing so we don’t need to store any music data in PIC32, which will definitely increase our music database capacity since Raspberry Pi has gigabytes of free storage.

Also since our game involves drawing circles on the TFT screen, sometimes the frame rate will drop if there are too many circles on the screen, the problem can be alleviated by writing our own efficient drawing library, since the provided graphic library is not very efficient according to what Bruce said from earlier Labs.

The music files we used in our game, while are all based on commercially written titles, are synthesized from royalty-free online sample MIDI files, and we do not intend to make any profit from our project.

Intellectual property considerations

We did not reuse anyone’s code nor did we use any code from the public domain. The game is built from scratch with some of our own code from earlier labs, the visual presentation of our game is also original and we believe our project is safe from any copyright issues.

Appendix A : Approvals

The group approves this report for inclusion on the course website.

The group approves the video for inclusion on the course Youtube channel.

Appendix B : Schematic

Appendix B : Cost List

Part

Vendor

Quantity

Price

Two plastic bowls

Ourself

2

free

PIC32MX 250F128B

Lab stock

1

$5

Resistors

Lab stock

6

$0.3

Breadboard

Lab stock

1

$6

MCP6242

Lab stock

1

$0.36

Flex sensor

Lab stock

2

$10

Speakers

Lab stock

1

$20

Total price : $41.66

Appendix D :Work Distribution

Ye Kuang : Circuit construction, Controller Construction, Game logic design

Haodong Ping: Game logic coding, MiDi file parsing

Appendix E :References

MiDi files download site

MCP6242 datasheet

Flex sensor datasheet

MiDi parser source

Appendix F :Commented Midi Parsing Code

from mido import MidiFile

songName = 'song.mid'

mid = MidiFile(songName)

id = 0

note_list = []

note_time_list = []

note_time_list.append(0)

for entries in mid.tracks[3]:

     if not entries.is_meta:

          ent = str(entries).split(' ')

          print(str(entries))

          if len(ent) == 5:

               if(ent[0] == 'note_on' and int(ent[2].split('=')[1]) >= 55):

                    note = ent[2].split('=')[1]

                    time = ent[4].split('=')[1]

                    note_time_list[id] = int((note_time_list[id] + int(time)) / 2)

                    if note_time_list[id] < 200:

                         note_time_list[id] = 200

                    n = int(note)

                    note_list.append(n)

                    id = id + 1

                    note_time_list.append(0)

               else:

                    time = ent[3].split('=')[1]

                    note_time_list[id] = note_time_list[id] + int(time)

note_time_list.remove(note_time_list[len(note_time_list) - 1])

print('timeLen:'+str(len(note_time_list)))

print('noteLen:'+str(len(note_list)))

print(note_time_list)

print(note_list)

Appendix G :Commented PIC32 Code

/*

 * File:        Drum Master

 

 * Author:      Haodong Ping, Ye Kuang

 * Target PIC:  PIC32MX250F128B

 

 * NOTE: Based on code written by Bruce Land.

 */

////////////////////////////////////

// clock AND protoThreads configure!

#include "config_1_3_2.h"

// threading library

#include "pt_cornell_1_3_2.h"

// the expander

#include "port_expander_brl4.h"

#include "music.h"

////////////////////////////////////

// graphics libraries

// SPI channel 1 connections to TFT

#include "tft_master.h"

#include "tft_gfx.h"

// need for rand function

#include <stdlib.h>

// need for sine function

#include <math.h>

// The fixed point types

#include <stdfix.h>

#include <stdio.h>

////////////////////////////////////

// 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);

////////////////////////////////////

// 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;

#define LEFTTHRESHOLD 30

#define RIGHTTHRESHOLD 30

#define LEFTSOFT 180

#define RIGHTSOFT 180

////////////////////////////////////

#define MAIN_MENU 0

#define DIFFICULTY 1

#define SONGSELECTION 2

#define SCORE 3

#define PLAY 4

#define NO_BEAT 0

#define SOFT_BEAT 1

#define HARD_BEAT 2

#define DIFFICULTY_HARD 800

#define DIFFICULTY_MEDIUM 500

#define DIFFICULTY_EASY 300

// string buffer

char buffer[60];

char score_buffer[60];

////////////////////////////////////

// Audio DAC ISR

// A-channel, 1x, active

#define DAC_config_chan_A 0b0011000000000000

// B-channel, 1x, active

#define DAC_config_chan_B 0b1011000000000000

// audio sample frequency

#define Fs 44000.0

// need this constant for setting DDS frequency

#define two32 4294967296.0 // 2^32

// sine lookup table for DDS

#define sine_table_size 256

#define LEFT 0

#define RIGHT 1

#define ON 1

#define OFF 0

#define LEFT_POS 183

#define RIGHT_POS 58

#define FALL_SPEED 6

#define BALL_SIZE 50

#define BALL_RADIUS 7

#define BIGBALL_RADIUS 10

volatile _Accum sine_table[sine_table_size] ;

//static int Fouts[8] = {262, 294, 330, 349, 392, 440, 494, 523};

static int soundFlag = 0;

// phase accumulator for DDS

volatile unsigned int DDS_phase ;

volatile unsigned int FM_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

volatile unsigned int DDS_increment = 0 ; //42949673 ;

volatile unsigned int FM_DDS_increment = 0 ; //42949673 ;

// waveform amplitude

volatile _Accum max_amplitude=2000;

// FM waveform amplitude

volatile _Accum FM_max_amplitude=2000;

// waveform amplitude envelope parameters

// rise/fall time envelope 44 kHz samples

volatile unsigned int attack_time=200, decay_time=35000, sustain_time=1000;

// FM waveform amplitude envelope parameters

volatile unsigned int FM_attack_time=200, FM_decay_time=5000, FM_sustain_time=1000 ;

//  0<= current_amplitude < 2048

volatile _Accum current_amplitude ;

volatile _Accum FM_current_amplitude = 0;

// amplitude change per sample during attack and decay

// no change during sustain

volatile _Accum attack_inc, decay_inc ;

volatile _Accum FM_attack_inc, FM_decay_inc ;

//== 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!!

// interrupt ticks since beginning of song or note

volatile unsigned int song_time, note_time ;

volatile unsigned int drum_index = 0;

volatile unsigned int guitar_index = 0;

volatile unsigned int max_drum_id, max_guitar_id;

volatile unsigned int drum_side = LEFT;

volatile unsigned int start_button = OFF;

volatile int left_sample_state;

volatile int right_sample_state;

volatile int menu_state;

volatile int menu_cursor;

volatile Music *current_song;

volatile int difficulty;

typedef struct ball {

    int x;

    int y;

    int appear;

    int color;

    int hard;

} Ball;

typedef struct drumBeat {

    int beat;

    int used;

} Beat;

volatile Beat left_beat, right_beat;

volatile Ball balls[BALL_SIZE];

volatile int cur_ballsize = 0;

volatile int circular_buffer[50];

volatile int left_id, left_max_val, left_temp_val;

volatile int right_id, right_max_val, right_temp_val;

volatile int score;

volatile int left_display_frame, right_display_frame;

void __ISR(_TIMER_3_VECTOR, ipl2) Timer3Handler(void){

        mT3ClearIntFlag();  

        left_temp_val = ReadADC10(0);

        right_temp_val = ReadADC10(1);

        if(left_temp_val > LEFTTHRESHOLD && left_sample_state == 0) {

            left_sample_state = 1;

            left_id = 0;

        }

        if(right_temp_val > RIGHTTHRESHOLD && right_sample_state == 0) {

            right_sample_state = 1;

            right_id = 0;

        }

           

       // circular_buffer[id] = temp_val;

        if(left_sample_state == 1) {

            left_max_val = left_temp_val > left_max_val ? left_temp_val : left_max_val;

            left_id++;

            if(left_id == 500) {

                if(left_max_val < LEFTTHRESHOLD)

                    left_beat.beat = NO_BEAT;

                else if(left_max_val >= LEFTTHRESHOLD && left_max_val < LEFTSOFT)

                    left_beat.beat = SOFT_BEAT;

                else

                    left_beat.beat = HARD_BEAT;

                           

                //left_beat.beat = left_max_val;

                left_beat.used = 0;

                left_max_val = 0;

                left_id = 0;

                left_sample_state = 0;

            }

        }

       

        if(right_sample_state == 1) {

            right_max_val = right_temp_val > right_max_val ? right_temp_val : right_max_val;

            right_id++;

            if(right_id == 500) {              

                if(right_max_val < LEFTTHRESHOLD)

                    right_beat.beat = NO_BEAT;

                else if(right_max_val >= LEFTTHRESHOLD && right_max_val < LEFTSOFT)

                    right_beat.beat = SOFT_BEAT;

                else

                    right_beat.beat = HARD_BEAT;

               // right_beat.beat = right_max_val;

                right_beat.used = 0;

                right_max_val = 0;

                right_id = 0;

                right_sample_state = 0;

            }

        }

}

void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void)

{

    int junk;

   

    mT2ClearIntFlag();

   

    // generate  sinewave

    // advance the phase

    DDS_phase += DDS_increment ;

        DAC_data_B = (int) max_amplitude  ;

       

        if (note_time < (attack_time + decay_time + sustain_time) && DDS_increment != 0){

             //envelope control

            current_amplitude = (note_time <= attack_time)?

            current_amplitude + attack_inc :

            (note_time <= attack_time + sustain_time)? current_amplitude:

                current_amplitude - decay_inc ;

           

            //FM envelope control

            FM_current_amplitude = (note_time <= FM_attack_time)?

            FM_current_amplitude + FM_attack_inc :

            (note_time <= FM_attack_time + FM_sustain_time)? FM_current_amplitude:

                FM_current_amplitude - FM_decay_inc ;

             

             if(note_time >= FM_attack_time + FM_sustain_time + FM_decay_time) {

                 FM_DDS_increment = 0;

                 FM_current_amplitude = 0;

             }

           

            //FM synthesis

             FM_DDS_phase += FM_DDS_increment;

             int FM_out = (int)(FM_current_amplitude*sine_table[FM_DDS_phase>>24]);

             DDS_phase += (FM_out << 16);

             

             DAC_data_A = (int)(current_amplitude*sine_table[DDS_phase>>24]) + 2048 ;

             DAC_data_B = (int)(FM_current_amplitude*sine_table[FM_DDS_phase>>24])/5 + 2048 ;

         } else {

             DAC_data_A = 2048;

             DDS_increment = 0;  

         }

//    

//    

     // 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

    note_time++ ;

    // 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    

}

void tft_initGame(){

    tft_drawLine(120,60,120,320,ILI9340_WHITE);

}

void redrawCircle(int left_hard, int right_hard){

    int left_ball_r = left_hard ? BIGBALL_RADIUS : BALL_RADIUS;

    int right_ball_r = right_hard ? BIGBALL_RADIUS : BALL_RADIUS;

    if(left_display_frame >= 3) {

        tft_fillCircle(LEFT_POS, 300, BIGBALL_RADIUS, ILI9340_BLACK);

        tft_drawCircle(LEFT_POS, 300, left_ball_r, ILI9340_WHITE);

    } else {

        tft_fillCircle(LEFT_POS, 300, left_ball_r, ILI9340_YELLOW);

    }

    if(right_display_frame >= 3) {

        tft_fillCircle(RIGHT_POS, 300, BIGBALL_RADIUS, ILI9340_BLACK);

        tft_drawCircle(RIGHT_POS, 300, right_ball_r, ILI9340_WHITE);

    } else {

        tft_fillCircle(RIGHT_POS, 300, right_ball_r, ILI9340_YELLOW);

    }

}

void updatePos(int x, int y, int color, int hard) {

    int r = hard ? BIGBALL_RADIUS : BALL_RADIUS;

    tft_fillCircle(x, y, r, ILI9340_BLACK); //x, y, radius, color

    switch (color) {

        case 0: tft_fillCircle(x, y + FALL_SPEED, r, ILI9340_RED); break;

        case 1: tft_fillCircle(x, y + FALL_SPEED, r, ILI9340_GREEN); break;

    }

   

}

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);

}

static struct pt pt_drum, pt_display, pt_guitar, pt_adc, pt_main_menu, pt_score_menu, pt_difficulty_menu, pt_song_menu;

static PT_THREAD (protothread_display(struct pt *pt)) {

    PT_BEGIN(pt);

    while(1) {

        if(menu_state != PLAY) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

        tft_initGame();

        int i;

        int left_min_dis, right_min_dis;

        int left_min_id, right_min_id;

        left_min_dis = 10000;

        right_min_dis = 10000;

        left_min_id = -1;

        right_min_id = -1;

        for(i = 0; i < cur_ballsize; i++) {

           // if(balls[i].y < 0) balls[i].appear = 0;

            if(balls[i].appear == 1) {

                updatePos(balls[i].x, balls[i].y, balls[i].color, balls[i].hard);                

                balls[i].y += FALL_SPEED;  

                if(balls[i].x == LEFT_POS) {

                    int left_dis = abs(balls[i].y - 300);

                    if(left_dis <= left_min_dis) {

                        left_min_dis = left_dis;

                        left_min_id = i;

                    }

                       

                }                    

                else if(balls[i].x == RIGHT_POS) {

                    int right_dis = abs(balls[i].y - 300);

                    if(right_dis <= right_min_dis) {

                        right_min_dis = right_dis ;

                        right_min_id = i;

                    }

                   

                }

                int r = balls[i].hard ? BIGBALL_RADIUS : BALL_RADIUS;

                if(balls[i].y <= 0) {

                    tft_fillCircle(balls[i].x, balls[i].y, r, ILI9340_BLACK);

                    balls[i].appear = 0;

                }

            }                

        }

        int left_hard = left_min_id == -1 ? 0 : balls[left_min_id].hard;

        int right_hard = right_min_id == -1 ? 0 : balls[right_min_id].hard;

        redrawCircle(left_hard, right_hard);

        if(left_beat.used == 0) {

            left_display_frame = 0;

            int r = left_hard ? BIGBALL_RADIUS : BALL_RADIUS;

            tft_fillCircle(LEFT_POS, 300, r, ILI9340_YELLOW);

            left_beat.used = 1;

            if(left_min_id != -1){

                if(left_min_dis <= (2 * r + 5) && left_hard == (left_beat.beat - 1)) {                    

                    score++;

                }                    

            }

        } else {

            left_display_frame = left_display_frame >= 3 ? 3 : left_display_frame + 1;

        }

       

        if(right_beat.used == 0) {

            right_display_frame = 0;

            int r = right_hard ? BIGBALL_RADIUS : BALL_RADIUS;

            tft_fillCircle(RIGHT_POS, 300, r, ILI9340_YELLOW);

            right_beat.used = 1;

            if(right_min_id != -1){

                if(right_min_dis <= (2 * r + 5)&& right_hard == (right_beat.beat - 1)) {                    

                    score++;

                }                    

            }

        } else {

            right_display_frame = right_display_frame >= 3 ? 3 : right_display_frame + 1;

        }

       

//        if(left_beat.beat == SOFT_BEAT)

//            sprintf(buffer,"left:soft:%d left_dis: %d score:%d", left_beat.beat, left_min_dis, score);

//        else if(left_beat.beat == HARD_BEAT)

//            sprintf(buffer,"left:hard:%d left_dis: %d score:%d",left_beat.beat, left_min_dis, score);

       

        tft_fillRect(145, 25, 60, 30, ILI9340_BLACK);

        tft_setCursor(80, 30);

        tft_setTextColor(ILI9340_WHITE);

        tft_setTextSize(2);

        sprintf(buffer,"Score: %d", score);

        tft_writeString(buffer);

       

        PT_YIELD_TIME_msec(15);

    }

    PT_END(pt);

}

static PT_THREAD (protothread_drum(struct pt *pt)) {

    PT_BEGIN(pt);

    static Ball temp;

    while(1) {                  

        if(menu_state != PLAY) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

        if(drum_index >= current_song->drum_len) {

            menu_state = SCORE;

            tft_fillScreen(ILI9340_BLACK);

            current_song->latest_score = score;

            current_song->highest_score = score > current_song->highest_score ?

                score : current_song->highest_score;

            continue;

        }

        if(current_song->drum_time[drum_index] != 0)

            PT_YIELD_TIME_msec(current_song->drum_time[drum_index]);

        if(current_song->drum_time[drum_index] < 300) {            

            drum_side = drum_side ? LEFT : RIGHT;            

        }

        //if()

        temp.x = drum_side ? RIGHT_POS : LEFT_POS;

        temp.y =  70;

        temp.appear = rand() % 1000 <= difficulty ? 1 : 0;

        temp.color = drum_side ? 1 : 0;

        temp.hard  = rand() % 1000 <= 200 ? 1 : 0;

           

        balls[drum_index % BALL_SIZE] = temp;

        cur_ballsize = cur_ballsize >= BALL_SIZE ? BALL_SIZE : cur_ballsize + 1;

       

        drum_index++;      

    }

    PT_END(pt);

}

static PT_THREAD (protothread_guitar(struct pt *pt)) {

    PT_BEGIN(pt);

    static Ball temp;

    while(1) {              

        if(menu_state != PLAY) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

        if(guitar_index >= current_song->guitar_len) {

            guitar_index = current_song->guitar_len;

            menu_state = SCORE;

            tft_fillScreen(ILI9340_BLACK);

            current_song->latest_score = score;

            current_song->highest_score = score > current_song->highest_score ?

                score : current_song->highest_score;

            continue;

        }

        int sleep_time = current_song->guitar_time[guitar_index] ;//>= 500 ?

        //    current_song->guitar_time[guitar_index] : 500;

       // if(current_song->guitar_time[guitar_index] >= 500)

            PT_YIELD_TIME_msec(sleep_time);

        note_time = 0;

        current_amplitude = 0;

        FM_current_amplitude = 0;

        int freq = Fouts[current_song->guitar[guitar_index]];

        DDS_increment = freq * two32/Fs;

        FM_DDS_increment = 3 * freq * two32/Fs ;

               

        guitar_index++;

       

    }

    PT_END(pt);

}

static PT_THREAD (protothread_main_menu(struct pt *pt)) {

    PT_BEGIN(pt);

    while(1) {

        if(menu_state != MAIN_MENU) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

       // sprintf(buffer,"left:%d    right:%d", left_beat.beat, right_beat.beat);

        printLine(0, buffer, ILI9340_WHITE, ILI9340_BLACK);

       // tft_fillScreen(ILI9340_BLACK);

        /*1. draw UI

          2. track the cursor */

        if(left_beat.used == 0 && left_beat.beat == SOFT_BEAT) {

            menu_cursor = (menu_cursor + 1) % 4;

        }

        else if(right_beat.used == 0 && right_beat.beat == SOFT_BEAT) {

            menu_cursor = menu_cursor == 0 ? 3 : menu_cursor - 1;

        }

        int pressed = (left_beat.used == 0 && left_beat.beat == HARD_BEAT) ||

        (right_beat.used == 0 && right_beat.beat == HARD_BEAT);

        left_beat.used = 1;

        right_beat.used = 1;

        switch (menu_cursor) {

            case 0:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_RED);

                tft_writeString("Start Game");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_WHITE);                              

                tft_writeString("Choose Songs");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Difficulty Setting");

               

                tft_setCursor(100, 160);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Score");

                if(pressed) {

                    menu_state = PLAY;

                    guitar_index = 0;

                    drum_index = 0;

                    cur_ballsize = 0;

                    score = 0;

                    tft_fillScreen(ILI9340_BLACK);

                }

                   

                   

                break;

            case 1:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Start Game");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_RED);                              

                tft_writeString("Choose Songs");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Difficulty Setting");

               

                tft_setCursor(100, 160);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Score");

                if(pressed) {

                    menu_state = SONGSELECTION;

                    tft_fillScreen(ILI9340_BLACK);

                    menu_cursor = 0;

                }

                   

                break;

               

            case 2:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Start Game");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_WHITE);                              

                tft_writeString("Choose Songs");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_RED);

                tft_writeString("Difficulty Setting");

               

                tft_setCursor(100, 160);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Score");

                if(pressed) {

                    menu_state = DIFFICULTY;

                    tft_fillScreen(ILI9340_BLACK);

                    menu_cursor = 0;

                }              

                break;    

            case 3:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Start Game");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_WHITE);                              

                tft_writeString("Choose Songs");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Difficulty Setting");

               

                tft_setCursor(100, 160);

                tft_setTextColor(ILI9340_RED);

                tft_writeString("Score");

                if(pressed) {

                    menu_state = SCORE;

                    tft_fillScreen(ILI9340_BLACK);

                }

                   

                break;  

        }

       

        PT_YIELD_TIME_msec(30);

    }

    PT_END(pt);

}

static PT_THREAD (protothread_score_menu(struct pt *pt)) {

    PT_BEGIN(pt);

    while(1) {

        if(menu_state != SCORE) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

        //sprintf(buffer,"left:%d    right:%d", left_beat.beat, right_beat.beat);

        printLine(0, buffer, ILI9340_WHITE, ILI9340_BLACK);

       // tft_fillScreen(ILI9340_BLACK);

        /*1. draw UI

          2. track the cursor */

        int pressed = (left_beat.used == 0 && left_beat.beat == HARD_BEAT) ||

        (right_beat.used == 0 && right_beat.beat == HARD_BEAT);

        left_beat.used = 1;

        right_beat.used = 1;

       

        if(pressed) {

            tft_fillScreen(ILI9340_BLACK);

            menu_state = MAIN_MENU;

            continue;

        }

        tft_setCursor(25, 100);

        tft_setTextColor(ILI9340_WHITE);

        tft_writeString("Song Name");      

        tft_setCursor(125, 100);

        tft_setTextColor(ILI9340_WHITE);

        tft_writeString("Highest");        

        tft_setCursor(175, 100);

        tft_setTextColor(ILI9340_WHITE);

        tft_writeString("Latest");

       

        tft_setCursor(25, 140);

        tft_setTextColor(ILI9340_WHITE);

        sprintf(score_buffer,"%s", song_1.name);

        tft_writeString(score_buffer);      

        tft_setCursor(125, 140);

        tft_setTextColor(ILI9340_WHITE);

        sprintf(score_buffer,"%d", song_1.highest_score);

        tft_writeString(score_buffer);        

        tft_setCursor(175, 140);

        tft_setTextColor(ILI9340_WHITE);

        sprintf(score_buffer,"%d", song_1.latest_score);

        tft_writeString(score_buffer);  

       

        tft_setCursor(25, 160);

        tft_setTextColor(ILI9340_WHITE);

        sprintf(score_buffer,"%s", song_2.name);

        tft_writeString(score_buffer);      

        tft_setCursor(125, 160);

        tft_setTextColor(ILI9340_WHITE);

        sprintf(score_buffer,"%d", song_2.highest_score);

        tft_writeString(score_buffer);        

        tft_setCursor(175, 160);

        tft_setTextColor(ILI9340_WHITE);

        sprintf(score_buffer,"%d", song_2.latest_score);

        tft_writeString(score_buffer);  

        PT_YIELD_TIME_msec(30);

    }

    PT_END(pt);

}

static PT_THREAD (protothread_difficulty_menu(struct pt *pt)) {

    PT_BEGIN(pt);

    while(1) {

        if(menu_state != DIFFICULTY) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

        sprintf(buffer,"");

        printLine(0, buffer, ILI9340_WHITE, ILI9340_BLACK);

       

        if(left_beat.used == 0 && left_beat.beat == SOFT_BEAT) {

            menu_cursor = (menu_cursor + 1) % 3;

        }

        else if(right_beat.used == 0 && right_beat.beat == SOFT_BEAT) {

            menu_cursor = menu_cursor == 0 ? 2 : menu_cursor - 1;

        }

        /*1. draw UI

          2. track the cursor */

        int pressed = (left_beat.used == 0 && left_beat.beat == HARD_BEAT) ||

        (right_beat.used == 0 && right_beat.beat == HARD_BEAT);

        left_beat.used = 1;

        right_beat.used = 1;

       

        switch (menu_cursor) {

            case 0:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_RED);

                tft_writeString("Easy");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_WHITE);                              

                tft_writeString("Medium");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Hard");

               

                if(pressed) {

                    menu_state = MAIN_MENU;

                    difficulty = DIFFICULTY_EASY;

                    menu_cursor = 2;

                    tft_fillScreen(ILI9340_BLACK);

                }

                   

                   

                break;

            case 1:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Easy");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_RED);                              

                tft_writeString("Medium");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Hard");

                if(pressed) {

                    menu_state = MAIN_MENU;

                    difficulty = DIFFICULTY_MEDIUM;

                    menu_cursor = 2;

                    tft_fillScreen(ILI9340_BLACK);

                }

                break;

               

            case 2:

                tft_setCursor(100, 100);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString("Easy");

               

                tft_setCursor(100, 120);            

                tft_setTextColor(ILI9340_WHITE);                              

                tft_writeString("Medium");

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_RED);

                tft_writeString("Hard");

                if(pressed) {

                    menu_state = MAIN_MENU;

                    difficulty = DIFFICULTY_HARD;

                    menu_cursor = 2;

                    tft_fillScreen(ILI9340_BLACK);

                }

                break;  

         

        PT_YIELD_TIME_msec(30);

    }

    PT_END(pt);

}

}

static PT_THREAD (protothread_song_menu(struct pt *pt)) {

    PT_BEGIN(pt);

    while(1) {

        if(menu_state != SONGSELECTION) {

            PT_YIELD_TIME_msec(60);

            continue;

        }

        sprintf(buffer,"");

        printLine(0, buffer, ILI9340_WHITE, ILI9340_BLACK);

       

        if(left_beat.used == 0 && left_beat.beat == SOFT_BEAT) {

            menu_cursor = (menu_cursor + 1) % 2;

        }

        else if(right_beat.used == 0 && right_beat.beat == SOFT_BEAT) {

            menu_cursor = menu_cursor == 0 ? 1 : menu_cursor - 1;

        }

        /*1. draw UI

          2. track the cursor */

        int pressed = (left_beat.used == 0 && left_beat.beat == HARD_BEAT) ||

        (right_beat.used == 0 && right_beat.beat == HARD_BEAT);

        left_beat.used = 1;

        right_beat.used = 1;

       

        switch (menu_cursor) {

            case 0:              

                tft_setCursor(100, 120);

                tft_setTextColor(ILI9340_RED);

                tft_writeString(song_1.name);

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString(song_2.name);

                               

                if(pressed) {

                    menu_state = MAIN_MENU;

                    current_song = &song_1;

                    menu_cursor = 1;

                    tft_fillScreen(ILI9340_BLACK);

                }                                        

                break;

               

            case 1:              

                tft_setCursor(100, 120);

                tft_setTextColor(ILI9340_WHITE);

                tft_writeString(song_1.name);

               

                tft_setCursor(100, 140);

                tft_setTextColor(ILI9340_RED);

                tft_writeString(song_2.name);

                               

                if(pressed) {

                    menu_state = MAIN_MENU;

                    current_song = &song_2;

                    menu_cursor = 1;

                    tft_fillScreen(ILI9340_BLACK);

                }                                        

                break;

         

        PT_YIELD_TIME_msec(30);

    }

    PT_END(pt);

}

}

void adc_init() {

    CloseADC10();        // ensure the ADC is off before setting the configuration

    #define PARAM1  ADC_FORMAT_INTG16 | ADC_CLK_AUTO | ADC_AUTO_SAMPLING_ON //

        // define setup parameters for OpenADC10

        // ADC ref external  | disable offset test | disable scan mode | do 1 sample | use single buf | alternate mode off

        #define PARAM2  ADC_VREF_AVDD_AVSS | ADC_OFFSET_CAL_DISABLE | ADC_SCAN_ON | ADC_SAMPLES_PER_INT_2 | ADC_ALT_BUF_OFF | ADC_ALT_INPUT_OFF

    #define PARAM3 ADC_CONV_CLK_PB | ADC_SAMPLE_TIME_15 | ADC_CONV_CLK_Tcy  

        // define setup parameters for OpenADC10

        // set AN11 and  as analog inputs

        #define PARAM4        ENABLE_AN11_ANA | ENABLE_AN5_ANA //

        // define setup parameters for OpenADC10

        // do not assign channels to scan

        #define PARAM5        SKIP_SCAN_AN0 | SKIP_SCAN_AN1 | SKIP_SCAN_AN2 | SKIP_SCAN_AN3 | SKIP_SCAN_AN4 | SKIP_SCAN_AN6 | SKIP_SCAN_AN7 | SKIP_SCAN_AN8 | SKIP_SCAN_AN9 | SKIP_SCAN_AN10 | SKIP_SCAN_AN12 | SKIP_SCAN_AN13 | SKIP_SCAN_AN14 | SKIP_SCAN_AN15

        // use ground as neg ref for A | use AN11 for input A    

        // configure to sample AN11

    SetChanADC10( ADC_CH0_NEG_SAMPLEA_NVREF); //

        OpenADC10( PARAM1, PARAM2, PARAM3, PARAM4, PARAM5 ); // configure ADC using the parameters defined above

        EnableADC10(); // Enable the ADC

}

// === Main  ======================================================

void main(void) {

 //SYSTEMConfigPerformance(PBCLK);

 

  ANSELA = 0; ANSELB = 0;

 

  // set up DAC on big board

  // timer interrupt //////////////////////////

    // Set up timer2 on,  interrupts, internal clock, prescalar 1, toggle rate

    // at 40 MHz PB clock

    // 40,000,000/Fs = 909 : since timer is zero-based, set to 908

    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

    OpenTimer3(T3_ON | T3_SOURCE_INT | T3_PS_1_1, 4000);

    ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_2);

    mT3ClearIntFlag(); // and clear the interrupt flag

   

//    OpenTimer4(T4_ON | T4_SOURCE_INT | T4_PS_1_1, 4000);

//    ConfigIntTimer4(T4_INT_ON | T4_INT_PRIOR_2);

//    mT4ClearIntFlag(); // 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

    adc_init();

   //

   // build the sine lookup table

   // scaled to produce values between 0 and 4096

   int i;

   for (i = 0; i < sine_table_size; i++){

        sine_table[i] = (_Accum)(sin((float)i*6.283/(float)sine_table_size));

    }

   

        // set up increments for calculating bow envelope

        attack_inc = max_amplitude/(_Accum)attack_time ;

        decay_inc = max_amplitude/(_Accum)decay_time ;

   

    // set up increments for calculating FM bow envelope

    FM_attack_inc = FM_max_amplitude/(_Accum)FM_attack_time ;

        FM_decay_inc = FM_max_amplitude/(_Accum)FM_decay_time ;

   

  // init the display

  // NOTE that this init assumes SPI channel 1 connections

  tft_init_hw();

  tft_begin();

  tft_fillScreen(ILI9340_BLACK);

  init_song();

  current_song = &song_1;

  left_sample_state = 0;

  right_sample_state = 0;

  score = 0;

  //240x320 vertical display

  tft_setRotation(0); // Use tft_setRotation(1) for 320x240

  // === setup system wide interrupts  ========

  INTEnableSystemMultiVectoredInt();

 

  // === config threads ==========

  // turns OFF UART support and debugger pin, unless defines are set

  PT_setup();

  // init the threads

  PT_INIT(&pt_drum);

  PT_INIT(&pt_guitar);

  PT_INIT(&pt_display);

  PT_INIT(&pt_main_menu);

  PT_INIT(&pt_score_menu);

  PT_INIT(&pt_difficulty_menu);

  PT_INIT(&pt_song_menu);

 

  start_button = ON;

  menu_state = MAIN_MENU;

  //menu_state = PLAY;

  menu_cursor = 0;

  left_beat.beat = 0;

  right_beat.beat = 0;

  left_beat.used = 0;

  right_beat.used = 0;

  difficulty = DIFFICULTY_MEDIUM;

 

  // round-robin scheduler for threads

  while (1){

      PT_SCHEDULE(protothread_drum(&pt_drum));

      PT_SCHEDULE(protothread_guitar(&pt_guitar));

      PT_SCHEDULE(protothread_display(&pt_display));

      PT_SCHEDULE(protothread_main_menu(&pt_main_menu));    

      PT_SCHEDULE(protothread_score_menu(&pt_score_menu));

      PT_SCHEDULE(protothread_difficulty_menu(&pt_difficulty_menu));  

      PT_SCHEDULE(protothread_song_menu(&pt_song_menu));

      }

  } // main

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