def __init__(self, note_img, player, key_input, scores, fps): self.fps = fps self.note_img_cache = note_img self.player = player self.key_input = key_input self.scores = scores self.quit = False training_btn = Btn("Training", (250, 40), on_click = \ self.on_training_btn_click) game_btn = Btn("Play", (250, 80), on_click = \ self.on_play_btn_click) piano_btn = Btn("Piano", (250, 120), on_click = \ self.on_piano_btn_click) exit_btn = Btn("Quit", (250, 200), on_click = \ self.on_exit_btn_click) piano_game_txt = Text("Piano Game", (100, 40)) dev_by_txt = Text("By Guanqun Wu, Zhaopeng Xu", (100, 80), \ font_size = 20) self.stage = Stage() self.stage.add_btn(training_btn) self.stage.add_btn(game_btn) self.stage.add_btn(piano_btn) self.stage.add_btn(exit_btn) self.stage.add_elt(piano_game_txt) self.stage.add_elt(dev_by_txt)
def __init__(self, wrong_notes, early_notes, timing, score, fps): #Various settings self.stage = Stage() self.colors = {} self.colors["blue"] = (39, 117, 242) self.colors["black"] = (0, 0, 0) self.colors['green'] = (14, 230, 71) self.colors['red'] = (224, 9, 9) self.quit = False self.stage = Stage() self.exit_btn = Btn("Exit", (40, 200), on_click = \ self.on_exit_btn_click) expected_dur = 0.0 num_notes = 0 total_bars = score.get_total_bars() for i in range(total_bars): curr_bar = score.get_bar(i) expected_dur += curr_bar.get_length() * 60.0 * fps \ / curr_bar.get_bpm() num_notes += len(curr_bar.get_treble()) num_notes += len(curr_bar.get_bass()) used_dur = float(timing) pct_time = used_dur / expected_dur * 100 pct_early = float(early_notes) / num_notes * 100 pct_wrong = float(wrong_notes) / num_notes * 100 wrong_notes_txt = Text("Wrong Notes: {}".format(wrong_notes), \ (20, 20), centering = "topleft") early_notes_txt = Text("Early Notes: {0:} ({1:.2f}%)"\ .format(early_notes, pct_early), (20, 50), centering = "topleft") timing_txt = Text("Time Used: {0:.2f}s ({1:.2f}%)" \ .format(used_dur / fps, pct_time), \ (20, 80), centering = "topleft") expected_time = Text("Expected: {0:.2f}s".format(expected_dur / fps), \ (20, 110), centering = "topleft") #cutoff (pct_wrong, pct_early, pct_time) grade_cutoff = [('S', 1.0, 1.0, 110.0), \ ('A', 5.0, 5.0, 125.0), \ ('B', 15.0, 15.0, 140.0), \ ('C', 30.0, 30.0, 180.0), \ ('D', 50.0, 50.0, 200.0)] grade = 'F' for (cutoff_grade, wrong_cutoff, early_cutoff, time_cutoff) \ in grade_cutoff: if pct_wrong <= wrong_cutoff and pct_early <= early_cutoff and \ pct_time <= time_cutoff: grade = cutoff_grade break #Assign grade grade_txt = Text("{}".format(grade), \ (160, 140), centering = "center", font_size = 48) self.stage.add_btn(self.exit_btn) self.stage.add_elt(wrong_notes_txt) self.stage.add_elt(early_notes_txt) self.stage.add_elt(timing_txt) self.stage.add_elt(grade_txt)
class PianoMode: def __init__(self, player, key_input): self.stage = Stage() self.player = player self.key_input = key_input self.playable_pitches = key_input.get_playable_pitches() #Set of currently played pitches self.played_pitches = set() #Various parameters self.quit = False #Construct buttons self.exit_btn = Btn("Exit", (40, 200), on_click = \ self.on_exit_btn_click) self.piano_mode_txt = Text("Piano Mode", (160, 20)) self.notes_played_txt = Text("", (20, 100), centering="topleft") self.stage.add_btn(self.exit_btn) self.stage.add_elt(self.piano_mode_txt) self.stage.add_elt(self.notes_played_txt) def on_exit_btn_click(self, btn, pos): self.quit = True def advance_time(self, fps): #Consume updates from input #print(self.key_input) self.key_input.poll() updates = self.key_input.get_updates() for pitch, is_pressed in updates.items(): #print("Update: {}, {}".format(pitch, is_pressed)) if is_pressed: self.played_pitches.add(pitch) self.player.play_note([pitch]) elif pitch in self.played_pitches: self.played_pitches.remove(pitch) self.player.stop_note([pitch]) if (len(updates) > 0): notes_played = "" for pitch in self.played_pitches: notes_played += pitch notes_played += " " self.notes_played_txt.text = notes_played def bind_screen(self, parent_screen): self.parent_screen = parent_screen def draw(self, screen): #print("Drawing") self.stage.draw(screen) def handle_click(self, pos): self.stage.handle_click(pos) def has_quit(self): return self.quit
def __init__(self, player, key_input): self.stage = Stage() self.player = player self.key_input = key_input self.playable_pitches = key_input.get_playable_pitches() #Set of currently played pitches self.played_pitches = set() #Various parameters self.quit = False #Construct buttons self.exit_btn = Btn("Exit", (40, 200), on_click = \ self.on_exit_btn_click) self.piano_mode_txt = Text("Piano Mode", (160, 20)) self.notes_played_txt = Text("", (20, 100), centering="topleft") self.stage.add_btn(self.exit_btn) self.stage.add_elt(self.piano_mode_txt) self.stage.add_elt(self.notes_played_txt)
def __init__(self, note_img_cache, player, key_input, scores \ , fps, train_mode = True): #Various settings self.stage = Stage() self.scores_per_page = 4 #Set various attributes self.scores = scores self.img_cache = note_img_cache self.player = player self.key_input = key_input self.train_mode = train_mode self.fps = fps self.colors = {} self.colors["blue"] = (39, 117, 242) self.colors["black"] = (0, 0, 0) self.quit = False self.score_btns = {} self.score_to_idx = {} self.curr_idx = 0 self.sel_idx = -1 self.return_from_mode = False self.stage = Stage() self.exit_btn = Btn("Exit", (40, 200), on_click = \ self.on_exit_btn_click) self.select_btn = Btn("Select", (260, 180), on_click = \ self.on_select_btn_click) self.up_btn = ImageBtn("./img/up.png", (260, 40), on_click = \ self.on_up_btn_click, dimen = (20, 20)) self.down_btn = ImageBtn("./img/down.png", (260, 120), on_click = \ self.on_down_btn_click, dimen = (20, 20)) self.stage.add_btn(self.exit_btn) self.stage.add_btn(self.up_btn) self.stage.add_btn(self.down_btn) self.stage.add_btn(self.select_btn) self.refresh_scores()
class ScoreSelect: def __init__(self, note_img_cache, player, key_input, scores \ , fps, train_mode = True): #Various settings self.stage = Stage() self.scores_per_page = 4 #Set various attributes self.scores = scores self.img_cache = note_img_cache self.player = player self.key_input = key_input self.train_mode = train_mode self.fps = fps self.colors = {} self.colors["blue"] = (39, 117, 242) self.colors["black"] = (0, 0, 0) self.quit = False self.score_btns = {} self.score_to_idx = {} self.curr_idx = 0 self.sel_idx = -1 self.return_from_mode = False self.stage = Stage() self.exit_btn = Btn("Exit", (40, 200), on_click = \ self.on_exit_btn_click) self.select_btn = Btn("Select", (260, 180), on_click = \ self.on_select_btn_click) self.up_btn = ImageBtn("./img/up.png", (260, 40), on_click = \ self.on_up_btn_click, dimen = (20, 20)) self.down_btn = ImageBtn("./img/down.png", (260, 120), on_click = \ self.on_down_btn_click, dimen = (20, 20)) self.stage.add_btn(self.exit_btn) self.stage.add_btn(self.up_btn) self.stage.add_btn(self.down_btn) self.stage.add_btn(self.select_btn) self.refresh_scores() def bind_screen(self, parent_screen): self.parent_screen = parent_screen def draw(self, screen): self.stage.draw(screen) def handle_click(self, pos): self.stage.handle_click(pos) def refresh_scores(self): for _, btn in self.score_btns.items(): self.stage.remove_btn(btn) #Maps index to button objects self.score_btns = {} #Maps score name to index self.score_to_idx = {} for i in range(self.curr_idx, min(self.curr_idx + \ self.scores_per_page, len(self.scores))): score_name = self.scores[i].get_metadata()["name"] score_btn = Btn(score_name, (130, 40 + \ (i - self.curr_idx) * 30), \ on_click = self.on_score_btn_click, \ font_size = 24) self.stage.add_btn(score_btn) #Make blue if selected if i == self.sel_idx: score_btn.color = self.colors["blue"] self.score_to_idx[score_name] = i self.score_btns[i] = score_btn def advance_time(self, fps): if self.return_from_mode: self.return_from_mode = False info = self.parent_screen.get_info() if "early_notes" in info: score_disp = AssignScore(info.pop("wrong_notes"), \ info.pop("early_notes"), info.pop("frames_used"), \ self.scores[self.sel_idx], fps) self.parent_screen.add_child(score_disp) def on_exit_btn_click(self, btn, pos): self.quit = True def on_up_btn_click(self, btn, pos): if self.curr_idx - self.scores_per_page >= 0: self.curr_idx -= self.scores_per_page self.sel_idx = -1 self.refresh_scores() def on_down_btn_click(self, btn, pos): if self.curr_idx + self.scores_per_page < len(self.scores): self.curr_idx += self.scores_per_page self.sel_idx = -1 self.refresh_scores() def on_select_btn_click(self, btn, pos): if self.sel_idx != -1: score = self.scores[self.sel_idx] if self.train_mode: train = TrainingScore(self.img_cache, self.player, \ self.key_input, score = score) #This passes off control to the training screen self.parent_screen.add_child(train) self.return_from_mode = True else: game = GameScore(self.img_cache, self.player, \ self.key_input, score = score) #This passes off control to the training screen self.parent_screen.add_child(game) self.return_from_mode = True def on_score_btn_click(self, btn, pos): btn_idx = self.score_to_idx[btn.text] if self.sel_idx in self.score_btns: self.score_btns[self.sel_idx].color = self.colors["black"] self.sel_idx = btn_idx self.score_btns[self.sel_idx].color = self.colors["blue"] def has_quit(self): return self.quit
def replace_score(self, new_score): self.score = new_score #Advance at 1.0 pace self.advance_rate = 1.0 #Stage with no elements self.stage = Stage() #Keep track of current state #Start at the 0th bar self.curr_bar_idx = 0 #Have not started playing music, set to False when paused self.has_started = False #Grab current bar self.bars = self.get_bars() #The current timing in the current bar (based on bar timing and bpm) self.curr_timing = 0.0 #1.0 for normal, -<sth> for rewind, +<sth> for ffwd, 0 for pause self.advance_pace = 1.0 #Add stage elements #Add trigger points self.left_margin = 10 self.start_left_margin = 50 self.right_margin = 310 self.treble_begin = 70 self.treble_increment = 10 self.bass_begin = 160 self.bass_increment = 10 #Precompute adjustments self.bass_adj = self.get_adj(False) self.treble_adj = self.get_adj(True) #Add Treble Lines, Clef and Timing self.treble_lines = [] self.treble_clef = Image("img/treble_clef.png", (20, 50), (35, 70)) self.stage.add_elt(self.treble_clef) #30 up to 70 for i in range(self.treble_begin - 4 * self.treble_increment, \ self.treble_begin + self.treble_increment, self.treble_increment): line = Line((self.left_margin, i), (self.right_margin, i)) self.treble_lines.append(line) self.stage.add_elt(line) #Add Bass Lines, Clef and Timing self.bass_lines = [] self.bass_clef = Image("img/bass_clef.png", (25, 135), (35, 35)) self.stage.add_elt(self.bass_clef) #120 up to 160 for i in range(self.bass_begin - 4 * self.bass_increment, \ self.bass_begin + self.bass_increment, self.bass_increment): line = Line((self.left_margin, i), (self.right_margin, i)) self.bass_lines.append(line) self.stage.add_elt(line) #Add bar lines #Generate bar lines for left and right edges self.bar_lines = [\ Line((self.left_margin, self.treble_begin - 4 * self.treble_increment),\ (self.left_margin,self.treble_begin)), \ Line((self.left_margin, self.bass_begin - 4 * self.bass_increment),\ (self.left_margin,self.bass_begin))] #Draw bar lines (no changes) for bar_idx, bar in zip(range(len(self.bars)), self.bars): #Add bar lines end_x = self.get_bar_start_x(bar_idx + 1) self.bar_lines.append(Line((end_x, self.treble_begin \ - 4 * self.treble_increment),(end_x,self.treble_begin))) self.bar_lines.append(Line((end_x, self.bass_begin \ - 4 * self.bass_increment),(end_x,self.bass_begin))) for bar_line in self.bar_lines: self.stage.add_elt(bar_line) #Draw current position line play_line_pos = self.get_note_horizontal_pos(self.curr_bar_idx, \ self.curr_timing) + 5 self.play_line = Line((play_line_pos, self.treble_begin - \ 5 * self.treble_increment), (play_line_pos, self.bass_begin + \ self.bass_increment)) self.stage.add_elt(self.play_line) #Grab new timings self.refresh_timings()
class RenderedScore: #[__init__ self note_imgs player score] generates a new RenderedScore #using the images from [note_imgs], audio player [player] and score [score] def __init__(self, note_imgs, player, score = None): self.colors = {"yellow": (244, 247, 35), "black": (0,0,0), \ "dark_blue": (47, 29, 245)} self.note_imgs = note_imgs self.player = player self.num_bars = 2 #Used to mark previous notes as black self.mark_black = True #Used to notate whether to play notes self.play_notes = True if score == None: self.score = None else: self.replace_score(score) #[bind_screen self parent_screen] is required by the parent Screen. #It allows interaction with the parent Screen. def bind_screen(self, parent_screen): self.parent_screen = parent_screen #[replace_score self new_score] replaces the current score #with a new one and draws the new score onto the stage def replace_score(self, new_score): self.score = new_score #Advance at 1.0 pace self.advance_rate = 1.0 #Stage with no elements self.stage = Stage() #Keep track of current state #Start at the 0th bar self.curr_bar_idx = 0 #Have not started playing music, set to False when paused self.has_started = False #Grab current bar self.bars = self.get_bars() #The current timing in the current bar (based on bar timing and bpm) self.curr_timing = 0.0 #1.0 for normal, -<sth> for rewind, +<sth> for ffwd, 0 for pause self.advance_pace = 1.0 #Add stage elements #Add trigger points self.left_margin = 10 self.start_left_margin = 50 self.right_margin = 310 self.treble_begin = 70 self.treble_increment = 10 self.bass_begin = 160 self.bass_increment = 10 #Precompute adjustments self.bass_adj = self.get_adj(False) self.treble_adj = self.get_adj(True) #Add Treble Lines, Clef and Timing self.treble_lines = [] self.treble_clef = Image("img/treble_clef.png", (20, 50), (35, 70)) self.stage.add_elt(self.treble_clef) #30 up to 70 for i in range(self.treble_begin - 4 * self.treble_increment, \ self.treble_begin + self.treble_increment, self.treble_increment): line = Line((self.left_margin, i), (self.right_margin, i)) self.treble_lines.append(line) self.stage.add_elt(line) #Add Bass Lines, Clef and Timing self.bass_lines = [] self.bass_clef = Image("img/bass_clef.png", (25, 135), (35, 35)) self.stage.add_elt(self.bass_clef) #120 up to 160 for i in range(self.bass_begin - 4 * self.bass_increment, \ self.bass_begin + self.bass_increment, self.bass_increment): line = Line((self.left_margin, i), (self.right_margin, i)) self.bass_lines.append(line) self.stage.add_elt(line) #Add bar lines #Generate bar lines for left and right edges self.bar_lines = [\ Line((self.left_margin, self.treble_begin - 4 * self.treble_increment),\ (self.left_margin,self.treble_begin)), \ Line((self.left_margin, self.bass_begin - 4 * self.bass_increment),\ (self.left_margin,self.bass_begin))] #Draw bar lines (no changes) for bar_idx, bar in zip(range(len(self.bars)), self.bars): #Add bar lines end_x = self.get_bar_start_x(bar_idx + 1) self.bar_lines.append(Line((end_x, self.treble_begin \ - 4 * self.treble_increment),(end_x,self.treble_begin))) self.bar_lines.append(Line((end_x, self.bass_begin \ - 4 * self.bass_increment),(end_x,self.bass_begin))) for bar_line in self.bar_lines: self.stage.add_elt(bar_line) #Draw current position line play_line_pos = self.get_note_horizontal_pos(self.curr_bar_idx, \ self.curr_timing) + 5 self.play_line = Line((play_line_pos, self.treble_begin - \ 5 * self.treble_increment), (play_line_pos, self.bass_begin + \ self.bass_increment)) self.stage.add_elt(self.play_line) #Grab new timings self.refresh_timings() #[refresh_timings self] refreshes all of the bars, timings and bpm #displayed on the stage based on the current timing (self.curr_timing) #and current bar index (self.curr_bar_idx) def refresh_timings(self): self.stage.clear_tmp_elts() self.bar_numbers = [] self.timings = [] self.pace_text = [] #Grab current bar self.bars = self.get_bars() prev_timing = (None, None) prev_pace = None #Render all the bars for bar_idx, bar in zip(range(len(self.bars)), self.bars): bar_timings = [] top_timing, bottom_timing = bar.get_timing() curr_pace = bar.get_bpm() start_x = self.get_bar_start_x(bar_idx) bar_idx_norm = self.curr_bar_idx - self.curr_bar_idx % 2 bar_num = bar_idx_norm + bar_idx + 1 self.bar_numbers.append(Text(str(bar_num), (start_x + 5, \ self.treble_begin - 4 * self.treble_increment - 10), \ font_size = 20)) if prev_timing != (top_timing, bottom_timing): top_timing = str(top_timing) bottom_timing = str(bottom_timing) bar_timings.append(Text(top_timing, (start_x + 10, 40), \ font_size = 42)) bar_timings.append(Text(bottom_timing, (start_x + 10, 62), \ font_size = 42)) bar_timings.append(Text(top_timing, (start_x + 10, 130), \ font_size = 42)) bar_timings.append(Text(bottom_timing, (start_x + 10, 152), \ font_size = 42)) if prev_pace != curr_pace: self.pace_text.append(Text(self.pace_to_str(curr_pace), \ (start_x + 70, self.treble_begin - 4 * \ self.treble_increment - 10), font_size = 20)) #Add timings prev_timing = bar.get_timing() prev_pace = curr_pace self.timings.append(bar_timings) for timing in bar_timings: self.stage.add_tmp_elt(timing) for bar_number in self.bar_numbers: self.stage.add_tmp_elt(bar_number) for text in self.pace_text: self.stage.add_tmp_elt(text) self.refresh_notes() #[refresh_notes self] updates the position of all the notes on the screen #based on self.curr_timing and self.curr_bar_idx def refresh_notes(self): #Draw the notes #Stored by bar in same order as self.bars, then list of pitches for #each note, then a list of Components for each pitch #the note Image is always the last element in the list of Components self.treble_note_imgs = [] self.bass_note_imgs = [] for bar_idx, bar in zip(range(self.num_bars), self.bars): self.treble_bar_imgs = [] self.add_notes_from_clef(bar_idx, bar.get_treble(), True, \ self.treble_bar_imgs) self.bass_bar_imgs = [] self.add_notes_from_clef(bar_idx, bar.get_bass(), False, \ self.bass_bar_imgs) self.treble_note_imgs.append(self.treble_bar_imgs) self.bass_note_imgs.append(self.bass_bar_imgs) for bar in self.treble_note_imgs + self.bass_note_imgs: for pitches in bar: for pitch in pitches: for component in pitch: self.stage.add_tmp_elt(component) """ [add_notes_from_clef self bar_idx notes treble append_to] appends [notes] from the bar at [bar_idx] relative to self.curr_bar_idx from the clef indicated by [treble] (Treble if True, Bass if False) to the list [append_to]. Note that this takes into account note flips and chords where all notes point the same direction. """ def add_notes_from_clef(self, bar_idx, notes, treble, append_to): curr_dur = 0.0 for pitches, duration in notes: note_imgs = [] x_pos = self.get_note_horizontal_pos(self.curr_bar_idx + bar_idx, \ curr_dur) #print("x_pos: {}".format(x_pos)) should_force_flip = False for pitch in pitches: _,_,should_flip = self.pitch_adj_flip(pitch, treble) if should_flip: should_force_flip = True break for pitch in pitches: note_imgs.append(self.get_note_images(pitch, duration, \ x_pos, treble, force_flip = should_force_flip)) append_to.append(note_imgs) curr_dur += duration #[pace_to_str self pace] converts [pace] to a string based on the #beats per minute def pace_to_str(self, pace): pace_text = [(24, "Larghissimo"), (40, "Grave"), (60, "Largo"), \ (76, "Adagio"), (108, "Andante"), (120, "Moderato"), \ (156, "Allegro"), (176, "Vivace"), (200, "Presto")] pace_name = "Prestissimo" for (max_pace, name) in pace_text: if pace <= max_pace: pace_name = name break return "{} {}".format(pace_name, int(pace)) """ [get_bars self] gets the current bars based on self.curr_bar_idx and self.num_bars """ def get_bars(self): bars = [] bar_idx = self.curr_bar_idx - (self.curr_bar_idx % self.num_bars) for i in range(bar_idx, min(bar_idx + 2, \ self.score.get_total_bars())): bars.append(self.score.get_bar(i)) return bars """ [get_bar_start_x self bar_idx] gets the x position where the bar with relative index [bar_idx] (0 to self.num_bars - 1) should start """ def get_bar_start_x(self, bar_idx): return self.start_left_margin + \ float(self.right_margin - self.start_left_margin) * bar_idx \ / self.num_bars """ [get_note_horizontal_pos self bar_idx duration] gets the x position of the note relative to the start of the bar based on the note [duration] and the relative index of the bar [bar_idx] (0 to self.num_bars - 1) """ def get_note_horizontal_pos(self, bar_idx, duration): bar_pos = bar_idx % self.num_bars start_x = self.get_bar_start_x(bar_pos) #print("bar_idx: {}, result: {}".format(bar_idx, start_x)) #Consider position occupied by timing if bar_pos == 0 or self.bars[bar_pos].get_timing() \ != self.bars[bar_pos - 1].get_timing(): start_x += 20 end_x = self.get_bar_start_x(bar_pos + 1) bar_duration = self.bars[bar_pos].get_length() return start_x + float(end_x - start_x) * duration / bar_duration """ [get_adj self treble] gets the y axis adjustment needed based on the pitch of the note for the given clef based on [treble]. [treble] indicates the Treble Clef if True and Bass Clef if False. """ def get_adj(self, treble): clef_adj = {'-' : self.treble_increment * 2} if treble: alphabet = 'F' octave = 3 adj = -6 * float(self.treble_increment) / 2 else: alphabet = 'E' octave = 2 adj = -2 * float(self.bass_increment) / 2 while octave < 7: clef_adj[alphabet + str(octave)] = adj alphabet = chr(ord(alphabet) + 1) if alphabet > 'G': alphabet = 'A' if alphabet == 'C': octave += 1 adj += float(self.treble_increment) / 2 return clef_adj """ [get_note_images self pitch duration x_pos treble force_flip] returns a list of components needed to draw a particular pitch. [pitch] refers to the pitch we're currently considering [duration] refers to the duration of the pitch that we're considering [x_pos] refers to the computed horizontal position of this pitch [duration] refers to the length of the note in crotchets [treble] refers to whether this note should be rendered in the treble clef (Treble if True, Bass if False) [force_flip is used to force the note to be flipped if it appears in a chord. Note that this can innately handle sharps, handle chords and draw extra lines if a note is too high or too low. """ def get_note_images(self, pitch, duration, x_pos, treble, force_flip = False): #May have to return additional lines to draw certain notes images = [] #Pitch => ie 'A4', duration => ie 1.0 is_sharp = pitch.find("#") != -1 is_pause = pitch == '-' is_flipped = False if force_flip: is_flipped = True #Remove any sharps pitch = pitch.replace("#", "") duration = round(duration, 3) #Adjust x-axis by 10 x_pos += 10 adj = [0, 0] pitch_adj = 0 #Adjust for position #if treble: # pitch_adj = self.treble_adj[pitch] # adj[1] = self.treble_begin - pitch_adj #else: # pitch_adj = self.bass_adj[pitch] # adj[1] = self.bass_begin - pitch_adj #Adjust for flipping #if pitch_adj >= 2 * self.treble_increment: # is_flipped = True (adj, pitch_adj, should_flip) = self.pitch_adj_flip(pitch, treble) if should_flip: is_flipped = True #Draw extra lines if note is too low num_adj_lines = - int(pitch_adj) // int(self.treble_increment) for i in range(num_adj_lines): if treble: images.append(Line((x_pos - 10, self.treble_begin + (i + 1) * \ self.treble_increment), (x_pos + 10, self.treble_begin + \ (i + 1) * self.treble_increment))) else: images.append(Line((x_pos - 10, self.bass_begin + (i + 1) * \ self.bass_increment), (x_pos + 10, self.bass_begin + \ (i + 1) * self.bass_increment))) #Draw extra lines if note is too high num_adj_lines = (int(pitch_adj) - 4 * int(self.treble_increment)) // \ int(self.treble_increment) for i in range(num_adj_lines): if treble: images.append(Line((x_pos - 10, self.treble_begin - (i + 5) * \ self.treble_increment), (x_pos + 10, self.treble_begin - \ (i + 5) * self.treble_increment))) else: images.append(Line((x_pos - 10, self.bass_begin - (i + 5) * \ self.bass_increment), (x_pos + 10, self.bass_begin - \ (i + 5) * self.bass_increment))) #Adjust for image img_adjustments = {1.0 : [(0, -13), (0, +14), (0, 0)], \ 2.0: [(0, -13), (0, +14), (0, -2)], \ 3.0: [(0, -13), (0, +14), (0, -2)], \ 4.0: [(0, 0), (0, 0), (0, -7)], \ 1.5: [(0, -13), (0, +14), (0, 0)], \ 0.5: [(+4, -13), (-4, +14), (0, 0)], \ 0.75: [(+4, -13), (-4, +14), (0, 0)], \ 0.25: [(+4, -13), (-4, +14), (0, 0)]} img_adj = (0, 0) if duration in img_adjustments: if not is_flipped and not is_pause: #Normal Note img_adj = img_adjustments[duration][0] elif is_pause: img_adj = img_adjustments[duration][2] else: img_adj = img_adjustments[duration][1] adj[0] += img_adj[0] adj[1] += img_adj[1] #Adjust for sharp if is_sharp: images.append(Text("#", (x_pos - 10, adj[1] - img_adj[1]),\ font_size = 20)) #adj[0] += 10 #Grab image and adjust img_surf = self.note_imgs.get_note(duration, rest = is_pause, \ flip = is_flipped) images.append(Image(img_surf, (x_pos + adj[0], adj[1]), \ from_surf = True)) return images """ [pitch_adj_flip self pitch treble] adjusts a [pitch] for y position and determines if the pitch should be flipped based on its y position. [treble] is True if the note is in the Treble Clef and False otherwise. """ def pitch_adj_flip(self, pitch, treble): #Remove any sharps pitch = pitch.replace("#", "") adj = [0,0] is_flipped = False #Adjust for position if treble: pitch_adj = self.treble_adj[pitch] adj[1] = self.treble_begin - pitch_adj else: pitch_adj = self.bass_adj[pitch] adj[1] = self.bass_begin - pitch_adj #Adjust for flipping if pitch_adj >= 2 * self.treble_increment: is_flipped = True return (adj, pitch_adj, is_flipped) #[adjust_pace self new_pace] changes the relative pace that we're moving #along the song. 1.0 is the normal pace and other paces #would go faster or go slower. 0.0 is used to pause def adjust_pace(self, new_pace): #1.0 for normal, -<sth> for rewind, +<sth> for ffwd, 0 for pause self.advance_rate = new_pace #[advance_time self fps] steps through one frame at [fps] frames #per second. This causes the playback to advance according to #[self.advance_rate] def advance_time(self, fps): #Check if completed if self.curr_bar_idx >= self.score.get_total_bars(): return #Move play line play_line_pos = self.get_note_horizontal_pos(self.curr_bar_idx, \ self.curr_timing) + 5 self.play_line.change_x(play_line_pos, play_line_pos) curr_bar = self.score.get_bar(self.curr_bar_idx) #Resume the piece if not self.has_started: self.has_started = True treble_pitches = self.get_curr_pitches(True) bass_pitches = self.get_curr_pitches(False) if self.play_notes: self.player.play_note(treble_pitches) self.player.play_note(bass_pitches) #Mark the playing notes as dark blue self.change_timing_note_color(self.curr_timing, \ self.colors["dark_blue"], True) self.change_timing_note_color(self.curr_timing, \ self.colors["dark_blue"], False) else: #Advance the current time bpm = curr_bar.get_bpm() prev_timing = self.curr_timing prev_treble = curr_bar.note_at_time(self.curr_timing, True) prev_bass = curr_bar.note_at_time(self.curr_timing, False) self.curr_timing += float(self.advance_rate) * bpm / 60.0 / fps new_treble = curr_bar.note_at_time(self.curr_timing, True) new_bass = curr_bar.note_at_time(self.curr_timing, False) treble = curr_bar.get_treble() bass = curr_bar.get_bass() #Transition notes when changing timing if prev_treble != new_treble: prev_treble_pitches = treble[prev_treble][0] if self.play_notes: self.player.stop_note(prev_treble_pitches) self.on_note_stop(prev_treble_pitches, True) if self.play_notes: self.player.play_note(treble[new_treble][0]) if self.mark_black: self.change_timing_note_color(prev_timing, \ self.colors["black"], True) self.change_timing_note_color(self.curr_timing, \ self.colors["dark_blue"], True) if prev_bass != new_bass: prev_bass_pitches = bass[prev_bass][0] if self.play_notes: self.player.stop_note(prev_bass_pitches) self.on_note_stop(prev_bass_pitches, False) if self.play_notes: self.player.play_note(bass[new_bass][0]) if self.mark_black: self.change_timing_note_color(prev_timing, \ self.colors["black"], False) self.change_timing_note_color(self.curr_timing, \ self.colors["dark_blue"], False) #Move to next bar when available if self.curr_timing > curr_bar.get_length(): #Stop current notes prev_treble_pitches = self.get_curr_pitches(True) prev_bass_pitches = self.get_curr_pitches(False) if self.play_notes: self.player.stop_note(prev_treble_pitches) self.player.stop_note(prev_bass_pitches) self.player.stop_all() self.on_note_stop(prev_treble_pitches, True) self.on_note_stop(prev_bass_pitches, False) #Mark the stopped notes as black if self.mark_black: self.change_timing_note_color(self.curr_timing, \ self.colors["black"], True) self.change_timing_note_color(self.curr_timing, \ self.colors["black"], False) self.curr_timing -= curr_bar.get_length() self.curr_bar_idx += 1 #Update notes if required if self.curr_bar_idx % self.num_bars == 0 and \ self.curr_bar_idx < self.score.get_total_bars(): self.refresh_timings() #Play treble and bass notes for next bar if possible if self.curr_bar_idx < self.score.get_total_bars(): if self.play_notes: self.player.play_note(self.get_curr_pitches(True)) self.player.play_note(self.get_curr_pitches(False)) #Mark the playing notes as yellow self.change_timing_note_color(self.curr_timing, \ self.colors["dark_blue"], True) self.change_timing_note_color(self.curr_timing, \ self.colors["dark_blue"], False) #[on_note_stop self pitches treble] is called when we transition #between bars or between notes. [pitches] refer to the pitches which #are stopped and [treble] refers to the clef (Treble if True, Bass if False) def on_note_stop(self, pitches, treble): return True #[get_curr_pitches self treble] gets the pitches based on self.curr_bar_idx #and self.curr_timing that should be played now based on the clef indicated #by [treble] (Treble if True, Bass if False) def get_curr_pitches(self, treble): curr_bar = self.score.get_bar(self.curr_bar_idx) if treble: notes = curr_bar.get_treble() else: notes = curr_bar.get_bass() pitches,_ = notes[curr_bar.note_at_time(self.curr_timing, treble)] return pitches #[jump_to_next_timing self] is used to jump to the next note def jump_to_next_timing(self): #print("Changing color") bar_idx = self.curr_bar_idx % self.num_bars curr_bar = self.score.get_bar(self.curr_bar_idx) treble_idx = curr_bar.note_at_time(self.curr_timing, True) bass_idx = curr_bar.note_at_time(self.curr_timing, False) treble_dur = curr_bar.end_duration(treble_idx, True) bass_dur = curr_bar.end_duration(bass_idx, False) timing_jump = min(treble_dur, bass_dur) + 0.01 - self.curr_timing self.curr_timing += timing_jump self.on_early_note_release(timing_jump) #[on_early_note_release self timing_jump] is triggered when a note is #released early by the player. [timing_jump] is the amount of crotchets #missed when we jump to the next note. def on_early_note_release(self, timing_jump): return True #[change_timing_note_color self timing new_color treble] changes the color #of the note at [timing] to [new_color] with clef specified by [treble] #(Treble if True, Bass if False) def change_timing_note_color(self, timing, new_color, treble): #print("Changing color") bar_idx = self.curr_bar_idx % self.num_bars curr_bar = self.score.get_bar(self.curr_bar_idx) #print(self.treble_note_imgs) #print("Timing: {}".format(timing)) if treble: treble_idx = curr_bar.note_at_time(timing, True) for pitch in self.treble_note_imgs[bar_idx][treble_idx]: pitch[-1].change_color(new_color) else: bass_idx = curr_bar.note_at_time(timing, False) for pitch in self.bass_note_imgs[bar_idx][bass_idx]: pitch[-1].change_color(new_color) #[change_curr_pitch_color] changes the specified [pitches] to [new_color] #at the current timing defined by self.curr_bar_idx and self.curr_timing def change_curr_pitch_color(self, pitches, new_color): bar_idx = self.curr_bar_idx % self.num_bars curr_bar = self.score.get_bar(self.curr_bar_idx) timing = self.curr_timing treble_idx = curr_bar.note_at_time(timing, True) treble = curr_bar.get_treble() bass = curr_bar.get_bass() for pitch, imgs in zip(treble[treble_idx][0], \ self.treble_note_imgs[bar_idx][treble_idx]): if pitch in pitches: imgs[-1].change_color(new_color) bass_idx = curr_bar.note_at_time(timing, False) for pitch, imgs in zip(bass[bass_idx][0], \ self.bass_note_imgs[bar_idx][bass_idx]): if pitch in pitches: imgs[-1].change_color(new_color) #[handle_click self pos] handles a click event at position [pos] def handle_click(self, pos): self.stage.handle_click(pos) #[draw self screen] draws the elements in the score onto [screen] def draw(self, screen): self.stage.draw(screen) #[has_quit self] queries whether this score has quitted def has_quit(self): return self.curr_bar_idx >= self.score.get_total_bars()
class MainUI: def __init__(self, note_img, player, key_input, scores, fps): self.fps = fps self.note_img_cache = note_img self.player = player self.key_input = key_input self.scores = scores self.quit = False training_btn = Btn("Training", (250, 40), on_click = \ self.on_training_btn_click) game_btn = Btn("Play", (250, 80), on_click = \ self.on_play_btn_click) piano_btn = Btn("Piano", (250, 120), on_click = \ self.on_piano_btn_click) exit_btn = Btn("Quit", (250, 200), on_click = \ self.on_exit_btn_click) piano_game_txt = Text("Piano Game", (100, 40)) dev_by_txt = Text("By Guanqun Wu, Zhaopeng Xu", (100, 80), \ font_size = 20) self.stage = Stage() self.stage.add_btn(training_btn) self.stage.add_btn(game_btn) self.stage.add_btn(piano_btn) self.stage.add_btn(exit_btn) self.stage.add_elt(piano_game_txt) self.stage.add_elt(dev_by_txt) def bind_screen(self, parent_screen): self.parent_screen = parent_screen def draw(self, screen): self.stage.draw(screen) def handle_click(self, pos): self.stage.handle_click(pos) def advance_time(self, fps): return True def on_training_btn_click(self, btn, pos): select = ScoreSelect(self.note_img_cache, self.player, self.key_input, \ self.scores, self.fps, True) self.parent_screen.add_child(select) def on_play_btn_click(self, btn, pos): select = ScoreSelect(self.note_img_cache, self.player, self.key_input, \ self.scores, self.fps, False) self.parent_screen.add_child(select) def on_piano_btn_click(self, btn, pos): piano = PianoMode(self.player, self.key_input) self.parent_screen.add_child(piano) def on_exit_btn_click(self, btn, pos): self.quit = True def has_quit(self): return self.quit