ECE 5725 Guitar Hero

May 16, 2018

by Jessica Lee (jal476) and Katherine Lin (kl639)

game setup image

Introduction

Our project is a version of Guitar Hero run on the Raspberry Pi. Players play on an actual Guitar Hero guitar as well as a drum set we created. Single player mode is played on the guitar. Multiplayer mode is played between two people, one on the guitar and one on the drums. While playing the guitar, a player must hit the strum bar at the same time they press the button in order to earn points. Players have the option of selecting from a variety of songs as well. We used pygame to display falling notes corresponding to the buttons on the guitar or drum. We keep track of a score and provide audio feedback as well as penalize negative points when the wrong notes are pressed.


Project Objectives


Design

Drums

We started by designing our entire system setup, as depicted above. Since we had access to a Guitar Hero guitar, we decided not to create a new prototype for the guitar. We prototyped the drums with advice from Professor Skovira. We have four drums in the set. Each drum is on a protoboard with three buttons wired in parallel so that if a user hits any one of the buttons, we detect the press. This is to improve the play experience and prevent false negatives. We prevented false positives by debouncing our buttons in the code. The circuits are pull up and modified from the circuit diagram in Lab 2. After we tested our circuit we soldered all our boards and secured them onto a foam base with four foam core pads as the drum tops.

We had some issues with our circuits while creating the drum pads. We initially used super glue to glue the pad onto the button tops. Unfortunately with two boards the glue seeped down into the cracks dried, holding our button down in a pressed position. We managed to pull the button back to an unpressed position, but the glue had broken our circuit and we had to resolder to boards. After resoldering our boards, we used tape instead of glue to secure the drum pads onto the buttons.

drums image circuit image buttons image

Images of our drum setup. Left: Complete drumset Middle: Soldered back Right: Buttons


Before we decided to resolder our board, we wanted to try to use the desoldering wick to pull out the broken buttons so that we wouldn't have to wire an entirely new protoboard. But because we used so much solder for each button connection it was difficult for the wick to remove everything. The wick started to remove the copper off the board and as we continued to heat it to try and remove solder we started to burn the wood in the protoboard. After that we decided to just remake the entire board.

desoldering attempt

Damaged board that we tried to desolder

We were initially unfamiliar with soldering. When we first began soldering, we soldered components to the back of the board (the side without the copper pads). We also thought we were supposed to melt the solder on the tip first before spreading the melted solder on the wire. We learned that we should hold the flux-cored wire to the component rather than melting the solder first. We also had issues with desoldering using the wick when we realized we had to fix some of the components on the perf-board. The perf-board was charred down to the wood in some parts and the copper pads were completely burnt off when we first began. However, we gained proficiency.


Guitar

Our guitar has a 6 pin PS2 connector so we acquired a PS2 to USB converter. This plugs into the pi. We managed to use the pygame.joystick library to read inputs from the guitar. We found that pressing the five colored buttons on the guitar created different JoyButtonUp events in pygame. The strum bar on the guitar created a JoyAxisMotion event. Running the command < pygame.joystick.get_count() > within our terminal in the terminal's Python interpreter returned 2. This is because the adapter has 2 ports and thus returns that 2 game controllers are connected regardless of if a game controller is actually connected to the adapter's port or not. We found that in order for the Pi to best detect controller inputs, we should always connect our guitar to the right the adapter port. This port corresponds to player one. We decided to only map the top four buttons on the guitar to four falling notes on our screen because we created four drums and wanted to keep the gameplay fair in multiplayer mode.

Before we decided to use a Guitar Hero guitar, we considered prototyping our own guitar on a wooden board. We had the idea of making a circuit with an exposed wire, where we would touch that wire to ground it. We would then be able to see the circuit be pulled high and read that as a user 'press'. Unfortunately our circuit did not work and then Professor Skovira found a guitar hero guitar for us so we decided to use that.

The guitar we are using is borrowed from a friend because we ran into some problems with our initial guitar. The pi was not registering any device connected when we plugged it in so we decided to test our PS2 to USB connector on another guitar. When the second guitar worked with the pi, we figured out that the old guitar we were using was out of batteries. Note that the PS2 to USB adapter has two ports.

In order to properly process inputs from the Guitar Hero guitar, we initially attempted to use a new image for our Pi system. We at first decided to flash a completely new copy of RetroPie to our SD card. RetroPie allows a user to customize their system to play games from multiple console systems (e.g: Atari, Nintendo). We thought it would be a good idea to leverage some of the things already inbuilt in the RetroPie system. However, when we used RetroPie we found that we had countless issues.

The first issue was that RetroPie did not come preinstalled with many libraries. Time was sunk into installing git, pygame, and various other libraries. We had already done installations for our previous kernel, so this was an issue unique to RetroPie.

The second issue was that we had difficulty getting RetroPie to recognize the drum set as a controller. Upon starting RetroPie, we were able to map inputs of the controller to different buttons corresponding to a regular Playstation controller such as buttons x, y, a and b. However, when we initially attempted to connect them both, we realized RetroPie did not come preinstalled with GPIO-associated libraries so we had install those in order to map them to controller actions. We also attempted to use GPIONext, a library fully compatible with Retropie that maps a gamepad to the Pi’s GPIO pins. However, this proved unsuccessful due to the presence of two controllers (the guitar and the pins themselves) which will be in the discussion of the third issue.

The third issue was that RetroPie refused to recognize the drums and the guitar as one device. Upon startup, RetroPie asks users to map user actions (e.g: pressing buttons x, y, a, and b) to inputs from the controller. The Pi was able to recognize and thus map guitar button presses to the appropriate user actions.However, it was unable to do so for the GPIO-based drums. Our theory is that the Pi automatically assumes that once a USB device is plugged in, it assumes all user actions come from the device connected from the USB and ignores pin inputs.

Main Menu Screen

start menu image


Our main menu screen is the first thing you see after launching the python game. The display is drawn with pygame. Our game is initialized with a song, multiplayer, and start variable. Hitting different text selections on the screen toggles different initialization variables. When "PLAY" is hit, the start variable is toggled and gameplay begins.

Gameplay

Our display is similar to Guitar Hero's display of falling notes. We drew these by initializing a pygame.sprite.Group(). Using sprites was a convenient way for us to call update functions on every note at once. We then added four notes to the group for the four falling notes per line. Each note added was of a note class that we created.


              class Note(pygame.sprite.Sprite):
                  def __init__(self, color, id):
                      super(Note, self).__init__()
                      self.image = pygame.image.load('blacksquare.png')
                      self.image.fill(color)
                      self.rect = self.image.get_rect()
                      self.col = random.randrange(0, NUM_COLS)
                      self.rect.y = 0
                      self.rect.x = RANDOM_COL[self.col]
                      self.hit = False
                      self.id = id
              

As depicted above, each note instance has a self.col attribute that determines what column on the display it is on. The notes are initialized into a random column. We call an update() method on each note at every iteration of our main gameplay while loop. The update() method will move the note down and check if the note has entered the valid 'hit zone' on the screen (50%-75% down on the screen). If so, any time the correct button press is detected the note will color itself the valid hit color. Otherwise, once the note passes the 75% mark the note will turn red if it hasn't been hit. At the bottom of the screen, our update() method resets the color to black and puts the note back in a random self.col.

singleplayer image
Single player screen
invalid notes image  
Multiplayer screen

Our single player and multiplayer are depicted above. In the multiplayer screen, the right side of the screen is associated with the player on the drums and the left with the guitar. Our single player features the same valid hit zone bar with only four notes on a row at a time. We also keep a running count of the score displayed on the screen.


We play music in the background using the pygame.mixer library. The .wav file in the song variable is loaded into one pygame.mixer channel. Another .wav file is loaded into a second mixer channel, and this sound plays whenever a wrong note is detected for extra audio feedback.

Gameplay Backend

Gameplay is executed in a main while loop during which we continually poll the locations of falling notes. When a note is within the valid hit zone, it will modify a global array hit_zone[self.col] = True, to indicate that the hit zone is active for a particular column. When the note exits the valid hit zone, it sets that value back to False.


We also run a for loop through pygame events in the while loop, looking for the JoyAxisMotion and JoyButtonUp events mentioned earlier. If a particular button associated with note x was hit, the code then checks whether hit_zone[x] == True. If so, a variable is written to a json file indicating that a note was successfully hit, where our json file at some point in time may look like:


              #a single player mode json
              {"0": false, "1": false, "2": true, "3": false}

              #a multiplayer mode json
              {"0": false, "1": true, "2": false, "3": false, "4": true, "5": false, "6": false, "7": false}
              


When a note hits its update() method again and is in the valid hit zone, it will load the json and check if the value at its self.col is True. If so, it will turn green (or gold in multiplayer mode). Finally, when a note hits the end of the screen a dump is performed to write everything in the json to false. We store our hit data in a separate json file because we initially expected our game to run two python files. We thought we would have a main gameplay file running the GUI while a second file ran in the background that would only look for and log inputs from our peripherals. The json file was a point of contact between these two processes. But as we developed, we realized the code could be consolidated into one file because the while loop ran fast enough that there was no delay between polling for inputs and checking those inputs against the falling note.

We had some issues with our json file when we tried expanding our code to support multiplayer. We discovered that json.dump(dict, dest) writes the contents of dict to dest and converts everything to a string. In our code, we were loading the dictionaries with keys as strings. But we accidentally wrote to them with keys as ints, and we expected that to remain the same in the loaded dictionary. This resulted in copies being made of dictionary entries in the json file, for example {"1":True, "1":False}. When we loaded from the file again to determine whether or not to turn a note green, we would load the wrong dictionary entry and so all the notes were False and would not turn green. We fixed this by using strings of ints as the keys and indexing by those strings.

For a long time, we ran into an inconsistent issue where the distance between two rows of notes fluctuated from game to game. We discovered the issue was with our game dependence on pygame.time.Clock() and a call to clock.tick(10) at every iteration of the while loop. We figured out the clock ticks speed must be changing each time because sometimes our rows of falling notes would appear right on top of one another whereas other times they were properly separated. We finally fixed this issue by changing to a variable 'clock' that mimics the time.clock but is incremented manually in our code clock_counter += 10.

End Screen

end screen image

We have a simple end screen that displays the winner of the game when the game is in multiplayer mode or the score of the player when in single player mode. Our end screen also gives players the option of returning to the initial start screen or exiting the game.

We had some trouble with bringing the player back to the start screen and originally wanted to use a FIFO with queued calls to our game python file. This did not work out as we realized we would need two terminal windows open and we couldn't get the subprocess command working properly. In the end we figured out we could use a call to execfile("guitarhero.py") to execute this functionality properly.


Drawings

design plan image
A diagram of how we planned to connect our system.



circuit plan image
A diagram of how our drums are wired.


Testing

We initially tested our code by developing both on the pi and on our laptops. We tested code by running the python file via terminal. On our laptops, we made a local version that played guitar hero but with inputs from the keyboard because it was more convenient to do the backend development on our laptops without having to deal with booting up the pi and wiring up all the extra devices.

Developing our circuits, we first made the circuit on the breadboard connected to our pi and ran a small script using the Rpi.GPIO library to ensure our pi was reading a button press on the correct GPIO pin for any of our three parallel buttons. We then put our circuit on the prototype board. Between each connection we soldered on the board, we used a multimeter to ensure that we had soldered it correctly. After each protoboard was done, we ran our simple script again to ensure that the entire circuit was working with the pi.

We wanted to test things as separately as possible and leave integration of parts as its own testing stage. Thus, testing of the UI (the python script) was done separately until integration of controller input. For example, in first implementing the descending notes, we realized that notes with different speeds would always collide with notes of dissimilar speeds. Professor Skovira suggested that we maintain consistent speed to avoid this problem, and the UI looked better as a result. We also used mouse clicks to mimic gameplay as we wanted integration to have its own testing stage.This proved to be a wise decision as it was more convenient to deal with the multiple bugs appearing. Among them were notes not being turned the correct color, notes not evenly distanced, descending notes with distance that varied too much, and notes not detecting when they were “hit.” We also had multiple discussions about dealing with UI related semantics as well after examining a test run of the script. For example, we discussed the advantages and disadvantages of the players being directed to hit the note when it reached the bottom of the screen after realizing it was more difficult to play. We decided to go with the approach of a “hit zone” for players to hit the note within so they could better see that they had missed or hit a note. However, this discussion would not have happened if not for our aggressive UI testing. Integration testing we attempted to do as rigorously as possible. During integration, we made sure to test the same edge cases for single player as we did for multiplayer. For example, for both single and multiplayer, we tested missing a note and hitting a different note while the two notes were in the hit zone. The list below includes most but not all test cases we examined:

Examining and rationalizing the responses of the UI to these test cases helped us to rigorously test our game. However, we were unsure that we had resolved every issue in the integration testing stage. We decided that we would at some point need testers who were not familiar with our game and would potentially perform user actions that were not obvious to us. We wanted to test incrementally so we tested the guitar separately as well. We wrote a python script with the pygame.joystick library to check that the guitar was properly connected. We wanted to ensure that the Pi received all guitar inputs correctly and that we mapped the correct events to the correct user actions. For example, the first button on the guitar was not indexed with 0 as we had expected. It was indexed with 5. The button indexed with 0 was actually the third button. We also attempted to rigorously test user actions. We wanted to know how the Pi would detect a user pressing four buttons together simultaneously or two buttons together along with the strum concurrently because we were concerned that the Pi would not detect all inputs. One functionality we were concerned about implementing was that the strum had to be pressed along with the button corresponding with the note on the screen due to the uncertainty about the Pi's response to concurrent user actions. However, we discovered that the Pi was able to detect both in quick succession, and so it was not an issue.

Once we had a finished project, we invited friends to test our product. We initially did not penalize for hitting incorrect notes. During the testing trial we figured out that players will exploit that and repeatedly hit every button so that they can achieve almost 100% accuracy without penalization. After that, we decided to include negative scoring.


Results, Conclusion, and Future Work

Our end result is a guitar hero game that works well. There is no noticeable lag between hitting buttons and visual or audio feedback. We learned a lot about how flexible pygame is and how we can customize it to process inputs from something like a video game controller. We also gained a lot of practice soldering boards. As we made the drum set we developed good habits creating organized, neat circuits.

We accomplished almost everything we set out to do. Our gameplay experience and input processing was a success.

The last thing we would do if we had more time is create a beat detection functionality. Currently, our falling notes are all randomly generated and have no correlation with the music selected to play in the background. Ideally, we would implement an FFT algorithm to create a set of falling notes that correspond to the tune of the music. Beyond that, we brainstormed several more instrument ideas that we could have prototyped such as a flute that used wind detection or a floor pedal to use in conjunction with the drums (the way we use a strum with the guitar). We spent a long time attempting to resolve issues with RetroPie and had become quite involved with the library already. We had already installed RetroPie twice - once directly from the site and once manually by recreating the stock RetroPie image on top of an already installed Raspbian Jessie image. We did not find the pygame.joystick library until we had fully committed to giving up on using RetroPie. We also wanted to commit to a more aggressive testing. This was a wise decision because we would have not found a lot of issues otherwise. Thus, we ended up with more refined product. We would have had more time to include an extension if not for issues with RetroPie and our decision to do more refined testing.


Work Distribution

Jessica Lee

jal476@cornell.edu

Designed the circuit and did the soldering. Also contributed to designing the software.

Katherine Lin

kl639@cornell.edu

Helped develop software backend and GUI. Contributed to soldering.


Parts List

Total: $45.00


References

Pygame Mixer Library
Pygame Joystick Library
Pygame Sprites Library
Bootstrap
R-Pi GPIO Document

Code Appendix


import pygame
import random
from pygame import *
import time
import json
import RPi.GPIO as GPIO

#GPIO setup
GPIO.setmode(GPIO.BCM)
GPIO.setup(19, GPIO.IN, pull_up_down = GPIO.PUD_UP);
GPIO.setup(13, GPIO.IN, pull_up_down = GPIO.PUD_UP);
GPIO.setup(6, GPIO.IN, pull_up_down = GPIO.PUD_UP);
GPIO.setup(5, GPIO.IN, pull_up_down = GPIO.PUD_UP);

#CONSTANTS
FINISH_SCREEN = False
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED =  (255, 51, 51)
GOLD = (212, 175, 55)
MAROON = (255, 51, 51)
SCREEN_HEIGHT = 500
SCREEN_WIDTH = 800
FONT_SIZE = 20
QUIT = {"Press to Quit":(3.7*SCREEN_WIDTH/4, 3.9*SCREEN_HEIGHT/4, BLACK)}
RESTART = {"Press to Restart":(3.7*SCREEN_WIDTH/4, 3.6*SCREEN_HEIGHT/4, BLACK)}

PLAYER_OPTIONS = {"PLAYER OPTIONS":(SCREEN_WIDTH/4, SCREEN_HEIGHT/5, GREEN),
                  "SINGLE PLAYER":(SCREEN_WIDTH/4, 2*SCREEN_HEIGHT/4, RED),
                  "MULTI PLAYER":(SCREEN_WIDTH/4, 3*SCREEN_HEIGHT/4, RED)}
TITLE = {"GUITARHERO":(SCREEN_WIDTH/2, 0.1*SCREEN_HEIGHT, GREEN)}
SONG_CHOICE = {"SONGS":(2*SCREEN_WIDTH/4, 1*SCREEN_HEIGHT/5, GREEN),
                "Demo Audio (0:41)": (2*SCREEN_WIDTH/4, 3*SCREEN_HEIGHT/10, WHITE),
                "Super Mario Theme Song (1:22)":  (2*SCREEN_WIDTH/4, 4*SCREEN_HEIGHT/10, WHITE),
                "Humble by Kendrick Lamar (2:58)":  (2*SCREEN_WIDTH/4, 5*SCREEN_HEIGHT/10, WHITE),
                "All Star by Smash Mouth (3:13)": (2*SCREEN_WIDTH/4, 6*SCREEN_HEIGHT/10, WHITE),
                "Never Gonna Give You Up (3:16)":  (2*SCREEN_WIDTH/4, 7*SCREEN_HEIGHT/10, WHITE),
                "Despacito by Justin Bieber (3:49)": (2*SCREEN_WIDTH/4, 8*SCREEN_HEIGHT/10, WHITE),
                "Get Lucky by Daft Punk (4:17)":  (2*SCREEN_WIDTH/4, 9*SCREEN_HEIGHT/10, WHITE)
                }
START = {"PLAY":(3*SCREEN_WIDTH/4, 3*SCREEN_HEIGHT/4, WHITE)}
SONG = 'allstar_smashmouth.wav'
BPM = 8
NUM_COLS = 4
MULTI_PLAYER_NUM_COLS = 8
RANDOM_COL = [1*SCREEN_WIDTH/(NUM_COLS+1), 2*SCREEN_WIDTH/(NUM_COLS+1),
              3*SCREEN_WIDTH/(NUM_COLS+1), 4*SCREEN_WIDTH/(NUM_COLS+1)]
MULTI_PLAYER_RANDOM_COL = [1*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1), 2*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1),
              3*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1), 4*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1),
              5*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1), 6*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1),
              7*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1), 8*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1)]
NOTES_ON_SCREEN = 4
MULTI_PLAYER_NOTES_ON_SCREEN = 8
GAME = True
STARTING = False
MULTI_PLAYER = True

def render_text(screen_text, screen):
        """
        renders text of tuple represented by screen_text onto the appropriate screen

        screen_text: tuple with text options (e.g: width, color)
        screen: pygame screen object, tuple of screen width and height
        """
        text_font = pygame.font.Font(None, FONT_SIZE)
        for text, text_options in screen_text:
            width = text_options[0]
            height = text_options[1]
            color = text_options[2]
            text_surface = text_font.render(text, True, color)
            screen.blit(text_surface, text_surface.get_rect(center=[width,height]))


#START MENU CODE
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
render_text(PLAYER_OPTIONS.items() + TITLE.items() + SONG_CHOICE.items() + START.items(), screen)

while(not STARTING):
    pygame.display.flip()

    for event in pygame.event.get():
        x,y = pygame.mouse.get_pos()
        if (x > SONG_CHOICE["Demo Audio (0:41)"][0] - 60 and x < SONG_CHOICE["Demo Audio (0:41)"][0] + 60 and y > SONG_CHOICE["Demo Audio (0:41)"][1] - 20 and y < SONG_CHOICE["Demo Audio (0:41)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: Demo Audio")
            SONG = 'sampleaudio.wav'
        if (x > SONG_CHOICE["Super Mario Theme Song (1:22)"][0] - 60 and x < SONG_CHOICE["Super Mario Theme Song (1:22)"][0] + 60 and y > SONG_CHOICE["Super Mario Theme Song (1:22)"][1] - 20 and y < SONG_CHOICE["Super Mario Theme Song (1:22)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: Super Mario Theme Song")
            SONG = 'supermario.wav'
        if (x > SONG_CHOICE["Humble by Kendrick Lamar (2:58)"][0] - 60 and x < SONG_CHOICE["Humble by Kendrick Lamar (2:58)"][0] + 60 and y > SONG_CHOICE["Humble by Kendrick Lamar (2:58)"][1] - 20 and y < SONG_CHOICE["Humble by Kendrick Lamar (2:58)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: Humble")
            SONG = 'humble_kendrick.wav'
        if (x > SONG_CHOICE["All Star by Smash Mouth (3:13)"][0] - 60 and x < SONG_CHOICE["All Star by Smash Mouth (3:13)"][0] + 60 and y > SONG_CHOICE["All Star by Smash Mouth (3:13)"][1] - 20 and y < SONG_CHOICE["All Star by Smash Mouth (3:13)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: All Star")
            SONG = 'allstar_smashmouth.wav'
        if (x > SONG_CHOICE["Never Gonna Give You Up (3:16)"][0] - 60 and x < SONG_CHOICE["Never Gonna Give You Up (3:16)"][0] + 60 and y > SONG_CHOICE["Never Gonna Give You Up (3:16)"][1] - 20 and y < SONG_CHOICE["Never Gonna Give You Up (3:16)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: Never Gonna Give You Up")
            SONG = 'giveyouup.wav'
        if (x > SONG_CHOICE["Despacito by Justin Bieber (3:49)"][0] - 60 and x < SONG_CHOICE["Despacito by Justin Bieber (3:49)"][0] + 60 and y > SONG_CHOICE["Despacito by Justin Bieber (3:49)"][1] - 20 and y < SONG_CHOICE["Despacito by Justin Bieber (3:49)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: Despacito")
            SONG = 'despacito_justin.wav'
        if (x > SONG_CHOICE["Get Lucky by Daft Punk (4:17)"][0] - 60 and x < SONG_CHOICE["Get Lucky by Daft Punk (4:17)"][0] + 60 and y > SONG_CHOICE["Get Lucky by Daft Punk (4:17)"][1] - 20 and y < SONG_CHOICE["Get Lucky by Daft Punk (4:17)"][1] + 20 ) and event.type is MOUSEBUTTONUP:
            print ("Song Selected: Get Lucky")
            SONG = 'getlucky_daftpunk.wav'
        if (x > PLAYER_OPTIONS["MULTI PLAYER"][0] - 50 and x < PLAYER_OPTIONS["MULTI PLAYER"][0] + 50 and y > PLAYER_OPTIONS["MULTI PLAYER"][1] - 50 and y < PLAYER_OPTIONS["MULTI PLAYER"][1] + 50) and event.type is MOUSEBUTTONUP:
            print ("MULTI PLAYER")
            MULTI_PLAYER = True
        if (x > PLAYER_OPTIONS["SINGLE PLAYER"][0] - 50 and x < PLAYER_OPTIONS["SINGLE PLAYER"][0] + 50 and y > PLAYER_OPTIONS["SINGLE PLAYER"][1] - 50 and y < PLAYER_OPTIONS["SINGLE PLAYER"][1] + 50) and event.type is MOUSEBUTTONUP:
            print ("SINGLE PLAYER")
            MULTI_PLAYER = False
        if (x > START["PLAY"][0] - 50 and x < START["PLAY"][0] + 50 and y > START["PLAY"][1] - 50 and y < START["PLAY"][1] + 50) and event.type is MOUSEBUTTONUP:
            print ("STARTING")
            STARTING=True

#VARIABLES SPECIFICALLY FOR GAMEPLAY
default = {0:False, 1:False, 2:False, 3:False}
hit_zone = [False, False, False, False]

multi_default = {0:False, 1:False, 2:False, 3:False, 4:False, 5:False, 6:False, 7:False}
multi_hit_zone = [False, False, False, False, False, False, False, False]

player1_score = 0
player2_score = 0

class Note(pygame.sprite.Sprite):
        """
        class to represent a descending note, superclass of Sprite inherited from pygame
        contains methods:
        ___init___ - initialize note
        getX - returns X position of note
        getID - returns ID of note
        update - updates position of note
        reset_pos - resets position of note once it reaches the bottom of the screen
        collide - manages collisions - with the hitzone, with the bottom of the screen
        """

    def __init__(self, color, id):
        super(Note, self).__init__()
        self.image = pygame.image.load('blacksquare.png')
        self.image.fill(color)
        self.rect = self.image.get_rect()
        self.col = random.randrange(0, NUM_COLS)
        self.rect.y = 0
        self.rect.x = RANDOM_COL[self.col]
        self.hit = False
        self.id = id

    def getX(self):
        return self.rect.x

    def getID(self):
        return self.id

    def update(self):
        self.rect.y += BPM
        self.collide()

    def reset_pos(self):
        self.image.fill(BLACK)
        self.col = random.randrange(0, NUM_COLS)
        self.rect.y = 0
        self.rect.x = RANDOM_COL[self.col]

    def collide(self):
        global hit_zone
        if self.rect.y > SCREEN_HEIGHT:
            self.reset_pos()
        elif self.rect.y >= SCREEN_HEIGHT*.75 and self.rect.y < SCREEN_HEIGHT*0.76:
            hit_zone = [False, False, False, False]
            with open('hits.json','r') as src:
                hits = json.load(src)

            if not hits[str(self.col)]:
                self.image.fill(RED)
            else:
                self.image.fill((0,0,255))

        elif self.rect.y >= SCREEN_HEIGHT*.78 and self.rect.y < SCREEN_HEIGHT*0.79:
            with open('hits.json', 'w+') as dest:
                json.dump(default,dest)

        elif self.rect.y >= SCREEN_HEIGHT*.49 and self.rect.y < SCREEN_HEIGHT*.75:
            hit_zone[self.col] = True
            with open('hits.json','r') as src:
                hits = json.load(src)
            if hits[str(self.col)]:
                self.image.fill((0,0,255))

class MultiNote(pygame.sprite.Sprite):
        """
        class to represent a descending note for multiplayer gameplay, superclass of Sprite inherited from pygame
        contains methods:
        ___init___ - initialize MultiNote
        getX - returns X position of MultiNote
        getID - returns ID of MultiNote
        update - updates position of MultiNote
        reset_pos - resets position of MultiNote once it reaches the bottom of the screen
        collide - manages collisions - with the hitzone, with the bottom of the screen
        """


    def __init__(self, color, id):
        super(MultiNote, self).__init__()
        self.image = pygame.image.load('blacksquare.png')
        self.rect = self.image.get_rect()
        self.col = random.randrange(0, MULTI_PLAYER_NUM_COLS)
        self.rect.y = 0
        self.rect.x = MULTI_PLAYER_RANDOM_COL[self.col]
        self.hit = False
        self.id = id

    def getX(self):
        return self.rect.x

    def getID(self):
        return self.id

    def update(self):
        self.rect.y += BPM
        self.collide()

    def reset_pos(self):
        self.image.fill(BLACK)
        self.col = random.randrange(0, MULTI_PLAYER_NUM_COLS)
        self.rect.y = 0
        self.rect.x = MULTI_PLAYER_RANDOM_COL[self.col]

    def collide(self):
        global multi_hit_zone
        if self.rect.y > SCREEN_HEIGHT:
            self.reset_pos()
        elif self.rect.y >= SCREEN_HEIGHT*.75 and self.rect.y < SCREEN_HEIGHT*0.76:
            multi_hit_zone = [False, False, False, False, False, False, False, False]
            with open('hits.json','r') as src:
                multi_hits = json.load(src)

            if not multi_hits[str(self.col)]:
                self.image.fill(RED)
            else:
                self.image.fill(GOLD)

        elif self.rect.y >= SCREEN_HEIGHT*.78 and self.rect.y < SCREEN_HEIGHT*0.79:
            with open('hits.json', 'w+') as dest:
                json.dump(multi_default,dest)

        elif self.rect.y >= SCREEN_HEIGHT*.49 and self.rect.y < SCREEN_HEIGHT*.75:
            multi_hit_zone[self.col] = True
            multiUpdate()
            with open('hits.json','r') as src:
                multi_hits = json.load(src)
            if multi_hits[str(self.col)]:
                self.image.fill(GOLD)


def createBackground():
        """
        called each iteration during gameplay to update background
        """
    screen.fill(WHITE)
    pygame.draw.rect(screen, (0,255,0), (0, SCREEN_HEIGHT*0.5, SCREEN_WIDTH,0.25*SCREEN_HEIGHT), 0)
    render_score()

def multiCreateBackground():
        """
        called each iteration during multiplayer gameplay to update background during multiplayer gameplay
        """
    screen.fill(WHITE)
    pygame.draw.rect(screen, (211,211,211), (SCREEN_WIDTH/2 + 10, 0, SCREEN_WIDTH/2, SCREEN_HEIGHT), 0)
    pygame.draw.rect(screen, (87,135,135), (0, SCREEN_HEIGHT*0.5,  SCREEN_WIDTH,0.25*SCREEN_HEIGHT), 0)
    render_score()

def render_score():
        """
        called during createBackground and multiCreateBackground to update player scores
        """
        global player1_score, player2_score
        render_text(QUIT.items(), screen)
        text_font = pygame.font.Font(None, 35)
        xpos = 0.5*SCREEN_WIDTH/(MULTI_PLAYER_NUM_COLS+1)
        ypos = 0.1*SCREEN_HEIGHT
        score1_string = str(player1_score)

        if MULTI_PLAYER:
            score2_string = str(player2_score)

            score1_text_surface = text_font.render(score1_string, True, BLACK)
            screen.blit(score1_text_surface, score1_text_surface.get_rect(center=[xpos,ypos]))
            score2_text_surface = text_font.render(score2_string, True, BLACK)
            screen.blit(score2_text_surface, score2_text_surface.get_rect(center=[SCREEN_WIDTH - 30, ypos]))
        else:
            text_surface = text_font.render(score1_string, True, BLACK)
            screen.blit(text_surface, text_surface.get_rect(center=[xpos,ypos]))


#MULTIPLAYER SETUP
if MULTI_PLAYER:
    with open('hits.json', 'w+') as dest:
        json.dump(multi_default, dest)

#GAMEPLAY SETUP
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
pygame.mixer.init(channels=1)

bg_channel = pygame.mixer.Channel(0)
no_channel = pygame.mixer.Channel(1)

bg_sound = pygame.mixer.Sound(SONG)
no_sound = pygame.mixer.Sound('no.wav')

bg_channel.play(bg_sound)

my_joystick = pygame.joystick.Joystick(0)
my_joystick.init()

mul4 = 0
mul5 = 0
mul6 = 0
mul7 = 0

def multiUpdate():
        """
        method to update UI response and scores for user-prompted joystick actions
        """
    global mul4, mul5, mul6, mul7, player1_score, GAME
    first = True
    b = 0
    l = []
    joy = False
    for event in pygame.event.get():
        x,y = pygame.mouse.get_pos()
        if (x > QUIT["Press to Quit"][0] - 50 and x < QUIT["Press to Quit"][0] + 50 and y > QUIT["Press to Quit"][1] - 50 and y < QUIT["Press to Quit"][1] + 50 ) and event.type is MOUSEBUTTONUP:
                GAME = False
                break
        if 'JoyAxisMotion' in str(event):
            joy = True
        l.append(str(event))


    if joy:
        for event in l:
            if first:
                with open('hits.json', 'r') as src:
                    b = json.load(src)

                first = False
            if 'JoyButtonUp' in str(event):
                if '5' in str(event) and multi_hit_zone[0]:
                    b["0"] = True
                    print("a set to true ")
                    player1_score += 10
                elif '5' in str(event) and not multi_hit_zone[0]:
                    no_channel.play(no_sound)
                    player1_score -= 10
                    print("WRONG A")

                if "'button': 1" in str(event) and multi_hit_zone[1]:
                    b["1"] = True
                    player1_score += 10
                elif "'button': 1" in str(event) and not multi_hit_zone[1]:
                    no_channel.play(no_sound)
                    player1_score -= 10
                    print("WRONG S")

                if "'button': 0" in str(event) and multi_hit_zone[2]:
                    b["2"] = True
                    player1_score += 10
                elif "'button': 0" in str(event) and not multi_hit_zone[2]:
                    no_channel.play(no_sound)
                    player1_score -= 10
                    print("WRONG D")

                if "'button': 2" in str(event) and multi_hit_zone[3]:
                    b["3"] = True
                    player1_score += 10
                elif "'button': 2" in str(event) and not multi_hit_zone[3]:
                    no_channel.play(no_sound)
                    player1_score -= 10
                    print("WRONG F")

    first = True
    if b != 0:
        with open('hits.json', 'w') as destination:
            json.dump(b, destination)


def updateCallback():
        """
        update scores and UI response for user-prompted drum action
        """
    global mul4, mul5,mul6, mul7, player2_score
    b = 0
    with open('hits.json', 'r') as src:
        b = json.load(src)
    if mul4 == 1:
        b['4'] = True
        player2_score += 10
    elif mul4 == 2:
        no_channel.play(no_sound)
        player2_score -= 10
        print("WRONG GPIO 5")

    if mul5 == 1:
        b['5'] = True
        player2_score += 10
    elif mul5 == 2:
        no_channel.play(no_sound)
        player2_score -= 10
        print("WRONG GPIO 6")

    if mul6 == 1:
        b['6'] = True
        player2_score += 10
    elif mul6 == 2:
        no_channel.play(no_sound)
        player2_score -= 10
        print("WRONG GPIO 13")

    if mul7 == 1:
        b['7'] = True
        player2_score += 10
    elif mul7 == 2:
        no_channel.play(no_sound)
        player2_score -= 10
        print("WRONG GPIO 19")

    with open('hits.json', 'w') as dest:
        json.dump(b, dest)

    mul4 = mul5 = mul6 = mul7 = 0


def GPIO19_callback(channel):
        """
        callback for first drum action
        """
    global multi_hit_zone
    global mul7
    cmd = 'gpio 19 pressed'
    if multi_hit_zone[7] and mul7 == 0:
        mul7 = 1
    elif not multi_hit_zone[7] and mul7==0:
        mul7 = 2

def GPIO13_callback(channel):
        """
        callback for second drum action
        """
    global multi_hit_zone
    global mul6
    print ("gpio 13 pressed")
    if multi_hit_zone[6] and mul6 == 0:
        mul6 = 1
    elif not multi_hit_zone[6] and mul6 == 0:
        mul6 = 2

def GPIO6_callback(channel):
        """
        callback for third drum action
        """
    global multi_hit_zone
    global mul5
    print ("gpio 6 pressed")
    if multi_hit_zone[5] and mul5 == 0:
        mul5 = 1
    elif not multi_hit_zone[5] and mul5 == 0:
        mul5 = 2

def GPIO5_callback(channel):
        """
        callback for fourth drum action
        """
    global multi_hit_zone
    global mul4
    print ("gpio 5 pressed")
    if multi_hit_zone[4] and mul4 == 0:
        mul4 = 1
    elif not multi_hit_zone[6] and mul4 == 0:
        mul4 = 2

#ADD EVENT DETECTION FOR DRUMS
GPIO.add_event_detect(19, GPIO.FALLING, callback=GPIO19_callback, bouncetime=100)
GPIO.add_event_detect(13, GPIO.FALLING, callback=GPIO13_callback, bouncetime=100)
GPIO.add_event_detect(6, GPIO.FALLING, callback=GPIO6_callback, bouncetime=100)
GPIO.add_event_detect(5, GPIO.FALLING, callback=GPIO5_callback, bouncetime=100)

#GAMPELAY LOOP FOR MULTIPLAYER
if MULTI_PLAYER:
    m_game_notes = pygame.sprite.Group()
    m_game_notes2 = pygame.sprite.Group()
    with open('hits.json','w+') as destination:
        json.dump(multi_default, destination)


    for i in range(MULTI_PLAYER_NOTES_ON_SCREEN):
        note = MultiNote(BLACK, i)
        m_game_notes.add(note)

    for i in range(MULTI_PLAYER_NOTES_ON_SCREEN/2):
        note = MultiNote(BLACK, i)
        m_game_notes2.add(note)

    multi_clock = pygame.time.Clock()
    multi_ss = False
    multi_clock_counter = 0


    while GAME:
        if not pygame.mixer.get_busy():
            GAME = False
            FINISH_SCREEN = True

        time.sleep(0.1)

        multiUpdate()
        updateCallback()

        multiCreateBackground()
        multi_clock_counter += 10

        if multi_clock_counter > 260 and multi_clock_counter < 280 and not multi_ss:
            multi_ss = True

        # Calls update() method on every sprite in the list
        m_game_notes.update()

        #iteration_style(game_notes)
        # Draw all the spites
        m_game_notes.draw(screen)
        if multi_ss:
            m_game_notes2.update()
            m_game_notes2.draw(screen)


        pygame.display.flip()

else: #GAMEPLAY LOOP FOR SINGLE PLAYER
    game_notes = pygame.sprite.Group()
    game_notes2 = pygame.sprite.Group()
    clock_counter = 0

    with open('hits.json','w+') as destination:
        json.dump(default, destination)


    for i in range(NOTES_ON_SCREEN):
        note = Note(BLACK, i)
        game_notes.add(note)

    for i in range(NOTES_ON_SCREEN/2):
        note = Note(BLACK, i)
        game_notes2.add(note)

    clock = pygame.time.Clock()
    ss = False


    while GAME:
        time.sleep(0.1)
        clock_counter += 10
        if not pygame.mixer.get_busy():
            GAME = False
            FINISH_SCREEN = True

        l = []
        joy = False
        for event in pygame.event.get():
            x,y = pygame.mouse.get_pos()
            if (x > QUIT["Press to Quit"][0] - 50 and x < QUIT["Press to Quit"][0] + 50 and y > QUIT["Press to Quit"][1] - 50 and y < QUIT["Press to Quit"][1] + 50 ) and event.type is MOUSEBUTTONUP:
                GAME = False
                break
            if 'JoyAxisMotion' in str(event):
                joy = True
            l.append(str(event))


        if joy:
            for event in l:
                with open('hits.json', 'r') as src:
                    b = json.load(src)

                if 'JoyButtonUp' in str(event):
                    if '5' in str(event) and hit_zone[0]:
                        b["0"] = True
                        player1_score += 10
                    elif '5' in str(event) and not hit_zone[0]:
                        no_channel.play(no_sound)
                        player1_score -= 10
                        print("WRONG A")

                    if "'button': 1" in str(event) and hit_zone[1]:
                        b["1"] = True
                        player1_score += 10
                    elif "'button': 1" in str(event) and not hit_zone[1]:
                        no_channel.play(no_sound)
                        player1_score -= 10
                        print("WRONG S")

                    if "'button': 0" in str(event) and hit_zone[2]:
                        b["2"] = True
                        player1_score += 10
                    elif "'button': 0" in str(event) and not hit_zone[2]:
                        no_channel.play(no_sound)
                        player1_score -= 10
                        print("WRONG D")

                    if "'button': 2" in str(event) and hit_zone[3]:
                        b["3"] = True
                        player1_score += 10
                    elif "'button': 2" in str(event) and not hit_zone[3]:
                        no_channel.play(no_sound)
                        player1_score -= 10
                        print("WRONG F")
                    with open('hits.json', 'w+') as dest:
                        json.dump(b,dest)
        createBackground()

        if clock_counter > 260 and clock_counter < 280 and not ss:
            ss = True

        # Calls update() method on every sprite in the list
        game_notes.update()

        # Draw all the spites
        game_notes.draw(screen)
        if ss:
            game_notes2.update()

            game_notes2.draw(screen)


        pygame.display.flip()

def render_won():
        """
        method to render end of gameplay screen, displays winning player's scores if multiplayer, single player's
        score if single player
        """
        global player1_score, player2_score
        render_text(QUIT.items()+RESTART.items(), screen)
        won_font = pygame.font.Font(None, 40)
        text_font = pygame.font.Font(None, 25)
        xpos = 0.5*SCREEN_WIDTH
        ypos = 0.3*SCREEN_HEIGHT
        score1_string = 'You won with a score of '+str(player1_score)
        score2_string = 'You won with a score of '+str(player2_score)

        if MULTI_PLAYER and player1_score > player2_score:
            congrats_surface = won_font.render('Congratulations Player 1!', True, GOLD)
            screen.blit(congrats_surface, congrats_surface.get_rect(center=[xpos,ypos]))

            score1_text_surface = text_font.render(score1_string, True, BLACK)
            screen.blit(score1_text_surface, score1_text_surface.get_rect(center=[xpos,ypos+100]))

        elif MULTI_PLAYER and player2_score > player1_score:
            congrats_surface = won_font.render('Congratulations Player 2!', True, GOLD)
            screen.blit(congrats_surface, congrats_surface.get_rect(center=[xpos,ypos]))

            score2_text_surface = text_font.render(score2_string, True, BLACK)
            screen.blit(score2_text_surface, score2_text_surface.get_rect(center=[xpos,ypos+100]))

        elif MULTI_PLAYER and player2_score == player1_score:
            congrats_surface = won_font.render('Congratulations You TIED!', True, GOLD)
            screen.blit(congrats_surface, congrats_surface.get_rect(center=[xpos,ypos]))

        else:
            congrats_surface = text_font.render('Great Job Player!', True, GOLD)
            screen.blit(congrats_surface, congrats_surface.get_rect(center=[xpos,ypos]))

            text_surface = text_font.render('You scored '+str(score1_string), True, BLACK)
            screen.blit(text_surface, text_surface.get_rect(center=[xpos,ypos+100]))

        pygame.display.flip()


screen.fill(WHITE)
#FINISH SCREEN LOOP
while FINISH_SCREEN:
    render_won()
    for event in pygame.event.get():
        x,y = pygame.mouse.get_pos()
        if (x > RESTART["Press to Restart"][0] - 50 and x < RESTART["Press to Restart"][0] + 50 and y > RESTART["Press to Restart"][1] - 50 and y < RESTART["Press to Restart"][1] + 50 ) and event.type is MOUSEBUTTONUP:
            FINISH_SCREEN = False
            GPIO.cleanup()
            execfile("guitarlaptop.py")
        if (x > QUIT["Press to Quit"][0] - 50 and x < QUIT["Press to Quit"][0] + 50 and y > QUIT["Press to Quit"][1] - 50 and y < QUIT["Press to Quit"][1] + 50 ) and event.type is MOUSEBUTTONUP:
            FINISH_SCREEN = False
            GPIO.cleanup()


    pass

#TERMINATING COMMANDS TO CLEANUP PYGAME AND GPIO
GPIO.cleanup()
pygame.quit()