Introduction:
We created our own version of Guitar Hero which can play any song that has a MIDI file by using our custom controller and UI. We are big fans of the original Guitar Hero game, but felt limited by its inability to only play a set of songs. We came up with the solution, Guitar Hero MMMMDCCLX (4760 in Roman numerals). By using MIDI files, a way to process songs through keeping track of frequencies and timing, we were able to create a game that can play any song as long as we pre-process its MIDI file.
High Level Design:
Much of our design is based on the original Guitar Hero game, but because we do not directly use any code, artwork, or sound from the game, we did not infringe upon any rights that the game has. Our game therefore complies with IEEE standards as we are not reproducing this game we are simply making our own version for fun.
We knew that we did not want to hard code a set of songs into our game, so we decided to use MIDI files which we have been told have never been used in this class before. We can process a MIDI file into notes that our game can play, so that anyone playing our game can give us a file they want to play. We would have to code in a set of songs for our demo, of course, but if someone wanted to they would indeed be able to play any song they wanted. The MIDI files use no background math, which saves us CPU time.
We needed a way to synthesize the notes being played. At first we considered using Karplus-Strong string synthesis to create the sound, which would sound close to a real instrument. This would require a lot of dynamic calculations though, so we decided to use Direct Digital Synthesis instead. Although this is a tradeoff for the sound quality, we do save memory space and CPU time. In fact, we are only using about 4 kB per song with this method so we could store around 32 songs in flash memory (we have 128 kB).
One other challenge we needed to overcome was how to time the notes on the screen. We considered saving the time of each note and what time it appeared on the screen, but did not have time to complete the overall timing aspect in the end. Instead, we found a modified solution described in the Note Timing section. This was not a trade off for the GUI, as the notes still fell on time, but it was a trade off for the sound as the timing of all of our songs are currently the same.
Program/Hardware Design:
Software:
Python Script for MIDI Files
We used Python to translate the MIDI files into a format that our program could read. MIDI (Musical Instrument Digital Interface) files are used to store song information encoded into 2 byte lines where each line can take the form of different messages telling the system how to behave. For our purposes, we only looked at the note on and note off messages because these contain the note number which we use to produce the corresponding sound from the game, and play the note on screen in the correct column. The note numbers go from 0 to 127 so by modding each number by 12 (the number of keys per octave), we could sort the notes into 5 bins to determine which color note the note should be in the game. We print the original note number and the modded note number in the following format to a .txt file that we directly cut and paste into our songs header file:
Direct Digital Synthesis and the DAC
First, we needed an array of frequencies to use for the Direct Digital Synthesis. We use frequencies that a piano plays to create an array to choose from for playing our songs. This, along with our song array are stored in a separate header file called sounds.h. At first, we wanted each song to have a separate array to choose from, however in using C we were not able to switch between the arrays appropriately. Instead, we had to combine all the songs into one array and keep track of their starting and ending points. Depending on which song the user selects, we start indexing at that certain part in the master songs array.
We use Direct Digital Synthesis (DDS) to create the music for the game. For DDS, we create a digital sine wave and then use the Digital to Analog Converter (DAC) to convert this to an analog output as sound from the speaker. To achieve this, we have a phase accumulator which is 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.
Button Debouncing
We had a few buttons that required debouncing. Debouncing is a way to make sure that the user actually pressed a button, and to make sure that multiple signals indicating a “press” only appear as a single long press in our program. Debouncing uses a switch statement with different states that indicate whether a button has been pressed, held down, or not pressed. We followed the pseudocode for debouncing provided by Professor Bruce Land on the course website. For each of the five note buttons and each of the limit switches, we have separate threads to keep track of their switch statements for if the button has been pressed. If a fret button is pressed, the corresponding circle at the bottom lights up with that color to indicate to the user that the button has been pressed. If a limit switch has been pressed, this sets a variable and allows the user to earn points if they are also pressing the correct fret.
Note Timing
Each note is separated by 200 ms in our implementation. This is controlled by using a PT_YIELD_TIME_msec(200 – (PT_GET_TIME() – (start time of the thread))) allowing each note to be very close to 200 ms apart. Even though the MIDI files contain timing information in them, we did not have time to implement this into our code. 200 ms is used because it produces music at approximately the right BPM for Sweet Child of Mine to sound correct, which was the original song we had running. In the future, we hope to integrate the timing information from the MIDI files to make each song sound more realistic. This would be pretty simple after we process the MIDI files to extract the timing information because we would simply replace the 200 in the yield time expression with the correct time each note should be played for.
Earning Points
To earn points, the user must be holding down the correct colored note while either strumming either up or down, all at the correct timing. The correct timing is when any part of the falling note is within the correct area at the bottom of the screen. To achieve this, we go through each row in our notes array to see if the correct button is being pressed while the falling note is within range of the circle at the bottom of the screen. The value of strumming up or strumming down must be true also. If the user is correct, the points increase by one and update at the top of the screen.
Graphics
We ran our GUI on 15 frames per second. We were able to get limited flickering with this frame rate. At first our circles for notes were larger than they currently are, but this caused some significant flickering. This is why we reduced the circle sizes to their current state, which you can see in this picture of the game play:
It was somewhat difficult to get a good image of Figure 1, as you can see some lag in the image. We added white dots in the center of each note to make it easier for the user to track whether or not they pressed the fret at the correct spot.
We needed a struct to keep track of the notes falling on the screen. Below is an overview of the note struct:
We create an array of notes at the beginning of the game with 20 notes in each of the 5 color columns. As we go through the song, we determine which column each note should be placed in based on the display note number in our song array. Depending on which column we need to put the note in, we find a note in the notes array in the corresponding column that is currently not being displayed and initialize it to be displayed. To initialize the note, we set its x value and display value so that it appears on the right side of the screen.We then go through the array in order, updating the x position to make the note move down the screen. If the user earns a point, the note will disappear when they earn the point. Otherwise, it will run the entire length of the screen until it goes off screen and we erase it.
We also needed to create a menu with menu options. Our menu is displayed when the program starts, and when the song is over and the game resets. The points from the last run of the game are displayed on the screen, so that when it first starts there are 0 points on the screen. See Figure 3 below for this design:
To get to the next screen, the user can press green the green fret button. This will take the user to a list of songs to choose from. To scroll through the songs, the user can use the strummer button which will correspondingly highlight a song in red. The user can press green to select the song they want which will take them to the next screen. See Figure 4 below for the song selection design:
The next screen, shown in Figure 5 below, displays the difficulty levels. The user can scroll through the difficulty levels to select one by using the green button, which will start the song and go to the Game Play screen. If the user decides they want to choose a different song to shred on, they can press the red button to go back and select a different song. When selecting a difficulty, the user is really selecting how many notes will be displayed on screen during the game. We mod the note number by either 1, 4, or 8 and check whether the result is 0 to determine whether or not to display the note. 1 is for hard because any number modulo 1 is 0 so each note that is heard is also displayed on the screen, and 8 is for easy because it displays every eighth note. Regardless of the difficulty, each note is played through the DAC so that the entire song is played, but the user will only have to play the notes according to the difficulty value.
State Machine:
We used state machines not only to debounce, but to store information for the menu. We use them for the difficulty, menu screen, and song. For difficulty, we had three states with easy, medium, and difficult. For menu screen, we have the main menu, songs menu, and difficulty menu. Lastly for the songs, we have our four songs that can be played. State Machines were extremely helpful because they allowed us to modify global variables that allowed us to display the correct menu on the screen, indicate to the user which option they were selecting, and change the settings for the other threads such as the difficulty and song variables to set how many notes the user would have to play, and for which song.
Hardware:
Button Circuit
For our note circuit, we used 5 push buttons from SparkFun and soldered them on a proto board. See Appendix B for the schematic of the hardware.
Strummer
We wanted to be able to recreate the classic strummer from the Guitar Hero game. This meant we needed some sort of rocker that rocks between two switches to indicate a strum up or a strum down. Bruce gave us two limit switches, which you can press up and down with a lever. We designed and 3D printed a chassis for the switches with the rocker to go on top. This is the final printed design:
Controller
To create the controller, we laser cut cardboard in the shape of a guitar. We laser cut 4 layers for structural integrity, and used hot glue to connect the pieces together. We were going to use acrylic for the guitar base, but this would put us over the budget. We cut a hole to put the strummer in. We then used heat shrink to cover the wires and make one long wire for both the buttons and the strummer. This was soldered to a small proto board which was used to plug into the breadboard when the user wanted to play. This can be seen in Figure 9 below.
Results
We found that our game was convincing and quite effective in terms of the number of songs we could store. By representing each note as only two ints, we found that each song was about 4 kB so we would be able to store around 32 songs. In addition to this, we were able to realize a difficulty selection system that only required a single int and a few clock cycles to calculate a modulo each 200 ms. By spending a significant amount of time working on the menu button interactions, we ended up with a menu system that was very easy to navigate. Holding down the red or green buttons would only count as a single button click meaning that you wouldn’t advance multiple screens accidentally by clicking too slowly. Similarly for the rocker we made sure that the cursor always could be moved with a single click.
For the game play, we were able to get 15 frames per second consistently throughout the game. Additionally, we combined the note display spawn thread with the sound generation thread so that we could guarantee the notes and sound were synced together. With our system of using bins to determine which of the five colors each note should be, we ended up with an even distribution of notes throughout the songs. As for the user inputs, we found that our method of polling the input ports for button inputs every 10 ms gave a very responsive game. When it appeared on the screen that the user had pressed the correct note and strummed at the correct time, the note would disappear and the points would increment.
Building our guitar controller:
Final demo:
Conclusions
Safety and Ethics
As far as safety is concerned, Guitar Hero MMMMDCCLX has a few, relatively minor considerations. The first is related to the physical, tactile nature of the buttons. We recognized that ergonomics plays a pivotal role in their layout and accessibility, realizing only when it was fully assembled that our design may induce slight cramping and difficulty playing the game. The buttons are spaced relatively far apart, a design decision that initially made it easier to play on a breadboard, but when mounted onboard the guitar, those with average-sized hands expressed difficulty after prolonged periods of play. Additionally the strum switch mechanism consists of two limit switches. Given the limited testing time available, we were unable to appropriately size a comfortable resistance for these switches. So, while they provide a very satisfying click as feedback, they are, at least initially, hard to strum.
Beyond the button interaction standpoint, we made use of the LCD TFT display. While a critical element of our gameplay involves moving shapes and changing colors, we made a concerted effort to minimize rapid flash-like animations, so as to reduce possible risk of inducing seizures in those prone to such events.
Such is also the case with accessibility for color-blind players. While we could have included a shape-based gameplay mode, we instead opted for a simpler layout of fret buttons corresponding directly to their on-screen order.
Our music in our program, while based upon commercially written titles, is synthesized from royalty-free MIDI files.
No radio communications were used in this project. As far as we know, no other regulatory body or other government regulations apply to this venture.
Our game reached our expectations by the end of the project. We were hoping to make a game that played and looked like Guitar Hero by using MIDI files and DDS for music generation. In the end, we ended up with a game that checked off all these boxes. It is fun and easy to play, has a start menu, can play multiple songs, and can play songs that are pretty recognizable through DDS.
In terms of meeting applicable standards, our game was very responsive to user inputs and was simple to play. The threads responsible for button inputs ran once every 10 ms which is significantly faster than a human can detect any latency. Additionally, our life-size guitar controller makes playing the game more similar to the original game. Unfortunately the buttons were a bit too spaced out as we have mentioned so it was slightly harder to play with this controller than with the Guitar Hero official guitars.
Future Modifications
To modify the game in the future, we would like to correct the timing for the notes so that we use the timing aspect of the MIDI file to play the songs. This is our main fix. We would also like to make the controller more user friendly by changing the buttons. It would also be nice to display the GUI on an actual monitor rather than the TFT, which is quite hard to see, although Bruce says this would be its own separate project! We would also like to eliminate the pre-processing aspect of our game, so that the C program can interact with the Python script such that we do not have to do multiple steps to play any song on the game. These are all potential additions if we decide to continue our project as an independent study, or side project, in the future.
Intellectual Property Concerns
We did not reuse anyone’s code nor did we use any code from the public domain. We were reverse engineering Guitar Hero, the video game, but since we did not use any of their code or use any of their artwork (only the idea behind the game), we should be safe from any copyright issues. Because our project is based on a game that is vastly popular our project is not worthy of a patent. Going into the future, if we add the Karplus-Strong synthesis algorithm and add more timing functionality, our game could definitely be publishable. We did not have to sign a non-disclosure form as we did not sample any parts.
Pictures:
Schematic
Commented MIDI Converter Code
from mido import MidiFile, Message, MidiTrack, MetaMessage | |
filename = “songname” | |
mid = MidiFile(‘midi/’+filename+‘.mid’) | |
#new_str = “” | |
notes_on = “” | |
notes_list = “” | |
notes = 0 | |
notes_mod = 0 | |
final_notes_list = [] | |
ones = 0 | |
twos = 0 | |
threes = 0 | |
fours = 0 | |
fives = 0 | |
notes_list2 = [] | |
for entries in mid.tracks[0v]: | |
new_str = str(entries) | |
#print new_str | |
note_onoff = new_str[0:8] | |
if ( note_onoff == “note_on “ ): | |
#notes_on = notes_on + str(entries) + ‘\n’ | |
#print new_str[23:25] | |
notes_list2.append(int(new_str[23:25])) | |
notes_list = new_str[23:25] | |
notes = int(notes_list) | |
notes_mod = notes % 12 | |
if ( notes_mod <= 1 ): | |
final_notes_list.append(1) | |
ones = ones + 1 | |
elif ( notes_mod <= 5 & notes_mod >=2 ): | |
final_notes_list.append(2) | |
twos = twos + 1 | |
elif ( notes_mod <= 7 & notes_mod >=6 ): | |
final_notes_list.append(3) | |
threes = threes + 1 | |
elif ( notes_mod == 8 ): | |
final_notes_list.append(4) | |
fours = fours + 1 | |
else: | |
final_notes_list.append(5) | |
fives = fives + 1 | |
#print notes_list2 | |
#print final_notes_list | |
file = open( filename+“.txt”, “w” ) | |
for i in range(len(notes_list2)/2): | |
file.write(str(“{ “) + str(final_notes_list[i*2]) + str(“, “) + str(notes_list2[i*2]) + str(” }, \n“)) | |
#print str(ones) + “, ” + str(twos) + “, ” + str(threes) + “, ” + str(fours) + “, ” + str(fives) |
Commented PIC32 Code
/* | |
* File: Guitar Hero MMMMDCCLX | |
* Author: Brian Dempsey, Katarina Martucci, Liam Patterson | |
* Target PIC: PIC32MX250F128B | |
* NOTE: Based on code written by Bruce Land. | |
*/ | |
//////////////////////////////////// | |
// clock AND protoThreads configure! | |
// You MUST check this file! | |
#include “config.h“ | |
// threading library | |
#include “pt_cornell_1_2.h“ | |
//////////////////////////////////// | |
// graphics libraries | |
#include “tft_master.h“ | |
#include “tft_gfx.h“ | |
#include “math.h“ | |
#include <stdlib.h> | |
#include “sounds.h“ | |
//////////////////////////////////// | |
// pullup/down macros for keypad | |
// PORT B | |
#define EnablePullDownB(bits) CNPUBCLR=bits; CNPDBSET=bits; | |
#define DisablePullDownB(bits) CNPDBCLR=bits; | |
#define EnablePullUpB(bits) CNPDBCLR=bits; CNPUBSET=bits; | |
#define DisablePullUpB(bits) CNPUBCLR=bits; | |
//PORT A | |
#define EnablePullDownA(bits) CNPUACLR=bits; CNPDASET=bits; | |
#define DisablePullDownA(bits) CNPDACLR=bits; | |
#define EnablePullUpA(bits) CNPDACLR=bits; CNPUASET=bits; | |
#define DisablePullUpA(bits) CNPUACLR=bits; | |
//////////////////////////////////// | |
#define NINETYDEG 420 | |
#define THIRTYDEG 500 | |
#define NEGTHIRTYDEG 360 | |
// DAC ISR | |
// A-channel, 1x, active | |
#define DAC_config_chan_A 0b0011000000000000 | |
// === thread structures ============================================ | |
// thread control structs | |
//print lock | |
static struct pt_sem print_sem ; | |
// note that UART input and output are threads | |
static struct pt pt_notehit, pt_print, pt_button1, pt_button2, | |
pt_button3, pt_button4, pt_button5, pt_draw, pt_time, pt_menu, | |
pt_spawnandsound, pt_rockerbuttondown, pt_rockerbuttonup; | |
// system 1 second interval tick | |
int sys_time_seconds ; | |
// variables to store whether buttons pressed | |
int greenPushed = 0; | |
int redPushed = 0; | |
int yellowPushed = 0; | |
int bluePushed = 0; | |
int orangePushed = 0; | |
// global variable telling how many points | |
int points = 0; | |
// variables to tell whether the menu is on | |
int menuOn = 1; | |
int done_with_menu = 0; | |
// note struct | |
typedef struct { | |
//position | |
int x; | |
int y; | |
// whether to display or not | |
int to_display; | |
//color of note | |
unsigned short color; | |
} note; | |
// maximum number of notes per row so that we don’t crash CPU | |
static const int num_per_row = 20; | |
// notes array, 5 notes for each color | |
note notes_array[5][20]; | |
//////////////////////////////////// | |
// DAC ISR | |
// A-channel, 1x, active | |
#define DAC_config_chan_A 0b0011000000000000 | |
// B-channel, 1x, active | |
#define DAC_config_chan_B 0b1011000000000000 | |
// DDS sine table | |
#define sine_table_size 256 | |
int sin_table[sine_table_size]; | |
// Frequency to Output | |
//static float Fout = 400.0; | |
// Sampling frequency | |
#define Fs 50000.0 | |
#define two32 4294967296.0 // 2^32 | |
int i; | |
//== Timer 2 interrupt handler =========================================== | |
// actual scaled DAC | |
volatile unsigned int DAC_data; | |
// the DDS units: | |
volatile unsigned int phase_accum_main1, phase_incr_main1=0; | |
// variables to help us keep track of which notes are on | |
int note_counter = 0, disp_note_counter = 0; | |
// difficulty setting | |
int difficulty = 4; | |
// enum to help us go through the menu state machine | |
typedef enum { | |
main_menu, | |
songs, | |
difficulty_menu | |
} menu_state; | |
// initialize menu_status | |
menu_state menu_status = main_menu; | |
// == Timer 2 ISR ===================================================== | |
// just toggles a pin for timing strobe | |
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void) | |
{ | |
phase_accum_main1 += phase_incr_main1 ; | |
// set DAC_to the sin_table value | |
DAC_data = (((sin_table[phase_accum_main1>>24]))); | |
// === Channel A ============= | |
// CS low to start transaction | |
mPORTBClearBits(BIT_4); // start transaction | |
// test for ready | |
//while (TxBufFullSPI2()); | |
// write to spi2 | |
WriteSPI2( DAC_config_chan_A | ( DAC_data + 2048 )); | |
while (SPI2STATbits.SPIBUSY); // wait for end of transaction | |
// CS high | |
mPORTBSetBits(BIT_4); // end transaction | |
mT2ClearIntFlag(); | |
} | |
int start_music = 0; | |
// where the song is contained | |
int start_index = 0, end_index = 0; | |
// time thread | |
static PT_THREAD ( protothread_time( struct pt *pt ) ) | |
{ | |
// static int spawn_loop; | |
PT_BEGIN(pt); | |
// wait while the menu is on | |
while( menuOn ) { | |
PT_YIELD_TIME_msec(10); | |
} | |
// once menu is off, wait 2 seconds for the notes to reach the bottom of the screen | |
PT_YIELD_TIME_msec(2000); | |
// now start playing the music | |
start_music = 1; | |
// start time of the thread | |
static int start_time_time = 0; | |
// while the menu is not on, increment the time by 200 milliseconds | |
while(!menuOn) { | |
// start time of the thread so we can increment the time by 200 ms | |
start_time_time = PT_GET_TIME(); | |
if ( !menuOn ) { | |
// increment | |
sys_time_seconds++ ; | |
} | |
// yield for exactly 200 ms | |
PT_YIELD_TIME_msec( 200 – ( PT_GET_TIME() – start_time_time ) ); | |
} | |
PT_END(pt); | |
} | |
// numbers of notes in each row to make sure we have less than 20 in each row | |
int num_green = 0, num_red = 0, num_yellow = 0, num_blue = 0, num_orange = 0; | |
//spawn thread to get notes on screen | |
int freq_to_play = 0; | |
static PT_THREAD (protothread_spawnandsound(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
// loop variables | |
static int spawn_loop; | |
static int spawn_loop_i = 0; | |
static int spawn_loop_break; | |
static int start_time_spawn = 0; | |
while(1) { | |
start_time_spawn = PT_GET_TIME(); | |
// only go into this if the menu is off | |
if ( !menuOn ) { | |
spawn_loop_break = 0; | |
// loop through the notes array | |
for ( spawn_loop_i = 0; spawn_loop_i < num_per_row && spawn_loop_break == 0; spawn_loop_i++ ) { | |
// check if we should be spawning a note | |
if ( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display == 0 && ( disp_note_counter % difficulty == 0 ) ) { | |
// put the note in the corresponding color and row | |
switch( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].color ) { | |
// green | |
case ILI9340_GREEN : | |
// check if the note is not currently displayed and the number of notes in this row is less than 20 | |
if ( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display == 0 && num_green < 20 ) { | |
// set the to display value to 1 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display = 1; | |
// set the x value to 240 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].x = 240; | |
// break out of the loop | |
spawn_loop_i = num_per_row; | |
spawn_loop_break = 1; | |
// add 1 to the number of green in the row | |
num_green += 1; | |
} | |
break; | |
// red | |
case ILI9340_RED : | |
// check if the note is not currently displayed and the number of notes in this row is less than 20 | |
if ( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display == 0 && num_red < 20 ) { | |
// set the to display value to 1 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display = 1; | |
// set the x value to 240 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].x = 240; | |
// break out of the loop | |
spawn_loop_i = num_per_row; | |
// add 1 to the number of red in the row | |
num_red += 1; | |
} | |
break; | |
// yellow | |
case ILI9340_YELLOW : | |
// check if the note is not currently displayed and the number of notes in this row is less than 20 | |
if ( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display == 0 && num_yellow < 20 ) { | |
// set the to display value to 1 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display = 1; | |
// set the x value to 240 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].x = 240; | |
// break out of the loop | |
spawn_loop_i = num_per_row; | |
// add 1 to the number of yellow in the row | |
num_yellow += 1; | |
} | |
break; | |
// blue | |
case ILI9340_BLUE : | |
// check if the note is not currently displayed and the number of notes in this row is less than 20 | |
if ( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display == 0 && num_blue < 20 ) { | |
// set the to display value to 1 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display = 1; | |
// set the x value to 240 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].x = 240; | |
// break out of the loop | |
spawn_loop_i = num_per_row; | |
// add 1 to the number of blue in the row | |
num_blue += 1; | |
} | |
break; | |
// orange | |
default: | |
// check if the note is not currently displayed and the number of notes in this row is less than 20 | |
if ( notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display == 0 && num_orange < 20 ) { | |
// set the to display value to 1 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].to_display = 1; | |
// set the x value to 240 | |
notes_array[SONGS[disp_note_counter][0] – 1][spawn_loop_i].x = 240; | |
// break out of the loop | |
spawn_loop_i = num_per_row; | |
// add 1 to the number of orange in the row | |
num_orange += 1; | |
} | |
break; | |
} | |
} | |
} | |
// if we should be starting the music, we give the ISR the correct value to play | |
// out of the DAC | |
if ( start_music ) { | |
freq_to_play = (int)freqs[SONGS[note_counter][1] – 15]; | |
phase_incr_main1 = (int)(freq_to_play*(float)two32/Fs); | |
note_counter += 1; | |
} | |
// if we are at the end of the song, go here | |
if ( note_counter == end_index – 10 ) { | |
// go back to the menu and reset all the values | |
menuOn = 1; | |
done_with_menu = 0; | |
menu_status = main_menu; | |
disp_note_counter = 0; | |
note_counter = 0; | |
freq_to_play = 0; phase_incr_main1 = 0; start_music = 0; | |
static int inner_spawn_loop = 0; | |
static int spawn_loop = 0; | |
for(spawn_loop = 0; spawn_loop < 5; spawn_loop++ ){ | |
for ( inner_spawn_loop = 0; inner_spawn_loop < num_per_row; inner_spawn_loop++ ) { | |
notes_array[spawn_loop][inner_spawn_loop].to_display = 0; | |
notes_array[spawn_loop][inner_spawn_loop].x = 240; | |
} | |
} | |
num_red = 0; num_green = 0; num_blue = 0; num_yellow = 0; num_orange = 0; | |
tft_fillRoundRect(0,0,400,400,1,ILI9340_BLACK); | |
} | |
else { | |
// increment the displayed note counter | |
disp_note_counter += 1; | |
} | |
} | |
// yield for exactly 200 msec | |
PT_YIELD_TIME_msec(200 – ( PT_GET_TIME() – start_time_spawn ) ); | |
} | |
PT_END(pt); | |
} | |
// thread time | |
int time_beginning = 0; | |
static PT_THREAD (protothread_draw(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
// loop variables | |
static int spawn_loop, inner_spawn_loop; | |
while(1){ | |
// get the time at the beginning of the thread | |
time_beginning = PT_GET_TIME(); | |
// only go into this thread if the menu is off | |
if ( !menuOn ) { | |
// loop through all the notes | |
for(spawn_loop = 0; spawn_loop < 5; spawn_loop++ ){ | |
for ( inner_spawn_loop = 0; inner_spawn_loop < num_per_row; inner_spawn_loop++ ) { | |
// if the notes are being displayed but their location is less than 10, they should be erased | |
if(notes_array[spawn_loop][inner_spawn_loop].to_display == 1 && notes_array[spawn_loop][inner_spawn_loop].x < 10 ) { | |
// set the to display value to 0 | |
notes_array[spawn_loop][inner_spawn_loop].to_display = 0; | |
// erase the note | |
tft_fillRoundRect(notes_array[spawn_loop][inner_spawn_loop].x, notes_array[spawn_loop][inner_spawn_loop].y, 20, 20, 7, ILI9340_BLACK); | |
// reduce the number of that color note | |
switch ( notes_array[spawn_loop][inner_spawn_loop].color ) { | |
// green | |
case ILI9340_GREEN : | |
num_green = num_green – 1; | |
break; | |
// red | |
case ILI9340_RED : | |
num_red = num_red – 1; | |
break; | |
// yellow | |
case ILI9340_YELLOW : | |
num_yellow = num_yellow – 1; | |
break; | |
// blue | |
case ILI9340_BLUE : | |
num_blue = num_blue – 1; | |
break; | |
// orange | |
default: | |
num_orange = num_orange – 1; | |
break; | |
} | |
} | |
// simply update the location of the note on the screen | |
if(notes_array[spawn_loop][inner_spawn_loop].to_display == 1){ | |
tft_fillRoundRect(notes_array[spawn_loop][inner_spawn_loop].x, notes_array[spawn_loop][inner_spawn_loop].y, 20, 20, 7, ILI9340_BLACK); | |
notes_array[spawn_loop][inner_spawn_loop].x = notes_array[spawn_loop][inner_spawn_loop].x – 8; | |
tft_fillRoundRect(notes_array[spawn_loop][inner_spawn_loop].x, notes_array[spawn_loop][inner_spawn_loop].y, 20, 20, 7, notes_array[spawn_loop][inner_spawn_loop].color); | |
tft_fillRoundRect(notes_array[spawn_loop][inner_spawn_loop].x+7, notes_array[spawn_loop][inner_spawn_loop].y+7, 6, 6, 1, ILI9340_WHITE); | |
} | |
} | |
} | |
} | |
// yield so we get as close to 15 FPS as possible | |
PT_YIELD_TIME_msec(67 – (PT_GET_TIME() – time_beginning) ); | |
} | |
PT_END(pt); | |
} | |
// the value of the button we read from the I/O | |
int button_val; | |
// Different states for our pushbutton debouncing state machine | |
typedef enum { | |
NoPush, | |
MaybePush, | |
Pushed, | |
MaybeNoPush | |
} State; | |
// Different states for user input modifications | |
typedef enum { | |
Pterm, | |
Iterm, | |
Dterm, | |
Angle | |
} button_push_state; | |
////////////// Button Debouncing Threads \\\\\\\\\\\\\\ | |
// All the threads are the same, only the first will be commented fully | |
State buttonState1 = NoPush; | |
// button 1 debouncing thread | |
static PT_THREAD (protothread_button1(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
while(1) { | |
// read the value of the I/O port | |
button_val = mPORTAReadBits( BIT_3 ) >> 3; | |
// debouncing switch case | |
switch( buttonState1 ) { | |
// if the button isn’t pushed | |
case NoPush: | |
// if the button is still pressed, go to MaybePush | |
if ( !button_val ) buttonState1 = MaybePush; | |
// otherwise stay at NoPush | |
else buttonState1 = NoPush; | |
break; | |
// if the button might be pressed | |
case MaybePush: | |
// check if the button is still pressed, | |
// go to Pushed and set greenPushed to 1 | |
if ( !button_val ) { | |
buttonState1 = Pushed; | |
if ( greenPushed == 0 ) { | |
greenPushed = 1; | |
} | |
else greenPushed = 0; | |
} | |
// otherwise go to NoPush | |
else buttonState1 = NoPush; | |
break; | |
// if the button is pushed | |
case Pushed: | |
// if the button is still pressed, stay here | |
if ( !button_val ) buttonState1 = Pushed; | |
// otherwise set greenPushed to 0 and go to MaybeNoPush state | |
else{ | |
greenPushed = 0; | |
buttonState1 = MaybeNoPush; | |
} | |
break; | |
// if the button might not be pressed anymore | |
case MaybeNoPush: | |
// if the button is pressed, go back to pushed state | |
if ( !button_val ) buttonState1 = Pushed; | |
// otherwise go to NoPush | |
else buttonState1 = NoPush; | |
break; | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 4 | |
// SAME AS THE ABOVE THREAD, NOT COMMENTED | |
State buttonState2 = NoPush; | |
// button 2 debouncing thread | |
static PT_THREAD (protothread_button2(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
while(1) { | |
button_val = mPORTAReadBits( BIT_4 ) >> 4; | |
switch( buttonState2 ) { | |
case NoPush: | |
if ( !button_val ) buttonState2 = MaybePush; | |
else buttonState2 = NoPush; | |
break; | |
case MaybePush: | |
if ( !button_val ) { | |
buttonState2 = Pushed; | |
if ( redPushed == 0 ) { | |
redPushed = 1; | |
} | |
else redPushed = 0; | |
} | |
else buttonState2 = NoPush; | |
break; | |
case Pushed: | |
if ( !button_val ) buttonState2 = Pushed; | |
else{ | |
redPushed = 0; | |
buttonState2 = MaybeNoPush; | |
} | |
break; | |
case MaybeNoPush: | |
if ( !button_val ) buttonState2 = Pushed; | |
else buttonState2 = NoPush; | |
break; | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 4 | |
// SAME AS THE ABOVE THREAD, NOT COMMENTED | |
State buttonState3 = NoPush; | |
// button 3 debouncing thread | |
static PT_THREAD (protothread_button3(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
while(1) { | |
button_val = mPORTAReadBits( BIT_2 ) >> 2; | |
switch( buttonState3 ) { | |
case NoPush: | |
if ( !button_val ) buttonState3 = MaybePush; | |
else buttonState3 = NoPush; | |
break; | |
case MaybePush: | |
if ( !button_val ) { | |
buttonState3 = Pushed; | |
if ( yellowPushed == 0 ) { | |
yellowPushed = 1; | |
} | |
else yellowPushed = 0; | |
} | |
else buttonState3 = NoPush; | |
break; | |
case Pushed: | |
if ( !button_val ) buttonState3 = Pushed; | |
else{ | |
yellowPushed = 0; | |
buttonState3 = MaybeNoPush; | |
} | |
break; | |
case MaybeNoPush: | |
if ( !button_val ) buttonState3 = Pushed; | |
else buttonState3 = NoPush; | |
break; | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 4 | |
// SAME AS THE ABOVE THREAD, NOT COMMENTED | |
State buttonState4 = NoPush; | |
// button 4 debouncing thread | |
static PT_THREAD (protothread_button4(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
while(1) { | |
button_val = mPORTAReadBits( BIT_1 ) >> 1; | |
switch( buttonState4 ) { | |
case NoPush: | |
if ( !button_val ) buttonState4 = MaybePush; | |
else buttonState4 = NoPush; | |
break; | |
case MaybePush: | |
if ( !button_val ) { | |
buttonState4 = Pushed; | |
if ( bluePushed == 0 ) { | |
bluePushed = 1; | |
} | |
else bluePushed = 0; | |
} | |
else buttonState4 = NoPush; | |
break; | |
case Pushed: | |
if ( !button_val ) buttonState4 = Pushed; | |
else{ | |
bluePushed = 0; | |
buttonState4 = MaybeNoPush; | |
} | |
break; | |
case MaybeNoPush: | |
if ( !button_val ) buttonState4 = Pushed; | |
else buttonState4 = NoPush; | |
break; | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 4 | |
// SAME AS THE ABOVE THREAD, NOT COMMENTED | |
State buttonState5 = NoPush; | |
// button 5 debouncing thread | |
static PT_THREAD (protothread_button5(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
while(1) { | |
button_val = mPORTBReadBits( BIT_7 ) >> 0; | |
switch( buttonState5 ) { | |
case NoPush: | |
if ( !button_val ) buttonState5 = MaybePush; | |
else buttonState5 = NoPush; | |
break; | |
case MaybePush: | |
if ( !button_val ) { | |
buttonState5 = Pushed; | |
if ( orangePushed == 0 ) { | |
orangePushed = 1; | |
} | |
else orangePushed = 0; | |
} | |
else buttonState5 = NoPush; | |
break; | |
case Pushed: | |
if ( !button_val ) buttonState5 = Pushed; | |
else{ | |
orangePushed = 0; | |
buttonState5 = MaybeNoPush; | |
} | |
break; | |
case MaybeNoPush: | |
if ( !button_val ) buttonState5 = Pushed; | |
else buttonState5 = NoPush; | |
break; | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 4 | |
// values to tell us whether the strummer is activated | |
int StrummerUpOn = 0; | |
int Strummermenu = 0; | |
// same idea as the above debouncing threads but we only keep the strummer value | |
// high for only one cycle. Also we have a handshake value called strummermenu | |
// that prevents changing the value of the strummer while the menu is on. This | |
// makes it much easier to use the menu, but after the game has started we turn | |
// this off so that you can’t hold the strummer down and continue to get points | |
State buttonState6 = NoPush; | |
// strummer debouncing thread | |
static PT_THREAD (protothread_rockerbuttonup(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
static int rockerup_val = 0; | |
while(1) { | |
if ( !Strummermenu ) { | |
// yield time 1 second | |
rockerup_val = mPORTBReadBits( BIT_13 ) >> 13; | |
switch( buttonState6 ) { | |
case NoPush: | |
if ( rockerup_val ) buttonState6 = MaybePush; | |
else buttonState6 = NoPush; | |
break; | |
case MaybePush: | |
if ( rockerup_val ) { | |
buttonState6 = Pushed; | |
if ( StrummerUpOn == 0 ) { | |
StrummerUpOn = 1; | |
if ( menuOn ) Strummermenu = 1; | |
} | |
else StrummerUpOn = 0; | |
} | |
else buttonState6 = NoPush; | |
break; | |
case Pushed: | |
StrummerUpOn = 0; | |
if ( rockerup_val ) buttonState6 = Pushed; | |
else{ | |
StrummerUpOn = 0; | |
buttonState6 = MaybeNoPush; | |
} | |
break; | |
case MaybeNoPush: | |
if ( rockerup_val ) buttonState6 = Pushed; | |
else buttonState6 = NoPush; | |
break; | |
} | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 6 | |
// SAME AS THE THREAD ABOVE | |
int StrummerDownOn = 0; | |
State buttonState7 = NoPush; | |
// strummer debouncing thread | |
static PT_THREAD (protothread_rockerbuttondown(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
static int rockerdown_val = 0; | |
while(1) { | |
if ( !Strummermenu ) { | |
// yield time 1 second | |
rockerdown_val = mPORTBReadBits( BIT_8 ); | |
switch( buttonState7 ) { | |
case NoPush: | |
if ( rockerdown_val ) buttonState7 = MaybePush; | |
else buttonState7 = NoPush; | |
break; | |
case MaybePush: | |
if ( rockerdown_val ) { | |
buttonState7 = Pushed; | |
if ( StrummerDownOn == 0 ) { | |
StrummerDownOn = 1; | |
if ( menuOn ) Strummermenu = 1; | |
} | |
else StrummerDownOn = 0; | |
} | |
else buttonState7 = NoPush; | |
break; | |
case Pushed: | |
StrummerDownOn = 0; | |
if ( rockerdown_val ) buttonState7 = Pushed; | |
else{ | |
StrummerDownOn = 0; | |
buttonState7 = MaybeNoPush; | |
} | |
break; | |
case MaybeNoPush: | |
if ( rockerdown_val ) buttonState7 = Pushed; | |
else buttonState7 = NoPush; | |
break; | |
} | |
} | |
PT_YIELD_TIME_msec(10); | |
// NEVER exit while | |
} // END WHILE(1) | |
PT_END(pt); | |
} // thread 7 | |
// string buffer to write | |
char buffer1[60]; | |
// check note hits | |
static PT_THREAD (protothread_notehit(struct pt *pt)) | |
{ | |
// initialize variables for hitting notes | |
static int green_hit = 0; | |
static int red_hit = 0; | |
static int yellow_hit = 0; | |
static int blue_hit = 0; | |
static int orange_hit = 0; | |
PT_BEGIN(pt); | |
while(1) { | |
// only run this thread if the menu is off | |
if ( !menuOn ) { | |
// iterate through the notes in each row | |
for ( i = 0; i < num_per_row; i++ ) { | |
// check if the notes in each row are in the correct position for a point to be gained and the user | |
// strums, give them a point and erase the note | |
if( greenPushed && (notes_array[0][i].x<40 && notes_array[0][i].x>0) && ( StrummerUpOn || StrummerDownOn ) ){ | |
points += 1; | |
notes_array[0][i].to_display = 0; | |
tft_fillRoundRect(notes_array[0][i].x, notes_array[0][i].y, 20, 20, 7, ILI9340_BLACK); | |
notes_array[0][i].x = 240; | |
num_green = num_green – 1; | |
} | |
else points += 0; | |
if ( redPushed && (notes_array[1][i].x<40 && notes_array[1][i].x>0 ) && ( StrummerUpOn || StrummerDownOn ) ) { | |
points += 1; | |
notes_array[1][i].to_display = 0; | |
tft_fillRoundRect(notes_array[1][i].x, notes_array[1][i].y, 20, 20, 7, ILI9340_BLACK); | |
notes_array[1][i].x = 240; | |
num_red = num_red – 1; | |
} | |
else points += 0; | |
if ( yellowPushed && (notes_array[2][i].x<40 && notes_array[2][i].x>0 ) && ( StrummerUpOn || StrummerDownOn ) ) { | |
points += 1; | |
notes_array[2][i].to_display = 0; | |
tft_fillRoundRect(notes_array[2][i].x, notes_array[2][i].y, 20, 20, 7, ILI9340_BLACK); | |
notes_array[2][i].x = 240; | |
num_yellow = num_yellow – 1; | |
} | |
else points += 0; | |
if ( bluePushed && (notes_array[3][i].x<40 && notes_array[3][i].x>0 ) && ( StrummerUpOn || StrummerDownOn )) { | |
points += 1; | |
notes_array[3][i].to_display = 0; | |
tft_fillRoundRect(notes_array[3][i].x, notes_array[3][i].y, 20, 20, 7, ILI9340_BLACK); | |
notes_array[3][i].x = 240; | |
num_blue = num_blue – 1; | |
} | |
else points += 0; | |
if ( orangePushed && (notes_array[4][i].x<40 && notes_array[4][i].x>0 ) && ( StrummerUpOn || StrummerDownOn ) ) { | |
points += 1; | |
notes_array[4][i].to_display = 0; | |
tft_fillRoundRect(notes_array[4][i].x, notes_array[4][i].y, 20, 20, 7, ILI9340_BLACK); | |
notes_array[4][i].x = 240; | |
num_orange = num_orange – 1; | |
} | |
else points += 0; | |
} | |
// print out the points | |
tft_setCursor(80, 10); tft_setTextSize(2); tft_setTextColor(ILI9340_WHITE); tft_setRotation(2); | |
sprintf(buffer1, “Points: %d“, points); | |
tft_writeString(buffer1); | |
tft_setRotation(1); | |
// NEVER exit while | |
} | |
PT_YIELD_TIME_msec(10); | |
} // END WHILE(1) | |
PT_END(pt); | |
} | |
// thread to print the frets at the bottom of the screen. If you are pressing | |
// the corresponding button on the guitar, the fret will light up | |
static PT_THREAD (protothread_print(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
while (1) { | |
if (!menuOn) { | |
tft_fillRoundRect(295,172, 15, 80, 1, ILI9340_BLACK); | |
tft_fillRoundRect(200, 100, 10, 10, 1, ILI9340_BLACK); | |
tft_fillRoundRect(18,15, 20, 20, 7, ILI9340_WHITE); | |
if ( greenPushed == 1 ) tft_fillRoundRect(18,15,20, 20, 7, ILI9340_GREEN); // x,y,w,h,radius,color | |
tft_fillRoundRect(18,63, 20, 20, 7, ILI9340_WHITE); | |
if ( redPushed == 1 ) tft_fillRoundRect(18,63, 20, 20, 7, ILI9340_RED); | |
tft_fillRoundRect(18,111, 20, 20, 7, ILI9340_WHITE); | |
if ( yellowPushed == 1 ) tft_fillRoundRect(18,111, 20, 20, 7, ILI9340_YELLOW); | |
tft_fillRoundRect(18,159, 20, 20, 7, ILI9340_WHITE); | |
if ( bluePushed == 1 ) tft_fillRoundRect(18,159, 20, 20, 7, ILI9340_BLUE); | |
tft_fillRoundRect(18,207, 20, 20, 7, ILI9340_WHITE); | |
if ( orangePushed == 1 ) tft_fillRoundRect(18,207, 20, 20, 7, 0xFB00); | |
} | |
PT_YIELD_TIME_msec(67); | |
} | |
PT_END(pt); | |
} // print thread | |
// enum to go with the state machine for difficulty setting | |
typedef enum { | |
easy, | |
medium, | |
hard | |
} difficulty_state; | |
// variable to help us move through the menu | |
int proceed = 0; | |
// enum to go with the state machine for songs to play | |
typedef enum { | |
Sweetchild, | |
Fireflies, | |
CliffsofDover, | |
Reptilia | |
} which_song; | |
// buffer to print the points | |
char buffer5123545[60]; | |
// start menu | |
static PT_THREAD (protothread_menu(struct pt *pt)) | |
{ | |
PT_BEGIN(pt); | |
// initialize variables for difficulty and song | |
static difficulty_state difficulty_setting = easy; | |
static which_song song_to_play = Sweetchild; | |
while(1) { | |
// run the menu while we aren’t done with the menu | |
while(menuOn && !done_with_menu) { | |
// if either of the buttons is still pressed and proceed is already | |
// 0, don’t continue | |
if ( ( redPushed | greenPushed ) && ( proceed == 0 ) ) { | |
proceed = 0; | |
} | |
else { | |
// otherwise set proceed to 1 and move on | |
proceed = 1; | |
// depends on where we currently are in the menu | |
switch( menu_status ) { | |
// if in main_menu, display welcome back message and points | |
case main_menu: | |
tft_setCursor(30, 10); tft_setTextSize(2); tft_setTextColor(ILI9340_WHITE); tft_setRotation(2); | |
tft_writeString(“Welcome back to:“); | |
tft_setCursor(55, 30); | |
tft_writeString(“Guitar Hero“); | |
tft_setCursor(65, 50); | |
tft_writeString(“MMMMDCCLX“); | |
tft_setCursor(40,70); | |
tft_writeString(“Previous Score:“); | |
tft_setCursor(110,90); | |
sprintf(buffer5123545, “%d“, points); | |
tft_writeString(buffer5123545); | |
// if the user pushes green, move onto songs | |
if ( greenPushed ) { | |
tft_fillRoundRect( 30,10, 200, 100, 1, ILI9340_BLACK ); | |
menu_status = songs; | |
proceed = 0; | |
} | |
// otherwise stay at main_menu | |
else { | |
menu_status = main_menu; | |
} | |
break; | |
// if we are on the songs menu | |
case songs: | |
// display the currently selected song in red and set | |
// the global song variable depending on selected song. | |
// Move up or down according to current song | |
switch( song_to_play ) { | |
case Sweetchild: | |
start_index = 0; | |
end_index = 689; | |
tft_setTextColor(ILI9340_RED); | |
tft_setCursor(30, 100); | |
tft_writeString(“Sweet Child“); | |
tft_setTextColor(ILI9340_WHITE); | |
tft_setCursor(30, 120); | |
tft_writeString(“Fireflies“); | |
tft_setCursor(30, 140); | |
tft_writeString(“Cliffs of Dover“); | |
tft_setCursor(30, 160); | |
tft_writeString(“Reptilia“); | |
if ( StrummerDownOn ) { | |
song_to_play = Fireflies; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
song_to_play = Reptilia; | |
Strummermenu = 0; | |
} | |
break; | |
case Fireflies: | |
start_index = 1233; | |
end_index = 1460; | |
tft_setCursor(30, 100); | |
tft_writeString(“Sweet Child“); | |
tft_setCursor(30, 120); | |
tft_setTextColor(ILI9340_RED); | |
tft_writeString(“Fireflies“); | |
tft_setTextColor(ILI9340_WHITE); | |
tft_setCursor(30, 140); | |
tft_writeString(“Cliffs of Dover“); | |
tft_setCursor(30, 160); | |
tft_writeString(“Reptilia“); | |
if ( StrummerDownOn ) { | |
song_to_play = CliffsofDover; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
song_to_play = Sweetchild; | |
Strummermenu = 0; | |
} | |
break; | |
case CliffsofDover: | |
start_index = 690; | |
end_index = 1232; | |
tft_setCursor(30, 100); | |
tft_writeString(“Sweet Child“); | |
tft_setCursor(30, 120); | |
tft_writeString(“Fireflies“); | |
tft_setCursor(30, 140); | |
tft_setTextColor(ILI9340_RED); | |
tft_writeString(“Cliffs of Dover“); | |
tft_setTextColor(ILI9340_WHITE); | |
tft_setCursor(30, 160); | |
tft_writeString(“Reptilia“); | |
if ( StrummerDownOn ) { | |
song_to_play = Reptilia; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
song_to_play = Fireflies; | |
Strummermenu = 0; | |
} | |
break; | |
case Reptilia: | |
start_index = 1461; | |
end_index = 2061; | |
tft_setCursor(30, 100); | |
tft_writeString(“Sweet Child“); | |
tft_setCursor(30, 120); | |
tft_writeString(“Fireflies“); | |
tft_setCursor(30, 140); | |
tft_writeString(“Cliffs of Dover“); | |
tft_setCursor(30, 160); | |
tft_setTextColor(ILI9340_RED); | |
tft_writeString(“Reptilia“); | |
tft_setTextColor(ILI9340_WHITE); | |
if ( StrummerDownOn ) { | |
song_to_play = Sweetchild; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
song_to_play = CliffsofDover; | |
Strummermenu = 0; | |
} | |
break; | |
default: | |
break; | |
} | |
if ( greenPushed ) { | |
tft_fillRoundRect( 30,100, 210, 80, 1, ILI9340_BLACK ); | |
menu_status = difficulty_menu; | |
proceed = 0; | |
disp_note_counter = start_index; | |
note_counter = start_index; | |
} | |
else if ( redPushed ) { | |
tft_fillRoundRect( 30,100, 210, 80, 1, ILI9340_BLACK ); | |
menu_status = main_menu; | |
proceed = 0; | |
} | |
break; | |
// if we are on the difficulty menu | |
case difficulty_menu: | |
// highlight the difficulty the user is hovering over | |
// and if they press green, set the global difficulty | |
// value. Move between the settings based on current | |
// position | |
switch ( difficulty_setting ) { | |
case easy: | |
difficulty = 8; | |
tft_setCursor(100, 100); | |
tft_setTextColor(ILI9340_RED); | |
tft_writeString(“Easy“); | |
tft_setTextColor(ILI9340_WHITE); | |
tft_setCursor(100, 120); | |
tft_writeString(“Medium“); | |
tft_setCursor(100, 140); | |
tft_writeString(“Hard“); | |
// down | |
if ( StrummerDownOn ) { | |
difficulty_setting = medium; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
difficulty_setting = hard; | |
Strummermenu = 0; | |
} | |
break; | |
case medium: | |
difficulty = 4; | |
tft_setCursor(100, 100); | |
tft_writeString(“Easy“); | |
tft_setCursor(100, 120); | |
tft_setTextColor(ILI9340_RED); | |
tft_writeString(“Medium“); | |
tft_setTextColor(ILI9340_WHITE); | |
tft_setCursor(100, 140); | |
tft_writeString(“Hard“); | |
// down | |
if ( StrummerDownOn ) { | |
difficulty_setting = hard; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
difficulty_setting = easy; | |
Strummermenu = 0; | |
} | |
break; | |
case hard: | |
difficulty = 1; | |
tft_setCursor(100, 100); | |
tft_writeString(“Easy“); | |
tft_setCursor(100, 120); | |
tft_writeString(“Medium“); | |
tft_setCursor(100, 140); | |
tft_setTextColor(ILI9340_RED); | |
tft_writeString(“Hard“); | |
tft_setTextColor(ILI9340_WHITE); | |
// down | |
if ( StrummerDownOn ) { | |
difficulty_setting = easy; | |
Strummermenu = 0; | |
} | |
//up | |
else if ( StrummerUpOn ) { | |
difficulty_setting = medium; | |
Strummermenu = 0; | |
} | |
break; | |
default: | |
difficulty_setting = easy; | |
break; | |
} | |
// if they press green, start the game | |
if ( greenPushed ) { | |
tft_fillRoundRect( 0, 0, 300, 300, 2, ILI9340_BLACK ); | |
tft_setRotation(1); | |
done_with_menu = 1; | |
menuOn = 0; | |
Strummermenu = 0; | |
points = 0; | |
} | |
else if ( redPushed ) { | |
tft_fillRoundRect( 100, 100, 80, 100, 2, ILI9340_BLACK ); | |
menu_status = songs; | |
proceed = 0; | |
} | |
break; | |
default: | |
menu_status = main_menu; | |
break; | |
} | |
} | |
PT_YIELD_TIME_msec(67); | |
} | |
} | |
PT_END(pt); | |
} // thread 4 | |
// === Main ====================================================== | |
int main(void) | |
{ | |
// === Config timer and output compare to make PWM ======== | |
// set up timer2 to generate the wave period — SET this to 1 mSec! | |
OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_1, 800); | |
// Need ISR to compute PID controller | |
ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2); | |
mT2ClearIntFlag(); // and clear the interrupt flag | |
// set up compare3 for PWM mode | |
//OpenOC3(OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE , pwm_on_time, pwm_on_time); // | |
// OC3 is PPS group 4, map to RPB9 (pin 18) | |
//PPSOutput(4, RPB9, OC3); | |
// === config the uart, DMA, vref, timer5 ISR =========== | |
PT_setup(); | |
// === setup system wide interrupts ==================== | |
INTEnableSystemMultiVectoredInt(); | |
// === set up i/o port pin =============================== | |
mPORTBSetPinsDigitalIn( BIT_13 | BIT_8 ); //Set port as output | |
EnablePullUpB( BIT_13 | BIT_8 ); | |
// init the display | |
tft_init_hw(); | |
tft_begin(); | |
tft_fillScreen(ILI9340_BLACK); | |
//240×320 vertical display | |
tft_setRotation(1); // Use tft_setRotation(1) for 320×240 | |
//tft_fillRoundRect(0,0, 320, 240, 1, ILI9340_GREEN);// x,y,w,h,radius,color | |
//bottom barrier | |
//tft_drawLine(80, 1, 80, 60, ILI9340_GREEN); | |
// Buttons | |
mPORTASetPinsDigitalIn( BIT_2 | BIT_3 ); | |
EnablePullUpA( BIT_2 | BIT_3 ); | |
// 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 2 for 20 MHz | |
SpiChnOpen(SPI_CHANNEL2, SPI_OPEN_ON | SPI_OPEN_MODE16 | SPI_OPEN_MSTEN | SPI_OPEN_CKE_REV , 2); | |
// end DAC setup | |
// initialize the notes | |
unsigned int color_array[5] = {ILI9340_GREEN, ILI9340_RED, ILI9340_YELLOW, ILI9340_BLUE, 0xFB00}; | |
int k; | |
for(k = 0; k < 5; k ++) { | |
for ( i = 0; i < num_per_row; i++ ) { | |
notes_array[k][i].x = 240; | |
notes_array[k][i].y = 15 + (48*k); | |
notes_array[k][i].color = color_array[k]; | |
notes_array[k][i].to_display = 0; | |
} | |
} | |
// generate the sine table | |
int ii; | |
for (ii = 0; ii < sine_table_size; ii++) { | |
sin_table[ii] = (int)(1023*sin((float)ii*6.283/(float)sine_table_size)); | |
} | |
// === now the threads =================================== | |
// init the threads | |
PT_INIT(&pt_print); | |
PT_INIT(&pt_button1); | |
PT_INIT(&pt_button2); | |
PT_INIT(&pt_button3); | |
PT_INIT(&pt_button4); | |
PT_INIT(&pt_button5); | |
PT_INIT(&pt_notehit); | |
PT_INIT(&pt_draw); | |
PT_INIT(&pt_time); | |
PT_INIT(&pt_menu); | |
PT_INIT(&pt_spawnandsound); | |
PT_INIT(&pt_rockerbuttondown); | |
PT_INIT(&pt_rockerbuttonup); | |
// schedule the threads | |
while(1) { | |
PT_SCHEDULE(protothread_print(&pt_menu)); | |
PT_SCHEDULE(protothread_print(&pt_print)); | |
PT_SCHEDULE(protothread_button1(&pt_button1)); | |
PT_SCHEDULE(protothread_button2(&pt_button2)); | |
PT_SCHEDULE(protothread_button3(&pt_button3)); | |
PT_SCHEDULE(protothread_button4(&pt_button4)); | |
PT_SCHEDULE(protothread_button5(&pt_button5)); | |
PT_SCHEDULE(protothread_notehit(&pt_notehit)); | |
PT_SCHEDULE(protothread_draw(&pt_draw)); | |
PT_SCHEDULE(protothread_time(&pt_time)); | |
PT_SCHEDULE(protothread_spawnandsound(&pt_spawnandsound)); | |
PT_SCHEDULE(protothread_rockerbuttondown(&pt_rockerbuttondown)); | |
PT_SCHEDULE(protothread_rockerbuttonup(&pt_rockerbuttonup)); | |
} | |
} // main |
Source: Guitar Hero MMMMDCCLX