// Guitar Hero Game played with a real guitar! // Copyright 2008 Les Hall // This software is protected by the GNU General Public License // parameters 1200 => int screen_width; // for positioning windows 800 => int screen_height; // for positioning windows 1.0 => float size; // for sizing things 2.0 => float max_size; // max scaling to perform 100 => int button_width; // default width of buttons 60 => int button_height; // default height of buttons 200 => int slider_width; // default width of sliders 60 => int slider_height; // default height of sliders 80 => int fret_button_width; // default width of fret buttons 66 => int fret_button_height; // default height of fret buttons 60 => int fret_button_x; // for positioning buttons 30 => int fret_button_y; // for positioning buttons 36 => int led_width; // default width of LEDs 36 => int led_height; // default height of LEDs 20 => int window_width; // width for window frame 50 => int window_height; // height for window frame 6 => int num_strings; // number of strings on guitar 6 => int max_num_strings; // maximum number of strings on guitar 12 => int num_frets; // number of frets to work with 25 => int max_num_frets; // maximum number of frets to work with 0 => int min_level; // minimum level number 2 => int max_level; // maximum level number // variables int player_score; // current score acheived by the player int player_level; // current level acheived by the player Event level_change; // fires when level has changed int exit; // one to exit program // the patch // the patch parameters // instantiate the classes introduction Intro; scoreboard ScoreBoard; fretboard FretBoard; level_fretboard LevelFretBoard; // start first level LevelFretBoard.play (); // time loop while (true) { if (exit == 1) { break; } 100::ms => now; } // cleanup FretBoard.close (); ScoreBoard.close (); // class for open strings level class level_fretboard { // parameters 0.5 => float chord_probability; // probability of generating a chord int string_number; // 0 thru 5, 5 is high E string, 0 is low E string 0.05 => float noise_threshold; // average signal must be this large or larger 5.0 => float signal_threshold; // notes must have this signal or higher [82.407, 87.31, 92.50, 98.00, 103.83, 110.00, 116.54, 123.47, 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94, 261.63, 277.18, 293.67, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88, 523.25, 554.37, 587.33, 622.25, 659.26, 698.46, 783.99, 830.61, 880.00, 932.33, 987.77, 1046.50, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91, 1479.98] @=> float string_frequencies[]; [0, 5, 10, 15, 19, 24] @=> int string_indices[]; // starting index of each string float string_table[num_strings][num_frets]; // the look-up table int time_delay; // time delay for this try 5 => int max_num_chords; // number of available chords [ [0, 2, 2, 1, 0, 0], [0, 0, 2, 2, 2, 0], [-1, 0, 0, 2, 3, 2], [3, 2, 0, 0, 0, 3], [3, 3, 2, 0, 1, 0] ] @=> int chords[][]; // the chord definitions ["E Major", "A Major", "D Major", "G Major", "C Major"] @=> string chord_names[]; // the names of the chords // variables float frequency; // the observed frequency float percent_error; // the error of the frequency int result; // 1 if correct note was struck int string; // indexes strings int fret; // indexes frets int frets[max_num_strings]; // indexes the frets int num_tries; // number of tries so far int num_successes; // number of successes so far int chord_selection; // indexes which chord was selected int num_frets_in_chord; // for setting detection threshold // the patch // comb filters and magnitude checkers Gain input[max_num_strings]; Gain sum[max_num_strings]; Delay K[max_num_strings]; Gain alpha[max_num_strings]; FullRect fwr[max_num_strings]; LPF lpf[max_num_strings]; Gain adder; for (0 => int string; string < max_num_strings; string++) { // feedback comb filter - picks off desired harmonics adc => input[string] => sum[string] => K[string] => alpha[string] => sum[string]; // magnitude checker sum[string] => fwr[string] => lpf[string] => adder; } adder => Gain divider => LPF output => blackhole; // reference signal magnitude adc => FullRect fwrx => LPF lpfx => divider; // success or failure buzzers SinOsc sinosc => dac; SqrOsc sqrosc => dac; // the patch parameters for (0 => int string; string < max_num_strings; string++) { // comb filters second / 20.0 => K[string].max; second / string_frequencies[0] => K[string].delay; 0.9 => alpha[string].gain; // magnitude checker 10 => lpf[string].freq; } 10 => lpfx.freq; 4 => divider.op; 20 => output.freq; // success or failure buzzers 880 => sinosc.freq; 110 => sqrosc.freq; 0 => sinosc.gain => sqrosc.gain; // build the string table for (0 => int string; string < num_strings; string++) { for (0 => int fret; fret < num_frets; fret++) { string_frequencies[string_indices[string]+fret] => string_table[string][fret]; } } // the play function fun void play () { while (true) { // decide if this is a chord or a note if ( (Std.rand2f (0, 1) < chord_probability) & (player_level > 0) ) { // choose chord Std.rand2 (0, max_num_chords - 1) => chord_selection; // fill frets with chord for (0 => int string; string < num_strings; string++) { chords[chord_selection][string] => frets[string]; } } else { // clear chord selection variable -1 => chord_selection; // clear fret indices for (0 => int string; string < num_strings; string++) { -1 => frets[string]; } // choose note Std.rand2 (0, num_strings - 1) => string; Std.rand2 (0, num_frets - 1) => fret; if (player_level == 0) { 0 => fret; } // fill frets with note fret => frets[string]; } // enable comb filters that have notes to detect 0 => num_frets_in_chord; for (0 => int string; string < num_strings; string++) { if (frets[string] >= 0) { 1 => input[string].gain; 1 +=> num_frets_in_chord; } else { 0 => input[string].gain; } } // set the comb filter delay times for (0 => int string; string < num_strings; string++) { if (frets[string] >= 0) { second / string_table[string][frets[string]] => K[string].delay; } else { second / string_table[0][0] => K[string].delay; } } // allow filters to settle 500::ms => now; // light up the button(s) for (0 => int string; string < num_strings; string++) { if (frets[string] >= 0) { FretBoard.push_button_fret (string, frets[string], 1); } } if (chord_selection >= 0) { FretBoard.chord_name (chord_names[chord_selection] + " Chord"); } // calculate time delay for this try (1000 * Math.exp (-num_tries / 50) ) $ int => time_delay; // time loop 0 => result; for (0 => int t; t < time_delay; t++) { // check to see if the correct signal is detected and signal is above noise level if ( (output.last () > signal_threshold * num_frets_in_chord) & (lpfx.last () > noise_threshold) ) { 1 +=> result; } // look for a few correct detections if (result >= 3) { break; } // break on exit if (exit == 1) { break; } // delay a bit 10::ms => now; } // remember how many tries have been made 1 +=> num_tries; // unlight the button for (0 => int string; string < num_strings; string++) { if (frets[string] >= 0) { FretBoard.push_button_fret (string, frets[string], 0); } } FretBoard.chord_name (""); // adjust score if (result >= 1) { player_level * player_level + 1 +=> player_score; 0.25 => sinosc.gain; 200::ms => now; 0.00 => sinosc.gain; 1 +=> num_successes; } else { 0.25 => sqrosc.gain; 200::ms => now; 0.00 => sqrosc.gain; } ScoreBoard.update_player_score (); // adjust player level according to score if ( (player_score > 10) & (player_level == 0) ) { 1 => player_level; ScoreBoard.update_player_level (); } // break on exit if (exit == 1) { break; } } } } // class for handling fretboard class fretboard { // variables int note; // indexes the note of this string int led_counter; // counts the LEDs // the fretboard window MAUI_View fret_view; fret_view.size (2 * led_width + (num_strings + 1) * fret_button_x * size, (num_frets + 1) * fret_button_y * size + button_height * size); fret_view.name ("Guitar Zero FretBoard"); // draw the fret buttons MAUI_Button button_fret[num_strings][num_frets]; for (0 => int string; string < num_strings; string++) { for (0 => int fret; fret < num_frets; fret++) { button_fret[string][fret].toggleType (); button_fret[string][fret].size (fret_button_width * size, fret_button_height * size); button_fret[string][fret].position (2 * led_width * size + string * fret_button_x * size, fret * fret_button_y * size); button_fret[string][fret].name (note_name (string, fret) ); fret_view.addElement (button_fret[string][fret]); } } // draw the fret name button MAUI_Button button_fret_name; button_fret_name.toggleType (); button_fret_name.size (num_strings * fret_button_x * size, button_height * size); button_fret_name.position (2 * led_width, (num_frets + 1) * fret_button_y * size); button_fret_name.name (""); fret_view.addElement (button_fret_name); // draw the fret markers MAUI_LED led_marker[12]; 0 => led_counter; for (0 => int fret; fret < num_frets; fret++) { if ( ( (fret % 12) == 3) | ( (fret % 12) == 5) | ( (fret % 12) == 7) | ( (fret % 12) == 9) ) { led_marker[led_counter].size (led_width, led_height); led_marker[led_counter].position (led_width / 2, led_height / 2 + fret * fret_button_y * size); led_marker[led_counter].color (led_marker[led_counter].blue); led_marker[led_counter].light (); fret_view.addElement (led_marker[led_counter]); led_counter++; } if ( ( (fret % 12) == 0) & (fret > 0) ) { led_marker[led_counter].size (led_width, led_height); led_marker[led_counter].position (0, led_height / 2 + fret * fret_button_y * size); led_marker[led_counter].color (led_marker[led_counter].blue); led_marker[led_counter].light (); fret_view.addElement (led_marker[led_counter]); led_counter++; led_marker[led_counter].size (led_width, led_height); led_marker[led_counter].position (led_width, led_height / 2 + fret * fret_button_y * size); led_marker[led_counter].color (led_marker[led_counter].blue); led_marker[led_counter].light (); fret_view.addElement (led_marker[led_counter]); led_counter++; } } // display the fretboard window fret_view.display (); // function to determine name of each fret position fun string note_name (int string, int fret) { // A A#Bb B C C#Db D D#Eb E F F#Gb G G#Ab A... // 0 1 2 3 4 5 6 7 8 9 10 11 0 // first find the note of the open string 7 => note; // default is E string if ( (string % 6) == 0) { 7 => note; } if ( (string % 6) == 1) { 0 => note; } if ( (string % 6) == 2) { 5 => note; } if ( (string % 6) == 3) { 10 => note; } if ( (string % 6) == 4) { 2 => note; } if ( (string % 6) == 5) { 7 => note; } // then add in the fret number fret +=> note; // and return the appropriate string if ( (note % 12) == 0) { return "A"; } if ( (note % 12) == 1) { return "A#Bb"; } if ( (note % 12) == 2) { return "B"; } if ( (note % 12) == 3) { return "C"; } if ( (note % 12) == 4) { return "C#Db"; } if ( (note % 12) == 5) { return "D"; } if ( (note % 12) == 6) { return "D#Eb"; } if ( (note % 12) == 7) { return "E"; } if ( (note % 12) == 8) { return "F"; } if ( (note % 12) == 9) { return "F#Gb"; } if ( (note % 12) == 10) { return "G"; } if ( (note % 12) == 11) { return "G#Ab"; } } // push a toggle button fun void push_button_fret (int string, int fret, int button_state) { button_fret[string][fret].state (button_state); } // name the chord fun void chord_name (string name) { button_fret_name.name (name); } // function to destroy the fretboard window fun void close () { fret_view.destroy (); } } // class for handling scoreboard class scoreboard { // the scoreboard window MAUI_View score_view; score_view.size (3 * button_width * size, 3 * button_height * size); score_view.position (0, screen_height); score_view.name ("Guitar Zero ScoreBoard"); // The exit button MAUI_Button button_exit; button_exit.pushType (); button_exit.size (button_width * size, button_height * size); button_exit.position (0, 0); button_exit.name ("Exit"); score_view.addElement (button_exit); // The score output button MAUI_Button button_score; button_score.pushType (); button_score.size (2 * button_width * size, button_height * size); button_score.position (button_width * size, 0); button_score.name ("" + player_score); score_view.addElement (button_score); // The level minus button MAUI_Button button_level_minus; button_level_minus.pushType (); button_level_minus.size (0.75 * button_width * size, button_height * size); button_level_minus.position (button_width * size, button_height * size); button_level_minus.name ("-"); score_view.addElement (button_level_minus); // The level output button MAUI_Button button_level; button_level.pushType (); button_level.size (button_width * size, button_height * size); button_level.position (1.5 * button_width * size, button_height * size); button_level.name ("Level " + player_level); score_view.addElement (button_level); // The level plus button MAUI_Button button_level_plus; button_level_plus.pushType (); button_level_plus.size (0.75 * button_width * size, button_height * size); button_level_plus.position (2.25 * button_width * size, button_height * size); button_level_plus.name ("+"); score_view.addElement (button_level_plus); // The credits button MAUI_Button button_credits; button_credits.pushType (); button_credits.size (2 * button_width * size, button_height * size); button_credits.position (button_width * size, 2 * button_height * size); button_credits.name ("Copyright 2008 Les Hall"); score_view.addElement (button_credits); // display the scoreboard window score_view.display (); // watch the level minus button spork ~ level_minus_button_watcher (); fun void level_minus_button_watcher () { while (true) { button_level_minus.onChange () => now; if (!button_level_minus.state ()) { 1 -=> player_level; if (player_level < min_level) { min_level => player_level; } update_player_level (); level_change.broadcast (); } } } // watch the level plus button spork ~ level_plus_button_watcher (); fun void level_plus_button_watcher () { while (true) { button_level_plus.onChange () => now; if (!button_level_plus.state ()) { 1 +=> player_level; if (player_level > max_level) { max_level => player_level; } update_player_level (); level_change.broadcast (); } } } // watch the exit button spork ~ exit_button_watcher (); fun void exit_button_watcher () { while (true) { button_exit.onChange () => now; if (!button_exit.state ()) { 1 => exit; } } } // update the level fun void update_player_level () { button_level.name ("Level " + player_level); } // update the score fun void update_player_score () { button_score.name ("" + player_score); } // function to destroy the scoreboard window fun void close () { score_view.destroy (); } } // class for handling introductory window class introduction { // the intro window MAUI_View intro_view; intro_view.size (slider_width * size, (2 * slider_height + button_height) * size); intro_view.position (screen_width / 2, screen_height / 2); intro_view.name ("Settings"); // slider to set the size MAUI_Slider slider_size; slider_size.range (1.0, max_size); slider_size.value (size); slider_size.size (slider_width * size, slider_height * size); slider_size.position (0, 0); slider_size.name ("Size Multiplier"); intro_view.addElement (slider_size); // slider to set the number of frets MAUI_Slider slider_num_frets; slider_num_frets.range (1, max_num_frets); slider_num_frets.value (num_frets); slider_num_frets.size (slider_width * size, slider_height * size); slider_num_frets.position (0, slider_height * size); slider_num_frets.displayFormat (slider_num_frets.integerFormat); slider_num_frets.name ("Number of Frets"); intro_view.addElement (slider_num_frets); // button to initialize the program parameters MAUI_Button button_initialize; button_initialize.pushType (); button_initialize.size (2 * button_width * size, button_height * size); button_initialize.position (0, 2 * slider_height * size); button_initialize.name ("Play Now"); intro_view.addElement (button_initialize); // display the intro window intro_view.display (); // wait for button press, then save parameters button_initialize => now; slider_size.value () => size; slider_num_frets.value () $ int + 1 => num_frets; // destroy the intro window intro_view.destroy (); }