def __init__(self, **kwargs): super(LevelSelectScreen, self).__init__(**kwargs) Window.clearcolor = colors['grey'].rgba self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.label = Label(text='Play it by Gear', font_name='./fonts/PassionOne-Regular', color=(.165, .718, .792, 1)) self.add_widget(self.label) self.buttons = [] self.id_counter = 0 self.audio = Audio(2) self.mixer = Mixer() self.audio.set_generator(self.mixer) self.start_time = None self.switch_level = None self.border_color = colors['dark_grey'] self.border = Line(rectangle=(0, 0, Window.width, Window.height), width=Window.width * 0.03) self.anim_group.add(self.border_color) self.anim_group.add(self.border) self.on_layout((Window.width, Window.height))
def __init__(self, norm, sandbox, mixer, client, client_id, block_handler, tempo=60): self.norm = norm self.module_name = 'TempoCursor' self.sandbox = sandbox self.mixer = mixer self.client = client self.cid = client_id self.block_handler = block_handler self.tempo = tempo self.clock = Clock() self.tempo_map = SimpleTempoMap(bpm=self.tempo) self.touch_points = {} self.cursors = AnimGroup() self.sandbox.add(self.cursors) self.gui = CursorGUI(norm, pos=self.norm.nt((20, 300)), beat_callback=self.update_touch_points) self.delete_mode = {}
def __init__(self): super(ExerciseWidget, self).__init__() self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.line = TriangleA() self.anim_group.add(self.line) self.info = topleft_label() self.add_widget(self.info) self.last_touch = None
def __init__(self, modes, label): super(IconBar, self).__init__() self.modes = modes self.label = label self.icons = [] self.icon_group = AnimGroup() self.add(self.icon_group) self.generate_icons()
def __init__(self): super(Scene, self).__init__() self.background = BackgroundWidget() self.add_widget(self.background) self.foreground = ForegroundWidget() self.add_widget(self.foreground) # Flying music notes self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.anim_group.add(FadingMusicNote())
def __init__(self): super(MainWidget2, self).__init__() self.audio = Audio(2) self.synth = Synth('../data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.tempo_map = SimpleTempoMap(120) self.sched = AudioScheduler(self.tempo_map) # connect scheduler into audio system self.audio.set_generator(self.sched) self.sched.set_generator(self.synth) # create the metronome: self.metro = Metronome(self.sched, self.synth) # create the arpeggiator: self.arpeg = Arpeggiator(self.sched, self.synth, channel = 1, program = (0,0) ) self.arpeg.set_direction('updown') #size of the notes sent to the arpeggiator self.arpegSize = 3 #all of the notes this program can make. However, only self.arpegSize notes are sent to the arpegiator at a time self.allNotes = [50, 53, 55, 56, 57, 60, 62, 65, 67, 68, 69, 72, 74] self.lastPitchIndex = None self.lastPulseIndex = None self.noteLengths = [240, 210, 180, 150, 120, 90, 60] self.articulation = .75 # and text to display our status self.label = topleft_label() self.add_widget(self.label) # and text to display our status self.label = topleft_label() self.add_widget(self.label) self.objects = AnimGroup() self.canvas.add(self.objects) self.add_lines()
def __init__(self, prev_room, level=0, on_finished_puzzle=None): super().__init__() self.door_sources = { (4, 9): "./data/Door_up.png", # UP (4, -1): "./data/door_down.png", # DOWN (-1, 4): "./data/door_left.png", # LEFT (9, 4): "./data/Door_right.png", # RIGHT } self.prev_room = prev_room self.on_finished_puzzle = on_finished_puzzle self.animations = AnimGroup() self.level = level self.notes, self.actual_key = levels[level] duration = choice(durations) pitch_shift = choice(range(-3, 4)) self.user_notes = [ Note(duration, n.get_pitch() + pitch_shift) for n in self.notes ] render_user_notes = self.level < 2 self.actual_sound = PuzzleSound(self.notes) self.user_sound = PuzzleSound(self.user_notes) self.music_bar = MusicBar(self.notes, self.user_notes, render_user_notes, self.actual_sound) self.animations.add(self.music_bar) self.add(self.animations) self.user_key = "C" self.place_objects() self.key_label = CLabelRect( (Window.width // 30, 23 * Window.height // 32), f"Key: {self.user_key}", 34) self.add(Color(rgba=(1, 1, 1, 1))) self.add(self.key_label) self.create_instructions((Window.width, Window.height)) self.add(self.instructions_text_color) self.add(self.instructions_text) self.objects = {}
class ExerciseWidget(BaseWidget): def __init__(self): super(ExerciseWidget, self).__init__() self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.line = TriangleA() self.anim_group.add(self.line) self.info = topleft_label() self.add_widget(self.info) self.last_touch = None def on_update(self): self.anim_group.on_update() self.update_info_label() def on_touch_move(self, touch): self.line.line.points = [200, 200, touch.pos[0], touch.pos[1]] send(str(touch.pos)) def update_info_label(self): self.info.text = str(Window.mouse_pos) self.info.text += '\nfps:%d' % kivyClock.get_fps() self.info.text += '\nobjects:%d' % len(self.anim_group.objects) self.info.text += '\nreceived:' + str(received)
class Scene(BaseWidget): def __init__(self): super(Scene, self).__init__() self.background = BackgroundWidget() self.add_widget(self.background) self.foreground = ForegroundWidget() self.add_widget(self.foreground) # Flying music notes self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.anim_group.add(FadingMusicNote()) def on_layout(self, win_size): self.background.on_layout(win_size) self.foreground.on_layout(win_size) def on_update(self): self.anim_group.on_update() def add_note_sprite(self): self.anim_group.add(FadingMusicNote((320, 80)))
def __init__(self, norm, sandbox, mixer, client, client_id): self.norm = norm self.module_name = 'SoundBlock' self.sandbox = sandbox self.mixer = mixer self.tempo_map = SimpleTempoMap(bpm=60) self.sched = AudioScheduler(self.tempo_map) self.synth = Synth("data/FluidR3_GM.sf2") self.sched.set_generator(self.synth) self.mixer.add(self.sched) self.cmd = {} self.client = client self.cid = client_id self.instruments = { 'piano': 1, 'violin': 41, 'trumpet': 57, 'ocarina': 80, 'choir': 53 } self.inst_list = ['piano', 'violin', 'trumpet', 'ocarina', 'choir'] self.drumkit = { 'snare': 38, 'crash': 49, 'bass': 35, 'hihat': 46, 'triangle': 81 } self.drum_list = ['snare', 'crash', 'bass', 'hihat', 'triangle'] self.channel = 0 self.drum_channel = len(self.inst_list) # set up the correct sound (program: bank and preset) # each instrument is on a different channel for index, inst in enumerate(self.inst_list): self.synth.program(index, 0, self.instruments[inst]) for index, drum in enumerate(self.drum_list): self.synth.program(index + len(self.instruments), 128, 0) # many variables here are dicts because a user's module handler needs to keep track of # not just its own variables, but other users' variables as well! so we use dictionaries # with client ids as the keys. self.hold_point = {} self.hold_shape = {} # this variable is needed for when a user clicks on a soundblock in a touch_down event, # so that the corresponding touch_up event is skipped self.skip = {} self.color_dict = { 'red': (201 / 255, 108 / 255, 130 / 255), 'orange': (214 / 255, 152 / 255, 142 / 255), 'yellow': (238 / 255, 234 / 255, 202 / 255), 'green': (170 / 255, 220 / 255, 206 / 255), 'teal': (159 / 255, 187 / 255, 208 / 255), 'blue': (44 / 255, 85 / 255, 123 / 255), 'turquoise': (50 / 255, 147 / 255, 140 / 255), 'indigo': (46 / 255, 40 / 255, 90 / 255), 'violet': (147 / 255, 127 / 255, 159 / 255), 'pink': (199 / 255, 150 / 255, 170 / 255), 'peach': (238 / 255, 142 / 255, 154 / 255), 'magenta': (172 / 255, 69 / 255, 133 / 255), 'grey': (140 / 255, 143 / 255, 148 / 255), 'white': (239 / 255, 226 / 255, 222 / 255) } self.default_color = self.color_dict['violet'] self.default_pitch = 60 self.default_timbre = 'sine' self.default_instrument = 'piano' self.default_drum = 'snare' self.color = {} self.pitch = {} self.timbre = {} self.instrument = {} self.drum = {} self.display = False self.blocks = AnimGroup() self.sandbox.add(self.blocks) self.gui = BlockGUI(self.norm, pos=self.norm.nt((50, 100)), is_drum=False, pitch_callback=self.update_pitch, instrument_callback=self.update_instrument, drum_callback=self.update_drum) self.delete_mode = {}
class SoundBlockHandler(object): """ Handles user interaction and drawing of graphics before generating a SoundBlock. Also stores and updates all currently active SoundBlocks. """ def __init__(self, norm, sandbox, mixer, client, client_id): self.norm = norm self.module_name = 'SoundBlock' self.sandbox = sandbox self.mixer = mixer self.tempo_map = SimpleTempoMap(bpm=60) self.sched = AudioScheduler(self.tempo_map) self.synth = Synth("data/FluidR3_GM.sf2") self.sched.set_generator(self.synth) self.mixer.add(self.sched) self.cmd = {} self.client = client self.cid = client_id self.instruments = { 'piano': 1, 'violin': 41, 'trumpet': 57, 'ocarina': 80, 'choir': 53 } self.inst_list = ['piano', 'violin', 'trumpet', 'ocarina', 'choir'] self.drumkit = { 'snare': 38, 'crash': 49, 'bass': 35, 'hihat': 46, 'triangle': 81 } self.drum_list = ['snare', 'crash', 'bass', 'hihat', 'triangle'] self.channel = 0 self.drum_channel = len(self.inst_list) # set up the correct sound (program: bank and preset) # each instrument is on a different channel for index, inst in enumerate(self.inst_list): self.synth.program(index, 0, self.instruments[inst]) for index, drum in enumerate(self.drum_list): self.synth.program(index + len(self.instruments), 128, 0) # many variables here are dicts because a user's module handler needs to keep track of # not just its own variables, but other users' variables as well! so we use dictionaries # with client ids as the keys. self.hold_point = {} self.hold_shape = {} # this variable is needed for when a user clicks on a soundblock in a touch_down event, # so that the corresponding touch_up event is skipped self.skip = {} self.color_dict = { 'red': (201 / 255, 108 / 255, 130 / 255), 'orange': (214 / 255, 152 / 255, 142 / 255), 'yellow': (238 / 255, 234 / 255, 202 / 255), 'green': (170 / 255, 220 / 255, 206 / 255), 'teal': (159 / 255, 187 / 255, 208 / 255), 'blue': (44 / 255, 85 / 255, 123 / 255), 'turquoise': (50 / 255, 147 / 255, 140 / 255), 'indigo': (46 / 255, 40 / 255, 90 / 255), 'violet': (147 / 255, 127 / 255, 159 / 255), 'pink': (199 / 255, 150 / 255, 170 / 255), 'peach': (238 / 255, 142 / 255, 154 / 255), 'magenta': (172 / 255, 69 / 255, 133 / 255), 'grey': (140 / 255, 143 / 255, 148 / 255), 'white': (239 / 255, 226 / 255, 222 / 255) } self.default_color = self.color_dict['violet'] self.default_pitch = 60 self.default_timbre = 'sine' self.default_instrument = 'piano' self.default_drum = 'snare' self.color = {} self.pitch = {} self.timbre = {} self.instrument = {} self.drum = {} self.display = False self.blocks = AnimGroup() self.sandbox.add(self.blocks) self.gui = BlockGUI(self.norm, pos=self.norm.nt((50, 100)), is_drum=False, pitch_callback=self.update_pitch, instrument_callback=self.update_instrument, drum_callback=self.update_drum) self.delete_mode = {} def on_touch_down(self, cid, pos): if cid == self.cid: self.gui.on_touch_down(pos) if not self.sandbox.in_bounds(pos): return # when a block is clicked, flash and play a sound for block in self.blocks.objects: if in_bounds(pos, block.pos, block.size): if self.delete_mode[cid]: self.blocks.objects.remove(block) self.blocks.remove(block) return block.flash() self.skip[cid] = True return # don't start drawing a SoundBlock if self.delete_mode[cid]: return self.hold_point[cid] = pos self.hold_shape[cid] = Rectangle(pos=pos, size=(0, 0)) self.sandbox.add(Color(1, 1, 1)) self.sandbox.add(self.hold_shape[cid]) def on_touch_move(self, cid, pos): if not self.sandbox.in_bounds(pos): return #determine which direction rectangle is being created in hold_point = self.hold_point[cid] size = self.calculate_size(hold_point, pos) bottom_left = pos #moving northeast if pos[0] > hold_point[0] and pos[1] > hold_point[1]: bottom_left = hold_point #moving southeast elif pos[0] > hold_point[0] and pos[1] < hold_point[1]: bottom_left = (hold_point[0], pos[1]) #moving southwest elif pos[0] < hold_point[0] and pos[1] < hold_point[1]: bottom_left = pos #moving northwest elif pos[0] < hold_point[0] and pos[1] > hold_point[1]: bottom_left = (pos[0], hold_point[1]) self.hold_shape[cid].pos = bottom_left self.hold_shape[cid].size = size def on_touch_up(self, cid, pos): if self.skip.get(cid): self.skip[cid] = False return if self.delete_mode[cid]: return if self.hold_shape.get(cid) not in self.sandbox: if not self.sandbox.in_bounds(pos): return bottom_left = self.hold_shape[cid].pos size = self.hold_shape[cid].size if size[0] <= 10 or size[1] <= 10: self.sandbox.remove(self.hold_shape[cid]) return self.sandbox.remove(self.hold_shape[cid]) pitch = self.pitch[cid] color = self.color[cid] instrument = self.instrument[cid] self.channel = self.inst_list.index(instrument) drum = self.drum[cid] self.drum_channel = self.drum_list.index(drum) + len(self.inst_list) if self.gui.is_drum: block = SoundBlock(self.norm, self.sandbox, bottom_left, size, self.drum_channel, self.drumkit[drum], color, self, self.sound) else: block = SoundBlock(self.norm, self.sandbox, bottom_left, size, self.channel, pitch, color, self, self.sound) self.blocks.add(block) def on_key_down(self, cid, key): index = lookup(key, 'q2w3er5t6y7ui', range(13)) if index is not None: if self.cid == cid: self.gui.ps.select(index) if key == '[': if cid == self.cid: self.gui.ps.left_press() if key == ']': if cid == self.cid: self.gui.ps.right_press() if key == 'v' and cid == self.cid: self.delete_mode[cid] = not self.delete_mode[cid] self.update_server_state(post=True) if key == 'up': if not self.gui.is_drum: self.gui.switch_module() if key == 'down': if self.gui.is_drum: self.gui.switch_module() if self.gui.is_drum: drum = lookup(key, 'asdfg', ['snare', 'crash', 'bass', 'hihat', 'triangle']) if drum is not None: self.drum[cid] = drum if self.cid == cid: self.drum_channel = self.drum_list.index(drum) + len( self.inst_list) self.gui.ds.select(drum) else: instrument = lookup( key, 'asdfg', ['piano', 'violin', 'trumpet', 'ocarina', 'choir']) if instrument is not None: self.instrument[cid] = instrument if self.cid == cid: self.channel = self.inst_list.index(instrument) self.gui.ints.select( instrument) # have the GUI update as well def display_controls(self): info = 'delete mode: {}\n'.format(self.delete_mode[self.cid]) if self.gui.is_drum: info += 'drum: {}\n'.format(self.drum_list[self.drum_channel - len(self.inst_list)]) else: info += 'instrument: {}\n'.format(self.inst_list[self.channel]) return info def on_update(self): self.blocks.on_update() self.gui.on_update(Window.mouse_pos) def update_server_state(self, post=False): """ Update server state. If post is True, relay this updated state to all clients. """ state = { 'color': self.color, 'pitch': self.pitch, 'timbre': self.timbre, 'instrument': self.instrument, 'drum': self.drum, 'delete_mode': self.delete_mode } data = { 'module': self.module_name, 'cid': self.cid, 'state': state, 'post': post } self.client.emit('update_state', data) def update_client_state(self, cid, state): """ Update this handler's state. """ if cid != self.cid: # this client already updated its own state self.color = state['color'] self.pitch = state['pitch'] self.timbre = state['timbre'] self.instrument = state['instrument'] self.drum = state['drum'] self.delete_mode = state['delete_mode'] def sync_state(self, state): """ Initial sync with the server's copy of module state. We don't sync with hold_shape, hold_point, and hold_line because those objects are not json-serializable and are short-term values anyway. """ self.color = state['color'] self.pitch = state['pitch'] self.timbre = state['timbre'] self.instrument = state['instrument'] self.drum = state['drum'] self.delete_mode = state['delete_mode'] # after initial sync, add default values for this client self.color[self.cid] = self.default_color self.pitch[self.cid] = self.default_pitch self.timbre[self.cid] = self.default_timbre self.instrument[self.cid] = self.default_instrument self.drum[self.cid] = self.default_drum self.delete_mode[self.cid] = False self.skip[self.cid] = False # now that default values are set, we can display this module's info self.display = True # update server with these default values # post=True here because we want all other clients' states to update with this client's # default values. self.update_server_state(post=True) def sound(self, channel, pitch): """ Play a sound with a given pitch on the given channel. """ if self.cmd.get((channel, pitch)): self.sched.cancel(self.cmd[(channel, pitch)]) self.synth.noteon(channel, pitch, 100) now = self.sched.get_tick() self.cmd[(channel, pitch)] = self.sched.post_at_tick(self._noteoff, now + 240, (channel, pitch)) def update_pitch(self, color, pitch): """Update this client's color and pitch due to PitchSelect.""" self.color[self.cid] = self.color_dict[color] self.pitch[self.cid] = pitch self.update_server_state(post=True) def update_instrument(self, instrument): """Update this client's instrument due to InstrumentSelect.""" self.instrument[self.cid] = instrument self.channel = self.inst_list.index(instrument) self.update_server_state(post=True) def update_drum(self, drum): self.drum[self.cid] = drum self.drum_channel = self.drum_list.index(drum) + len(self.inst_list) self.update_server_state(post=True) def _noteoff(self, tick, args): channel, pitch = args self.synth.noteoff(channel, pitch) def calculate_size(self, corner, pos): x = abs(pos[0] - corner[0]) y = abs(pos[1] - corner[1]) return (x, y) def calculate_center(self, corner, size): c_x = corner[0] + (size[0] / 2) c_y = corner[1] + (size[1] / 2) return (c_x, c_y)
class TempoCursorHandler(object): """ Handles the TempoCursor GUI. Also stores and updates all currently active TempoCursors. """ def __init__(self, norm, sandbox, mixer, client, client_id, block_handler, tempo=60): self.norm = norm self.module_name = 'TempoCursor' self.sandbox = sandbox self.mixer = mixer self.client = client self.cid = client_id self.block_handler = block_handler self.tempo = tempo self.clock = Clock() self.tempo_map = SimpleTempoMap(bpm=self.tempo) self.touch_points = {} self.cursors = AnimGroup() self.sandbox.add(self.cursors) self.gui = CursorGUI(norm, pos=self.norm.nt((20, 300)), beat_callback=self.update_touch_points) self.delete_mode = {} def on_touch_down(self, cid, pos): if cid == self.cid: self.gui.on_touch_down(pos) if not self.sandbox.in_bounds(pos): return for cursor in self.cursors.objects: cursor_pos = (cursor.pos[0] - cursor.size[0] / 2, cursor.pos[1] - cursor.size[1] / 2) if in_bounds(pos, cursor_pos, cursor.size): if self.delete_mode[cid]: self.cursors.objects.remove(cursor) self.cursors.remove(cursor) return if self.delete_mode[cid]: return touch_points = self.touch_points[cid] if len(touch_points) == 0: return cursor = TempoCursor(self.norm, pos, self.tempo, self.clock, self.tempo_map, copy.deepcopy(touch_points), self.block_handler) self.cursors.add(cursor) def on_touch_move(self, cid, pos): pass def on_touch_up(self, cid, pos): pass def on_key_down(self, cid, key): if key == 'p': self.clock.toggle() if key == 'v' and cid == self.cid: self.delete_mode[cid] = not self.delete_mode[cid] self.update_server_state(post=True) if key == 'up': self.tempo += 4 self.tempo_map.set_tempo(self.tempo) self.update_server_state(post=True) if key == 'down': self.tempo -= 4 self.tempo_map.set_tempo(self.tempo) self.update_server_state(post=True) def on_update(self): self.cursors.on_update() def update_touch_points(self, touch_points): self.touch_points[self.cid] = touch_points self.update_server_state(post=True) def display_controls(self): cur_time = self.clock.get_time() cur_tick = self.tempo_map.time_to_tick(cur_time) info = 'delete mode: {}\n\n'.format(self.delete_mode[self.cid]) info += 'tempo: {}\n'.format(self.tempo) return info def update_server_state(self, post=False): """Update server state. If post is True, relay this updated state to all clients.""" state = { 'touch_points': self.touch_points, 'delete_mode': self.delete_mode, 'tempo': self.tempo } data = { 'module': self.module_name, 'cid': self.cid, 'state': state, 'post': post } self.client.emit('update_state', data) def update_client_state(self, cid, state): """Update this handler's state.""" if cid != self.cid: # this client already updated its own state self.touch_points = state['touch_points'] self.delete_mode = state['delete_mode'] self.tempo = state['tempo'] def sync_state(self, state): """ Initial sync with the server's copy of module state. """ self.touch_points = state['touch_points'] self.delete_mode = state['delete_mode'] self.tempo = state['tempo'] # after initial sync, add default values for this client self.touch_points[self.cid] = [] self.delete_mode[self.cid] = False # update server with these default values # post=True here because we want all other clients' states to update with this client's # default values. self.update_server_state(post=True)
class PhysicsBubbleHandler(object): """ Handles user interaction and drawing of graphics before generating a PhysicsBubble. Handles the PhysicsBubble GUI. Also stores and updates all currently active PhysicsBubbles. """ def __init__(self, norm, sandbox, mixer, client, client_id, block_handler): self.norm = norm self.module_name = 'PhysicsBubble' self.sandbox = sandbox self.mixer = mixer self.client = client self.cid = client_id self.block_handler = block_handler # many variables here are dicts because a user's module handler needs to keep track of # not just its own variables, but other users' variables as well! so we use dictionaries # with client ids as the keys. self.hold_line = {} self.hold_point = {} self.hold_shape = {} self.text = {} self.text_color = Color(0, 0, 0) # this mysterious variable is needed for a race condition in which touch_up events are # sometimes registered before touch_down events when the user clicks too fast, causing # touch_down and touch_up to occur at roughly the same time. if touch_up happens first, # it returns early. when touch_down is called later, it **skips** (hence the name) # adding shapes to the sandbox, after which this is toggled to False again. basically, # nothing is drawn to the screen, which indirectly prompts the user to try again. self.skip = {} self.color_dict = { 'red': (201 / 255, 108 / 255, 130 / 255), 'orange': (214 / 255, 152 / 255, 142 / 255), 'yellow': (238 / 255, 234 / 255, 202 / 255), 'green': (170 / 255, 220 / 255, 206 / 255), 'teal': (159 / 255, 187 / 255, 208 / 255), 'blue': (44 / 255, 85 / 255, 123 / 255), 'turquoise': (50 / 255, 147 / 255, 140 / 255), 'indigo': (46 / 255, 40 / 255, 90 / 255), 'violet': (147 / 255, 127 / 255, 159 / 255), 'pink': (199 / 255, 150 / 255, 170 / 255), 'peach': (238 / 255, 142 / 255, 154 / 255), 'magenta': (172 / 255, 69 / 255, 133 / 255), 'grey': (140 / 255, 143 / 255, 148 / 255), 'white': (239 / 255, 226 / 255, 222 / 255) } self.pitch_list = [60, 62, 64, 65, 67, 69, 71, 72] self.default_color = self.color_dict['red'] self.default_pitch = self.pitch_list[0] self.default_timbre = 'sine' self.default_bounces = 5 self.color = {} self.pitch = {} self.timbre = {} self.bounces = {} self.gravity = {} # flag used to only display controls when this module is synced # see on_update() and sync_state() self.display = False self.bubbles = AnimGroup() self.sandbox.add(self.bubbles) # GUI elements self.gui = BubbleGUI(self.norm, pos=self.norm.nt((50, 100)), pitch_callback=self.update_pitch, bounce_callback=self.update_bounces, gravity_callback=self.update_gravity, timbre_callback=self.update_timbre) def timbre_to_shape(self, timbre, pos): if timbre == 'sine': return CEllipse(cpos=pos, size=self.norm.nt((80, 80)), segments=20) elif timbre == 'triangle': return CEllipse(cpos=pos, size=self.norm.nt((90, 90)), segments=3) elif timbre == 'square': return CRectangle(cpos=pos, size=self.norm.nt((80, 80))) elif timbre == 'sawtooth': # square rotated 45 degrees return CEllipse(cpos=pos, size=self.norm.nt((90, 90)), segments=4) def on_touch_down(self, cid, pos): if cid == self.cid: self.gui.on_touch_down(pos) if not self.sandbox.in_bounds(pos): return # start drawing drag line and preview of the PhysicsBubble self.hold_point[cid] = pos self.hold_shape[cid] = self.timbre_to_shape(self.timbre[cid], pos) self.hold_line[cid] = Line(points=(*pos, *pos), width=3) self.text[cid] = CLabelRect(cpos=pos, text=str(self.bounces[cid])) # if self.skip.get(cid) == True: # self.skip[cid] = False # return self.sandbox.add(Color(*self.color[cid])) self.sandbox.add(self.hold_shape[cid]) self.sandbox.add(self.hold_line[cid]) self.sandbox.add(self.text_color) self.sandbox.add(self.text[cid]) def on_touch_move(self, cid, pos): if not self.sandbox.in_bounds(pos): return # update the position of the drag line and preview of the PhysicsBubble self.hold_shape[cid].set_cpos(pos) self.text[cid].set_cpos(pos) self.hold_line[cid].points = (*self.hold_point[cid], *pos) def on_touch_up(self, cid, pos): if (self.hold_shape.get(cid) not in self.sandbox) or \ (self.text.get(cid) not in self.sandbox) or \ (self.hold_line.get(cid) not in self.sandbox): # if we were currently drawing a preview shape/line but released the mouse out of # bounds, we should release the shape anyway as a QOL measure if not self.sandbox.in_bounds(pos): return # else: # self.skip[cid] = True # return self.sandbox.remove(self.hold_shape[cid]) self.sandbox.remove(self.text[cid]) self.sandbox.remove(self.hold_line[cid]) # calculate velocity hold_point = self.hold_point[cid] dx = pos[0] - hold_point[0] dy = pos[1] - hold_point[1] vel = (-dx, -dy) pitch = self.pitch[cid] timbre = self.timbre[cid] color = self.color[cid] bounces = self.bounces[cid] gravity = self.gravity[cid] # release the PhysicsBubble bubble = PhysicsBubble(self.norm, self.sandbox, pos, vel, pitch, timbre, color, bounces, self, gravity=gravity, callback=self.sound) self.bubbles.add(bubble) def on_key_down(self, cid, key): index = lookup(key, 'q2w3er5t6y7ui', range(13)) if index is not None: if self.cid == cid: self.gui.ps.select(index) if key == '[': if cid == self.cid: self.gui.ps.left_press() if key == ']': if cid == self.cid: self.gui.ps.right_press() d_bounces = lookup(key, ['right', 'left'], [1, -1]) if d_bounces is not None: self.bounces[cid] += d_bounces if cid == self.cid: self.gui.bs.update_bounces(self.bounces[cid]) timbre = lookup(key, 'asdf', ['sine', 'square', 'triangle', 'sawtooth']) if timbre is not None: self.timbre[cid] = timbre if self.cid == cid: self.gui.ts.select(timbre) # have the GUI update as well if key == 'g': # toggle gravity if cid == self.cid: self.gravity[cid] = not self.gravity[cid] self.gui.gs.toggle() # other clients should update their state to reflect this client's new selection. if self.cid == cid: # don't want every client updating server's state at the same time! self.update_server_state(post=False) def sound(self, pitch, timbre): """ Play a sound when a PhysicsBubble collides with a collidable object. """ note = NoteGenerator(pitch, 1, timbre) env = Envelope(note, 0.01, 1, 0.2, 2) self.mixer.add(env) def update_pitch(self, color, pitch): """Update this client's color and pitch due to PitchSelect.""" self.color[self.cid] = self.color_dict[color] self.pitch[self.cid] = pitch self.update_server_state(post=True) def update_timbre(self, timbre): """Update this client's timbre due to TimbreSelect.""" self.timbre[self.cid] = timbre self.update_server_state(post=True) def update_gravity(self, gravity): """Update this client's gravity due to GravitySelect.""" self.gravity[self.cid] = gravity self.update_server_state(post=True) def update_bounces(self, bounces): """Update this client's bounces due to BounceSelect.""" self.bounces[self.cid] = bounces self.update_server_state(post=True) def display_controls(self): """Provides additional text info specific to this module to go on the top-left label.""" return 'click and drag!' def on_update(self): self.bubbles.on_update() self.gui.on_update(Window.mouse_pos) def update_server_state(self, post=False): """Update server state. If post is True, relay this updated state to all clients.""" state = { 'color': self.color, 'pitch': self.pitch, 'timbre': self.timbre, 'bounces': self.bounces, 'gravity': self.gravity } data = { 'module': self.module_name, 'cid': self.cid, 'state': state, 'post': post } self.client.emit('update_state', data) def update_client_state(self, cid, state): """Update this handler's state.""" if cid != self.cid: # this client already updated its own state self.color = state['color'] self.pitch = state['pitch'] self.timbre = state['timbre'] self.bounces = state['bounces'] self.gravity = state['gravity'] def sync_state(self, state): """ Initial sync with the server's copy of module state. We don't sync with hold_shape, hold_point, and hold_line because those objects are not json-serializable and are short-term values anyway. """ self.color = state['color'] self.pitch = state['pitch'] self.timbre = state['timbre'] self.bounces = state['bounces'] self.gravity = state['gravity'] # after initial sync, add default values for this client self.color[self.cid] = self.default_color self.pitch[self.cid] = self.default_pitch self.timbre[self.cid] = self.default_timbre self.bounces[self.cid] = self.default_bounces self.gravity[self.cid] = False self.skip[self.cid] = False # now that default values are set, we can display this module's info self.display = True # update server with these default values # post=True here because we want all other clients' states to update with this client's # default values. self.update_server_state(post=True)
class MainWidget3(BaseWidget) : def __init__(self): super(MainWidget3, self).__init__() self.audio = Audio(2) self.synth = Synth('../data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.tempo_map = SimpleTempoMap(104) self.sched = AudioScheduler(self.tempo_map) # connect scheduler into audio system self.audio.set_generator(self.sched) self.sched.set_generator(self.synth) # create the metronome: self.metro = Metronome(self.sched, self.synth) percNotes = [(480,35), (360,42), (120,35), (480,35), (480,42)] self.base1Notes = [(240,43), (240,43), (240,43), (120,47), (240,41), (240,41), (360,41), (120,40), (360,41), (240,41), (240,41), (120,40), (120,36), (480,-1), (120,40), (240,41), (120,43)] self.base2Notes = [(120,-1), (120,45), (240,43), (120,-1), (240,43), (120,40), (480,43), (120,-1), (120,45), (120,45), (120,48), (240,-1), (240,41), (120,-1), (240,41), (120,40), (480,41), (120,-1), (120,45), (120,45), (120,48), (240,-1), (240,45), (120,-1), (240,45), (120,45), (480,45), (240,43), (120,-1), (120,45), (240,-1), (240,45), (120,-1), (240,45), (120,45), (480,45), (120,-1), (120,45), (120,45), (120,48)] self.baseNotes = self.base2Notes #[40, 41, 43, 45 48,] #changes / pitch sutff self.changes = [ (1920, [72, 74, 76, 79, 81, 84]), (1920, [69, 72, 74, 81]), (3840, [69, 72, 74, 76, 79, 81, 84])] self.changesIndex = 0 self.curChanges = [] self.selectSize = 2 self.lastPitchIndex = None self.lastTouch = None #Note length stuff self.noteLengths = [480, 240, 120] self.articulation = 1 self.lastPulseIndex = 0 #Declare the players self.perc = NoteSequencer(self.sched, self.synth, 1, (128,0), percNotes) self.base1 = NoteSequencer(self.sched, self.synth, 2, (0,33), self.base1Notes, callback = self.graphic_callback) self.base2 = NoteSequencer(self.sched, self.synth, 2, (0,33), self.base2Notes, callback = self.graphic_callback) self.lead = Arpeggiator(self.sched, self.synth, channel = 3, program = (0,65), callback = self.graphic_callback) self.lead.set_direction('updown') #Start the non-interactive stuff now = self.sched.get_tick() next_beat = quantize_tick_up(now, 480) self.perc.toggle() self.base2.toggle() self.sched.post_at_tick(self._updateChanges, next_beat) #Update changes as music starts self.sched.post_at_tick(self._spawnCrossBar, next_beat) # and text to display our status #self.label = topleft_label() #self.add_widget(self.label) #Graphics stuff self.objects = AnimGroup() self.canvas.add(self.objects) #self.allNotes = [40, 41, 43, 45, 48, 900, 69, 72, 74, 76, 79, 81, 84] self.allNotes = [36, 40, 41, 43, 45, 47, 48, 900, 69, 72, 74, 76, 79, 81, 84] def graphic_callback(self, pitch, length): w = Window.width numBuckets = len(self.allNotes) bucket = self.allNotes.index(pitch) widthOfBucket = w/numBuckets width = widthOfBucket - 10 leftX = bucket*widthOfBucket + 5 height = length/480 * 100 shape = NoteShape((leftX,0), height, width) self.objects.add(shape) def _spawnCrossBar(self, tick, ignore): shape = CrossBar() self.objects.add(shape) self.sched.post_at_tick(self._spawnCrossBar, tick+480) def _updateChanges(self, tick, ignore): timeTillNextChange = self.changes[self.changesIndex][0] self.curChanges = self.changes[self.changesIndex][1] #print("CHANGE OCCURED: ", self.curChanges) self.changesIndex = (self.changesIndex + 1) % len(self.changes) self.sched.post_at_tick(self._updateChanges, tick+timeTillNextChange) self.lastPitchIndex = None if self.lastTouch != None: self.update_pitches(self.lastTouch) def changeBaseLine(self): self.base1.toggle() self.base2.toggle() def on_key_down(self, keycode, modifiers): obj = lookup(keycode[1], 'm', (self.metro)) if obj is not None: obj.toggle() if keycode[1] == 'q': self.changeBaseLine() def on_key_up(self, keycode): pass def on_touch_down(self, touch): p = touch.pos self.update_pitches(p) self.update_pulse(p) self.lead.start() self.lastTouch = p def on_touch_up(self, touch): self.lead.stop() def on_touch_move(self, touch): p = touch.pos self.update_pitches(p) self.update_pulse(p) self.lastTouch = p def update_pitches(self, pos=(0,0)): mouseX = pos[0] w = Window.width numBuckets = len(self.curChanges) - self.selectSize + 1 sizeOfBucket = w / numBuckets noteBucket = int(mouseX // sizeOfBucket) if noteBucket != self.lastPitchIndex: arpegNotes = self.curChanges[noteBucket:noteBucket+self.selectSize] self.lead.set_pitches(arpegNotes) self.lastPitchIndex = noteBucket def update_pulse(self, pos=(0,0)): mouseY = pos[1] h = Window.height numBuckets = len(self.noteLengths) sizeOfBucket = h / numBuckets pulseBucket = int(mouseY // sizeOfBucket) if pulseBucket < len(self.noteLengths) and pulseBucket != self.lastPulseIndex: length = self.noteLengths[pulseBucket] self.lead.set_rhythm(length, self.articulation) self.lastPulseIndex = pulseBucket def on_update(self) : self.audio.on_update() self.objects.on_update()
def __init__(self): super(MainWidget3, self).__init__() self.audio = Audio(2) self.synth = Synth('../data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.tempo_map = SimpleTempoMap(104) self.sched = AudioScheduler(self.tempo_map) # connect scheduler into audio system self.audio.set_generator(self.sched) self.sched.set_generator(self.synth) # create the metronome: self.metro = Metronome(self.sched, self.synth) percNotes = [(480,35), (360,42), (120,35), (480,35), (480,42)] self.base1Notes = [(240,43), (240,43), (240,43), (120,47), (240,41), (240,41), (360,41), (120,40), (360,41), (240,41), (240,41), (120,40), (120,36), (480,-1), (120,40), (240,41), (120,43)] self.base2Notes = [(120,-1), (120,45), (240,43), (120,-1), (240,43), (120,40), (480,43), (120,-1), (120,45), (120,45), (120,48), (240,-1), (240,41), (120,-1), (240,41), (120,40), (480,41), (120,-1), (120,45), (120,45), (120,48), (240,-1), (240,45), (120,-1), (240,45), (120,45), (480,45), (240,43), (120,-1), (120,45), (240,-1), (240,45), (120,-1), (240,45), (120,45), (480,45), (120,-1), (120,45), (120,45), (120,48)] self.baseNotes = self.base2Notes #[40, 41, 43, 45 48,] #changes / pitch sutff self.changes = [ (1920, [72, 74, 76, 79, 81, 84]), (1920, [69, 72, 74, 81]), (3840, [69, 72, 74, 76, 79, 81, 84])] self.changesIndex = 0 self.curChanges = [] self.selectSize = 2 self.lastPitchIndex = None self.lastTouch = None #Note length stuff self.noteLengths = [480, 240, 120] self.articulation = 1 self.lastPulseIndex = 0 #Declare the players self.perc = NoteSequencer(self.sched, self.synth, 1, (128,0), percNotes) self.base1 = NoteSequencer(self.sched, self.synth, 2, (0,33), self.base1Notes, callback = self.graphic_callback) self.base2 = NoteSequencer(self.sched, self.synth, 2, (0,33), self.base2Notes, callback = self.graphic_callback) self.lead = Arpeggiator(self.sched, self.synth, channel = 3, program = (0,65), callback = self.graphic_callback) self.lead.set_direction('updown') #Start the non-interactive stuff now = self.sched.get_tick() next_beat = quantize_tick_up(now, 480) self.perc.toggle() self.base2.toggle() self.sched.post_at_tick(self._updateChanges, next_beat) #Update changes as music starts self.sched.post_at_tick(self._spawnCrossBar, next_beat) # and text to display our status #self.label = topleft_label() #self.add_widget(self.label) #Graphics stuff self.objects = AnimGroup() self.canvas.add(self.objects) #self.allNotes = [40, 41, 43, 45, 48, 900, 69, 72, 74, 76, 79, 81, 84] self.allNotes = [36, 40, 41, 43, 45, 47, 48, 900, 69, 72, 74, 76, 79, 81, 84]
class MainWidget2(BaseWidget) : def __init__(self): super(MainWidget2, self).__init__() self.audio = Audio(2) self.synth = Synth('../data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.tempo_map = SimpleTempoMap(120) self.sched = AudioScheduler(self.tempo_map) # connect scheduler into audio system self.audio.set_generator(self.sched) self.sched.set_generator(self.synth) # create the metronome: self.metro = Metronome(self.sched, self.synth) # create the arpeggiator: self.arpeg = Arpeggiator(self.sched, self.synth, channel = 1, program = (0,0) ) self.arpeg.set_direction('updown') #size of the notes sent to the arpeggiator self.arpegSize = 3 #all of the notes this program can make. However, only self.arpegSize notes are sent to the arpegiator at a time self.allNotes = [50, 53, 55, 56, 57, 60, 62, 65, 67, 68, 69, 72, 74] self.lastPitchIndex = None self.lastPulseIndex = None self.noteLengths = [240, 210, 180, 150, 120, 90, 60] self.articulation = .75 # and text to display our status self.label = topleft_label() self.add_widget(self.label) # and text to display our status self.label = topleft_label() self.add_widget(self.label) self.objects = AnimGroup() self.canvas.add(self.objects) self.add_lines() def add_lines(self): w = Window.width h = Window.height numBuckets = len(self.allNotes) - self.arpegSize sizeOfBucket = w / numBuckets for i in range(numBuckets): xVal = i * sizeOfBucket line = Line(points=[xVal, 0, xVal, h], width=2) self.objects.add(line) numBuckets = len(self.noteLengths) sizeOfBucket = h / numBuckets for i in range(numBuckets): yVal = i * sizeOfBucket line = Line(points=[0, yVal, w, yVal], width=2) self.objects.add(line) def on_touch_down(self, touch): p = touch.pos self.update_pitches(p) self.update_pulse(p) self.arpeg.start() def on_touch_up(self, touch): self.arpeg.stop() def on_touch_move(self, touch): p = touch.pos self.update_pitches(p) self.update_pulse(p) def update_pitches(self, pos=(0,0)): mouseX = pos[0] w = Window.width numBuckets = len(self.allNotes) - self.arpegSize sizeOfBucket = w / numBuckets noteBucket = int(mouseX // sizeOfBucket) if noteBucket != self.lastPitchIndex: arpegNotes = self.allNotes[noteBucket:noteBucket+self.arpegSize] self.lastSlice = arpegNotes self.arpeg.set_pitches(arpegNotes) self.lastPitchIndex = noteBucket def update_pulse(self, pos=(0,0)): mouseY = pos[1] h = Window.height numBuckets = len(self.noteLengths) sizeOfBucket = h / numBuckets pulseBucket = int(mouseY // sizeOfBucket) if pulseBucket < len(self.noteLengths) and pulseBucket != self.lastPulseIndex: length = self.noteLengths[pulseBucket] self.arpeg.set_rhythm(length, self.articulation) self.lastPulseIndex = pulseBucket def on_update(self) : self.audio.on_update() self.label.text = self.sched.now_str() + '\n'
class LevelSelectScreen(Screen): def __init__(self, **kwargs): super(LevelSelectScreen, self).__init__(**kwargs) Window.clearcolor = colors['grey'].rgba self.anim_group = AnimGroup() self.canvas.add(self.anim_group) self.label = Label(text='Play it by Gear', font_name='./fonts/PassionOne-Regular', color=(.165, .718, .792, 1)) self.add_widget(self.label) self.buttons = [] self.id_counter = 0 self.audio = Audio(2) self.mixer = Mixer() self.audio.set_generator(self.mixer) self.start_time = None self.switch_level = None self.border_color = colors['dark_grey'] self.border = Line(rectangle=(0, 0, Window.width, Window.height), width=Window.width * 0.03) self.anim_group.add(self.border_color) self.anim_group.add(self.border) self.on_layout((Window.width, Window.height)) def generate_buttons(self, row_pos, num_buttons, max_num_buttons, num_rows, margin): for i in range(num_buttons): self.id_counter += 1 square_dim = min(Window.width / (max_num_buttons + 2), Window.height / (num_rows + 1)) button = LevelButton(pos=(i / 6 * (Window.width) + margin, row_pos), size=(square_dim, square_dim), id=self.id_counter) self.buttons.append(button) self.anim_group.add(button) def on_touch_up(self, touch): for b in self.buttons: if b.is_clicked(touch): self.mixer.add( WaveGenerator(WaveFile("./data/button_press.wav"))) self.start_time = time.time() edit_progress_file(b.id, 'y') self.switch_level = 'Level ' + str(b.id) def on_enter(self): # update button colors by regenerating buttons self.on_layout((Window.width, Window.height)) def on_layout(self, winsize): # update level buttons self.anim_group.remove_all() self.id_counter = 0 self.buttons = [] self.generate_buttons(row_pos=(3 * Window.height / 5 - Window.height / 10), num_buttons=4, max_num_buttons=5, num_rows=3, margin=Window.width / 6) self.generate_buttons(row_pos=(2 * Window.height / 5 - Window.height / 10), num_buttons=5, max_num_buttons=5, num_rows=3, margin=Window.width / 12) self.generate_buttons(row_pos=(1 * Window.height / 5 - Window.height / 10), num_buttons=4, max_num_buttons=5, num_rows=3, margin=Window.width / 6) # update title label self.label.center_x = Window.width / 2 self.label.center_y = 5 * Window.height / 6 self.label.font_size = str(Window.width // 15) + 'sp' # update border self.border_color = colors['dark_grey'] self.border = Line(rectangle=(0, 0, Window.width, Window.height), width=Window.width * 0.03) self.anim_group.add(self.border_color) self.anim_group.add(self.border) def on_update(self): self.audio.on_update() if self.start_time and time.time() - self.start_time > 0.13: self.start_time = None self.switch_to(self.switch_level)
class PhotoEditor(Screen): def __init__(self, sm, **kwargs): super(PhotoEditor, self).__init__(**kwargs) # start speech recognition thread threading.Thread(target=speech.speech_main, args=(self, )).start() self.sm = sm # initialize image object self.picture = Picture(filepath) self.canvas.add(self.picture) # create slider group self.slider_group = AnimGroup() self.canvas.add(self.slider_group) # initialize slider self.slider = Slider(1.0, 0, 4) self.slider_line = Line(points=[ 0.98 * Window.width, 0.02 * Window.height, 0.98 * Window.width, 0.98 * Window.height ], width=10) self.slider_group.add(self.slider_line) dim_size = min(0.03 * Window.width, 0.03 * Window.height) self.canvas.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.slider_knob = CEllipse(cpos=(0.98 * Window.width, 0.02 * Window.height), csize=(dim_size, dim_size)) self.slider_group.add(self.slider_knob) # create overlay (4 rectangles within overlay class to mimic crop frame) self.overlay = Overlay(0, 0, 0, 0, self.picture.rectangle.size, self.picture.rectangle.pos) self.canvas.add(self.overlay) # current modification self.mode; possible options are: # - None: no self.mode selected # - 'brightness' : change brightness using slider # - 'contrast' : change contrast using slider # - 'zoom' : makes photo bigger / smaller # - 'crop' : change size of photo self.modes = [ 'save', 'redo', 'undo', 'sticker', 'pixelate', 'invert', 'grayscale', 'transparent', 'saturation', 'sharpness', 'brightness', 'contrast', 'rotate', 'zoom', 'crop' ] self.slider_modes = [ 'brightness', 'contrast', 'saturation', 'sharpness' ] self.mode = None self.mode_instructions = { # we only have instructions for modes that you enter, modes that are applied as soon as the mode's voice command is said are omitted 'crop' : '[font=./fonts/Cairo-Bold]Crop Instructions: [/font][font=./fonts/Cairo-Regular] Use one or two hands; Start with hand(s) outside of the image and move your hand\ntowards the screen until the cursor(s) turns green. Then, move your hands left/right/up/down in toward the picture\nuntil you are happy with the crop. Finally, pull your hand(s) away from the screen until the cursor(s) turn yellow.[/font]', 'zoom' : '[font=./fonts/Cairo-Bold]Zoom Instructions: [/font][font=./fonts/Cairo-Regular] Move two hands towards the screen until both cursors turns green, then move\nthem horizontally/vertically towards or away from each other to zoom out or zoom in. When you are satified,\npull both hands away from the screen until the cursors turn yellow.\n\nTo zoom using your voice, say "in" to zoom in, and "out" to zoom out.[/font]', 'rotate' : '[font=./fonts/Cairo-Bold]Rotate Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then rotate your hand as if you were\nturning a knob so that your fingers face left/right. The picture will rotate 90º in the direction of your fingers.\n\nTo rotate the picture using your voice, say "rotate" and the picture will rotate 90º counterclockwise.[/font]', 'contrast' : '[font=./fonts/Cairo-Bold]Contrast Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the contrast. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the contrast using your voice, say "increase" (or a synonym) to increase the contrast, and "decrease" (or a\nsynonym) to decrease the contrast.[/font]', 'brightness' : '[font=./fonts/Cairo-Bold]Brightness Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the brightness. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the brightness using your voice, say "increase" (or a synonym) to increase the brightness, and "decrease" (or a\nsynonym)to decrease the brightness.[/font]', 'sharpness' : '[font=./fonts/Cairo-Bold]Sharpness Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the sharpness. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the sharpness using your voice, say "increase" (or a synonym) to increase the sharpness, and "decrease" (or a\nsynonym)to decrease the sharpness.[/font]', 'saturation' : '[font=./fonts/Cairo-Bold]Saturation Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the saturation. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the saturation using your voice, say "increase" (or a synonym) to increase the saturation, and "decrease" (or a\nsynonym)to decrease the saturation.[/font]', 'pixelate' : '[font=./fonts/Cairo-Bold]Pixelate Instructions: [/font][font=./fonts/Cairo-Regular] To increase the pixelation of the image by voice, say "increase" (or a synonym).\nTo return the picture to its original state, say "undo." [/font]', 'sticker' : '[font=./fonts/Cairo-Bold]Sticker Instructions: [/font][font=./fonts/Cairo-Regular] View the names of the stickers on the bottom of the screen by hovering over them. Then, hover\nover the part of the image where you want to place the sticker and say the name of the sticker you want to apply.[/font]', 'transparent' : '[font=./fonts/Cairo-Bold]Transparent Instructions: [/font][font=./fonts/Cairo-Regular] Hover over the area you wish to make transparent and say "apply". You can also modify\nthe transparency threshold in settings. To get to the settings page, hover over the gear in the top right corner.[/font]', } self.mode_instructions_label = Label(halign='center', markup=True) self.add_widget(self.mode_instructions_label) # add icon bar on left_hand side self.icon_label = Label() self.icon_bar = IconBar(self.modes, self.icon_label) self.canvas.add(self.icon_bar) self.add_widget(self.icon_label) # sticker bar self.sticker_label = Label() self.sticker_bar = StickerBar(self.sticker_label) self.add_widget(self.sticker_label) # settings button self.settings_button = SensorButton( size=(Window.width / 15, Window.width / 15), pos=(0.97 * Window.width - Window.width / 15, 0.98 * Window.height - Window.width / 15), mode='hover', texture=CoreImage('icons/settings.png').texture) self.canvas.add(self.settings_button) # menu button self.menu_button = SensorButton( size=(Window.width / 15, Window.width / 15), pos=(0.96 * Window.width - 2 * Window.width / 15, 0.98 * Window.height - Window.width / 15), mode='hover', texture=CoreImage('icons/menu.png').texture) self.canvas.add(self.menu_button) self.switch_to_timer = None # Hand objects that represent and show palm positions self.hands = [Hand(1), Hand(2)] for h in self.hands: self.canvas.add(h) def on_enter(self): print(self.sm.recent_screen) if self.sm.recent_screen == 'home': if self.picture in self.canvas.children: self.canvas.remove(self.picture) self.picture = Picture(filepath) self.canvas.add(Color(1, 1, 1, 1)) self.canvas.add(self.picture) self.canvas.remove(self.overlay) self.canvas.add(self.overlay) self.settings_button.on_update() self.menu_button.on_update() # reset icon bar self.canvas.remove(self.icon_bar) self.icon_bar = IconBar(self.modes, self.icon_label) self.canvas.add(self.icon_bar) # reset sticker bar if self.sticker_bar in self.canvas.children: self.canvas.remove(self.sticker_bar) self.sticker_bar = StickerBar(self.sticker_label) self.canvas.add(self.sticker_bar) else: self.sticker_bar = StickerBar(self.sticker_label) for h in self.hands: self.canvas.remove(h) self.canvas.add(h) self.on_layout((Window.width, Window.height)) def on_update(self): if DARK_MODE: self.sticker_label.color = (1, 1, 1, 1) self.icon_label.color = (1, 1, 1, 1) self.mode_instructions_label.color = (1, 1, 1, 1) else: self.sticker_label.color = (0, 0, 0, 1) self.icon_label.color = (0, 0, 0, 1) self.mode_instructions_label.color = (0, 0, 0, 1) # update instructions for the mode if applicable if ENABLE_INSTRUCTIONS and self.mode in self.mode_instructions.keys(): self.mode_instructions_label.text = self.mode_instructions[ self.mode] else: self.mode_instructions_label.text = '' screen_hands = list(filter(lambda h: not h.id == -1, self.hands)) # icon menu bar hover for hand in screen_hands: hovered_icon = self.icon_bar.identify_icon( hand.pos[0] * Window.width, hand.pos[1] * Window.height) if not hovered_icon == None: self.icon_bar.show_label(hovered_icon) else: self.icon_label.text = '' # sticker menu bar hover if self.sticker_bar in self.canvas.children: for hand in screen_hands: hovered_sticker = self.sticker_bar.identify_sticker( hand.pos[0] * Window.width, hand.pos[1] * Window.height) if not hovered_sticker == None: self.sticker_bar.show_label(hovered_sticker) else: self.sticker_label.text = '' active_hands = list(filter(lambda h: h.active, self.hands)) # update icon bar to show current mode if not self.mode == None: self.icon_bar.change_mode(self.modes.index(self.mode)) ###################################### ## SLIDER ## ###################################### self.update_slider(active_hands=active_hands) ###################################### ## SETTINGS/MENU BUTTON ## ###################################### if screen_hands: norm_pt = scale_point(screen_hands[0].leap_hand.palm_pos, kLeapRange) screen_xy = screen_hands[0].hand.to_screen_xy(norm_pt) self.settings_button.set_screen_pos(screen_xy, norm_pt[2]) self.menu_button.set_screen_pos(screen_xy, norm_pt[2]) if self.settings_button.is_on and self.switch_to_timer == None: self.switch_to_timer = time.time() self.switch_to('settings') if self.menu_button.is_on and self.switch_to_timer == None: self.switch_to_timer = time.time() self.switch_to('home') ###################################### ## CROP ## ###################################### # one handed if self.mode == 'crop' and len(active_hands) == 1: # get crop zone (top, right, left, bottom) image_pos = self.picture.rectangle.pos image_size = self.picture.rectangle.size hand_pos = active_hands[0].pos # get magnitude of crop crop_amount = active_hands[0].pos - active_hands[0].recent_pos # if hand is directly to the left of the image: if hand_pos[0] * Window.width < image_pos[0] and image_pos[ 1] < hand_pos[1] * Window.height < image_pos[ 1] + image_size[1]: self.overlay.change_left_delta(crop_amount[0] * Window.width) # if hand is directly to the right of the image: if image_pos[0] + image_size[0] < hand_pos[ 0] * Window.width and image_pos[1] < hand_pos[ 1] * Window.height < image_pos[1] + image_size[1]: self.overlay.change_right_delta(-crop_amount[0] * Window.width) # if hand is directly below the image: if hand_pos[1] * Window.height < image_pos[1] and image_pos[ 0] < hand_pos[0] * Window.width < image_pos[ 0] + image_size[0]: self.overlay.change_bottom_delta(crop_amount[1] * Window.height) # if hand is directly above the image: if image_pos[1] + image_size[1] < hand_pos[ 1] * Window.height and image_pos[0] < hand_pos[ 0] * Window.width < image_pos[0] + image_size[0]: self.overlay.change_top_delta(-crop_amount[1] * Window.height) # if hand is in top left if hand_pos[0] * Window.width < image_pos[0] and image_pos[ 1] + image_size[1] < hand_pos[1] * Window.height: self.overlay.change_left_delta(crop_amount[0] * Window.width) self.overlay.change_top_delta(-crop_amount[1] * Window.height) # if hand is in top right if image_pos[0] + image_size[ 0] < hand_pos[0] * Window.width and image_pos[ 1] + image_size[1] < hand_pos[1] * Window.height: self.overlay.change_right_delta(-crop_amount[0] * Window.width) self.overlay.change_top_delta(-crop_amount[1] * Window.height) # if hand is in bottom left if hand_pos[0] * Window.width < image_pos[ 0] and hand_pos[1] * Window.height < image_pos[1]: self.overlay.change_left_delta(crop_amount[0] * Window.width) self.overlay.change_bottom_delta(crop_amount[1] * Window.height) # if hand is in bottom right if image_pos[0] + image_size[ 0] < hand_pos[0] * Window.width and hand_pos[ 1] * Window.height < image_pos[1]: self.overlay.change_right_delta(-crop_amount[0] * Window.width) self.overlay.change_bottom_delta(crop_amount[1] * Window.height) # two handed if self.mode == 'crop' and len(active_hands) == 2: # get crop zone (top, right, left, bottom) image_pos = self.picture.rectangle.pos image_size = self.picture.rectangle.size hand_pos_A = active_hands[0].pos hand_pos_B = active_hands[1].pos # get magnitude of crop crop_amount_A = active_hands[0].pos - active_hands[0].recent_pos crop_amount_B = active_hands[1].pos - active_hands[1].recent_pos # hand A in top left if hand_pos_A[0] < 0.5 and hand_pos_A[1] > 0.5: self.overlay.change_left_delta(crop_amount_A[0] * Window.width) self.overlay.change_top_delta(-crop_amount_A[1] * Window.height) # hand B in top left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] > 0.5: self.overlay.change_left_delta(crop_amount_B[0] * Window.width) self.overlay.change_top_delta(-crop_amount_B[1] * Window.height) # hand A in top right if hand_pos_A[0] > 0.5 and hand_pos_A[1] > 0.5: self.overlay.change_right_delta(-crop_amount_A[0] * Window.width) self.overlay.change_top_delta(-crop_amount_A[1] * Window.height) # hand B in top right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] > 0.5: self.overlay.change_right_delta(-crop_amount_B[0] * Window.width) self.overlay.change_top_delta(-crop_amount_B[1] * Window.height) # hand A in bottom left if hand_pos_A[0] < 0.5 and hand_pos_A[1] < 0.5: self.overlay.change_left_delta(crop_amount_A[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_A[1] * Window.height) # hand B in bottom left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] < 0.5: self.overlay.change_left_delta(crop_amount_B[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_B[1] * Window.height) # hand A in bottom right if hand_pos_A[0] > 0.5 and hand_pos_A[1] < 0.5: self.overlay.change_right_delta(-crop_amount_A[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_A[1] * Window.height) # hand B in bottom right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] < 0.5: self.overlay.change_right_delta(-crop_amount_B[0] * Window.width) self.overlay.change_bottom_delta(crop_amount_B[1] * Window.height) ###################################### ## ROTATE ## ###################################### if self.mode == 'rotate' and len(active_hands) == 1: if all([ state == None for state in active_hands[0].recent_turn_states ]) and active_hands[0].turn_state == 'right': self.picture.change_rotation(angle=90, update_dims=True) self.picture.on_update() self.picture.update() elif all([ state == None for state in active_hands[0].recent_turn_states ]) and active_hands[0].turn_state == 'left': self.picture.change_rotation(angle=-90, update_dims=True) self.picture.on_update() self.picture.update() ###################################### ## ZOOM ## ###################################### self.update_on_zoom(active_hands=active_hands) ###################################### ## HAND OBJECTS ## ###################################### # update each hand for h in self.hands: h.on_update() # eliminate rapid screen switching bug if self.switch_to_timer and time.time() - self.switch_to_timer > 2: self.switch_to_timer = None def update_slider(self, active_hands=[], keyword=None): # update slider value if in slider self.mode and 1 hand is engaged (i.e. z < 0.6) if (self.mode in self.slider_modes) and \ (len(active_hands) == 1 or keyword in commands.slider_commands): # add green slider knob self.slider_group.remove_all() self.slider_group.add(self.slider_line) self.slider_group.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.slider_group.add(self.slider_knob) if len(active_hands) == 1: delta = active_hands[0].pos - active_hands[0].recent_pos delta_y = delta[1] elif keyword in commands.slider_commands: if keyword == 'up': delta_y = speech_deltas['slider'] elif keyword == 'down': delta_y = -speech_deltas['slider'] percent = self.slider.change_value_delta(delta_y) self.slider_knob.cpos = (self.slider_knob.cpos[0], Window.height * (0.96 * percent + 0.02)) # update brightness or contrast based on slider value if self.mode == 'pixelate': x = self.slider.value new_min = 0 new_max = 1 old_min = 1 old_max = 4 normalized_x = ((new_max - new_min) / (old_max - old_min)) * (x - old_max) + new_max print(x) print(normalized_x) # self.picture.pixelate(self.slider.value) self.picture.pixelate(normalized_x) # if self.mode == 'rotate': # self.picture.change_rotation(angle=90*(self.slider.value-1), update_dims=False) elif self.mode in self.slider_modes: # works for change_contrast, change_brightness, change_sharpness, change_saturation eval('self.picture.change_' + self.mode + '(' + str(self.slider.value) + ')') self.picture.on_update() elif (self.mode in self.slider_modes): # add yellow slider knob self.slider_group.remove_all() self.slider_group.add(self.slider_line) self.slider_group.add(Color(rgb=(.94, .76, 0))) self.slider_group.add(self.slider_knob) else: self.slider_group.remove_all() def update_on_zoom(self, active_hands=[], keyword=None): if self.mode == 'zoom': # two handed if len(active_hands) == 2: hand_pos_A = active_hands[0].pos hand_pos_B = active_hands[1].pos # get magnitude of zoom zoom_amount_A = active_hands[0].pos - active_hands[0].recent_pos zoom_amount_B = active_hands[1].pos - active_hands[1].recent_pos delta_w = 0 delta_h = 0 # hand A in top left if hand_pos_A[0] < 0.5 and hand_pos_A[1] > 0.5: delta_w += -zoom_amount_A[0] * Window.width delta_h += zoom_amount_A[1] * Window.height # hand B in top left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] > 0.5: delta_w += -zoom_amount_B[0] * Window.width delta_h += zoom_amount_B[1] * Window.height # hand A in top right if hand_pos_A[0] > 0.5 and hand_pos_A[1] > 0.5: delta_w += zoom_amount_A[0] * Window.width delta_h += zoom_amount_A[1] * Window.height # hand B in top right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] > 0.5: delta_w += zoom_amount_B[0] * Window.width delta_h += zoom_amount_B[1] * Window.height # hand A in bottom left if hand_pos_A[0] < 0.5 and hand_pos_A[1] < 0.5: delta_w += -zoom_amount_A[0] * Window.width delta_h += -zoom_amount_A[1] * Window.height # hand B in bottom left elif hand_pos_B[0] < 0.5 and hand_pos_B[1] < 0.5: delta_w += -zoom_amount_B[0] * Window.width delta_h += -zoom_amount_B[1] * Window.height # hand A in bottom right if hand_pos_A[0] > 0.5 and hand_pos_A[1] < 0.5: delta_w += zoom_amount_A[0] * Window.width delta_h += -zoom_amount_A[1] * Window.height # hand B in bottom right elif hand_pos_B[0] > 0.5 and hand_pos_B[1] < 0.5: delta_w += zoom_amount_B[0] * Window.width delta_h += -zoom_amount_B[1] * Window.height self.picture.zoom_delta(delta_w=delta_w, delta_h=delta_h) elif keyword: if keyword == 'in': delta_w = speech_deltas['zoom'] delta_h = speech_deltas['zoom'] elif keyword == 'out': delta_w = -speech_deltas['zoom'] delta_h = -speech_deltas['zoom'] delta_w *= Window.width delta_h *= Window.height self.picture.zoom_delta(delta_w=delta_w, delta_h=delta_h) def on_layout(self, win_size): for h in self.hands: h.on_layout(win_size) self.picture.on_layout(win_size) self.overlay.on_layout(self.picture.rectangle.size, self.picture.rectangle.pos) # update slider positioning percent = self.slider.get_slider_percent() self.slider_knob.cpos = (0.98 * Window.width, Window.height * (0.96 * percent + 0.02)) dim_size = min(0.03 * Window.width, 0.03 * Window.height) self.slider_knob.csize = (dim_size, dim_size) self.slider_line.points = [ 0.98 * Window.width, 0.02 * Window.height, 0.98 * Window.width, 0.98 * Window.height ] # update side mode icon bar self.icon_bar.on_layout() # update sticker bar self.sticker_bar.on_layout() # update settings and menu buttons self.settings_button.update_pos_and_size( pos=(0.97 * Window.width - Window.width / 15, 0.98 * Window.height - Window.width / 15), size=(Window.width / 15, Window.width / 15)) self.menu_button.update_pos_and_size( pos=(0.96 * Window.width - 2 * Window.width / 15, 0.98 * Window.height - Window.width / 15), size=(Window.width / 15, Window.width / 15)) # update instructions label self.mode_instructions_label.center_x = Window.width / 2 self.mode_instructions_label.center_y = 19 * Window.height / 20 self.mode_instructions_label.font_size = str( Window.width // 170) + 'sp' # maps key presses to changing editing self.mode # to be replaced by speech commands in V2 @mainthread def on_speech_recognized(self, keyword): print("keyword: ", keyword) old_mode = self.mode # change mode if keyword relates to a mode if keyword in self.modes: self.mode = keyword if keyword in commands.terminator: sys.exit() # TODO: if you call undo just once it sometimes doesn't work the first time if self.mode == 'undo': self.picture.undo() self.picture.on_update() self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) elif self.mode == 'redo': self.picture.redo() self.picture.on_update() self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) elif self.mode in self.slider_modes and keyword in commands.slider_commands: self.update_slider(keyword=keyword) elif self.mode == 'zoom' and keyword in commands.zoom_commands: self.update_on_zoom(keyword=keyword) # if switching from one self.mode to a different self.mode, update picture history elif not old_mode == self.mode: if old_mode == 'zoom': self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) # update slider to default value to start fresh next time if old_mode in self.slider_modes: percent = self.slider.change_value(1.0) self.slider_knob.cpos = (self.slider_knob.cpos[0], Window.height * (0.96 * percent + 0.02)) # commit crop and remove crop overlay if old_mode == 'crop': # actually crop self.picture.temp self.picture.crop(l=self.overlay.left_width, t=self.overlay.top_height, r=self.overlay.right_width, b=self.overlay.bottom_height) # reset crop overlay width and height params to 0,0,0,0 self.overlay.reset() self.picture.on_update() # ensure cropping change appears self.overlay.update_pos_and_size(self.picture.rectangle.size, self.picture.rectangle.pos) # update image history with changes self.picture.update( ) # TODO: (fix) currently this updates history every time a self.mode is changed, even if the photo is not modified while in that self.mode # if current mode is sticker, add a sticker based on its name if self.mode == 'sticker': if self.sticker_bar not in self.canvas.children: self.canvas.add(self.sticker_bar) if keyword in self.sticker_bar.sticker_names: screen_hands = list( filter(lambda h: not h.id == -1, self.hands)) # if hand on screen and within picture area if len( screen_hands ) == 1 and self.picture.rectangle.pos[0] < screen_hands[0].pos[ 0] * Window.width < self.picture.rectangle.pos[ 0] + self.picture.rectangle.size[ 0] and self.picture.rectangle.pos[ 1] < screen_hands[0].pos[ 1] * Window.height < self.picture.rectangle.pos[ 1] + self.picture.rectangle.size[1]: # get picture position (ignoring screen position of the image) x = screen_hands[0].pos[ 0] * Window.width - self.picture.rectangle.pos[0] y = self.picture.rectangle.size[1] - ( screen_hands[0].pos[1] * Window.height - self.picture.rectangle.pos[1]) self.picture.add_sticker('./stickers/' + keyword + '.png', x, y) self.picture.on_update() self.picture.update() else: self.canvas.remove(self.sticker_bar) if self.mode == 'transparent': if keyword == "apply": screen_hands = list( filter(lambda h: not h.id == -1, self.hands)) if len( screen_hands ) == 1 and self.picture.rectangle.pos[0] < screen_hands[0].pos[ 0] * Window.width < self.picture.rectangle.pos[ 0] + self.picture.rectangle.size[ 0] and self.picture.rectangle.pos[ 1] < screen_hands[0].pos[ 1] * Window.height < self.picture.rectangle.pos[ 1] + self.picture.rectangle.size[1]: x = screen_hands[0].pos[ 0] * Window.width - self.picture.rectangle.pos[0] y = screen_hands[0].pos[ 1] * Window.height - self.picture.rectangle.pos[1] mask = self.picture.magic_wand(x, y, transparency_threshold) self.picture.make_transparent(mask) self.picture.on_update() self.picture.update() if self.mode == 'rotate': if old_mode == "rotate": self.picture.change_rotation(angle=90, update_dims=True) self.picture.on_update() self.picture.update() elif self.mode == 'invert': self.picture.invert() self.picture.on_update() self.picture.update() elif self.mode == 'grayscale': self.picture.grayscale() self.picture.on_update() self.picture.update() # if current mode is save, save the image if self.mode == 'save': self.picture.save_image()
def __init__(self, sm, **kwargs): super(PhotoEditor, self).__init__(**kwargs) # start speech recognition thread threading.Thread(target=speech.speech_main, args=(self, )).start() self.sm = sm # initialize image object self.picture = Picture(filepath) self.canvas.add(self.picture) # create slider group self.slider_group = AnimGroup() self.canvas.add(self.slider_group) # initialize slider self.slider = Slider(1.0, 0, 4) self.slider_line = Line(points=[ 0.98 * Window.width, 0.02 * Window.height, 0.98 * Window.width, 0.98 * Window.height ], width=10) self.slider_group.add(self.slider_line) dim_size = min(0.03 * Window.width, 0.03 * Window.height) self.canvas.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.slider_knob = CEllipse(cpos=(0.98 * Window.width, 0.02 * Window.height), csize=(dim_size, dim_size)) self.slider_group.add(self.slider_knob) # create overlay (4 rectangles within overlay class to mimic crop frame) self.overlay = Overlay(0, 0, 0, 0, self.picture.rectangle.size, self.picture.rectangle.pos) self.canvas.add(self.overlay) # current modification self.mode; possible options are: # - None: no self.mode selected # - 'brightness' : change brightness using slider # - 'contrast' : change contrast using slider # - 'zoom' : makes photo bigger / smaller # - 'crop' : change size of photo self.modes = [ 'save', 'redo', 'undo', 'sticker', 'pixelate', 'invert', 'grayscale', 'transparent', 'saturation', 'sharpness', 'brightness', 'contrast', 'rotate', 'zoom', 'crop' ] self.slider_modes = [ 'brightness', 'contrast', 'saturation', 'sharpness' ] self.mode = None self.mode_instructions = { # we only have instructions for modes that you enter, modes that are applied as soon as the mode's voice command is said are omitted 'crop' : '[font=./fonts/Cairo-Bold]Crop Instructions: [/font][font=./fonts/Cairo-Regular] Use one or two hands; Start with hand(s) outside of the image and move your hand\ntowards the screen until the cursor(s) turns green. Then, move your hands left/right/up/down in toward the picture\nuntil you are happy with the crop. Finally, pull your hand(s) away from the screen until the cursor(s) turn yellow.[/font]', 'zoom' : '[font=./fonts/Cairo-Bold]Zoom Instructions: [/font][font=./fonts/Cairo-Regular] Move two hands towards the screen until both cursors turns green, then move\nthem horizontally/vertically towards or away from each other to zoom out or zoom in. When you are satified,\npull both hands away from the screen until the cursors turn yellow.\n\nTo zoom using your voice, say "in" to zoom in, and "out" to zoom out.[/font]', 'rotate' : '[font=./fonts/Cairo-Bold]Rotate Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then rotate your hand as if you were\nturning a knob so that your fingers face left/right. The picture will rotate 90º in the direction of your fingers.\n\nTo rotate the picture using your voice, say "rotate" and the picture will rotate 90º counterclockwise.[/font]', 'contrast' : '[font=./fonts/Cairo-Bold]Contrast Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the contrast. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the contrast using your voice, say "increase" (or a synonym) to increase the contrast, and "decrease" (or a\nsynonym) to decrease the contrast.[/font]', 'brightness' : '[font=./fonts/Cairo-Bold]Brightness Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the brightness. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the brightness using your voice, say "increase" (or a synonym) to increase the brightness, and "decrease" (or a\nsynonym)to decrease the brightness.[/font]', 'sharpness' : '[font=./fonts/Cairo-Bold]Sharpness Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the sharpness. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the sharpness using your voice, say "increase" (or a synonym) to increase the sharpness, and "decrease" (or a\nsynonym)to decrease the sharpness.[/font]', 'saturation' : '[font=./fonts/Cairo-Bold]Saturation Instructions: [/font][font=./fonts/Cairo-Regular] Move one hand towards the screen until the cursor turns green, then move your hand up/down to\nincrease/decrease the saturation. When you are satified, pull your hand away from the screen until the cursor turns yellow.\n\nTo change the saturation using your voice, say "increase" (or a synonym) to increase the saturation, and "decrease" (or a\nsynonym)to decrease the saturation.[/font]', 'pixelate' : '[font=./fonts/Cairo-Bold]Pixelate Instructions: [/font][font=./fonts/Cairo-Regular] To increase the pixelation of the image by voice, say "increase" (or a synonym).\nTo return the picture to its original state, say "undo." [/font]', 'sticker' : '[font=./fonts/Cairo-Bold]Sticker Instructions: [/font][font=./fonts/Cairo-Regular] View the names of the stickers on the bottom of the screen by hovering over them. Then, hover\nover the part of the image where you want to place the sticker and say the name of the sticker you want to apply.[/font]', 'transparent' : '[font=./fonts/Cairo-Bold]Transparent Instructions: [/font][font=./fonts/Cairo-Regular] Hover over the area you wish to make transparent and say "apply". You can also modify\nthe transparency threshold in settings. To get to the settings page, hover over the gear in the top right corner.[/font]', } self.mode_instructions_label = Label(halign='center', markup=True) self.add_widget(self.mode_instructions_label) # add icon bar on left_hand side self.icon_label = Label() self.icon_bar = IconBar(self.modes, self.icon_label) self.canvas.add(self.icon_bar) self.add_widget(self.icon_label) # sticker bar self.sticker_label = Label() self.sticker_bar = StickerBar(self.sticker_label) self.add_widget(self.sticker_label) # settings button self.settings_button = SensorButton( size=(Window.width / 15, Window.width / 15), pos=(0.97 * Window.width - Window.width / 15, 0.98 * Window.height - Window.width / 15), mode='hover', texture=CoreImage('icons/settings.png').texture) self.canvas.add(self.settings_button) # menu button self.menu_button = SensorButton( size=(Window.width / 15, Window.width / 15), pos=(0.96 * Window.width - 2 * Window.width / 15, 0.98 * Window.height - Window.width / 15), mode='hover', texture=CoreImage('icons/menu.png').texture) self.canvas.add(self.menu_button) self.switch_to_timer = None # Hand objects that represent and show palm positions self.hands = [Hand(1), Hand(2)] for h in self.hands: self.canvas.add(h)
class LevelEasyMediumScreen(Screen): def __init__(self, level, notes, goal_values, gear_values, **kwargs): super(LevelEasyMediumScreen, self).__init__(**kwargs) # set up notes for the level self.notes = notes # set up gear values for the levels self.goal_values = goal_values # set up gear values for the levels self.gear_values = gear_values self.level = level # only turn on tutorial for level 1 if self.level == 1: self.use_tutorial = True self.tutorial_screen = 'A' self.goal_play_status = None self.gear_play_status = None self.size_dim = min(Window.width / 6, Window.height / 6) self.tutorial_full_overlay = CRectangle(cpos=(Window.width / 2, Window.height / 2), csize=(Window.width, Window.height)) self.tutorial_options_overlay = Rectangle( pos=(0, 0), size=(Window.width, self.size_dim + Window.height / 25)) self.tutorial_musicbox_overlay = Rectangle( pos=(Window.width // 2, self.size_dim + Window.height / 25), size=(Window.width / 2, Window.height - (self.size_dim + Window.height / 25))) self.tutorial_gearbox_overlay = Rectangle( pos=(0, self.size_dim + Window.height / 25), size=(Window.width / 2, Window.height - (self.size_dim + Window.height / 25))) self.skip_image = CoreImage('images/skip_tutorial.png') self.tutorial_skip_button = Rectangle( pos=(0.98 * Window.width - (self.skip_image.width * self.size_dim / 300), 0.98 * Window.height - self.skip_image.height * self.size_dim / 300), size=(self.skip_image.width * self.size_dim / 300, self.skip_image.height * self.size_dim / 300), texture=self.skip_image.texture) else: self.use_tutorial = False ############################################ ### GOAL MUSIC ### ############################################ self.goal_audio = Audio(2) self.goal_synth = Synth('./data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.goal_tempo_map = SimpleTempoMap(120) self.goal_sched = AudioScheduler(self.goal_tempo_map) # connect scheduler into audio system self.goal_audio.set_generator(self.goal_sched) self.goal_sched.set_generator(self.goal_synth) # generate goal music self.goal_music = MusicPlayer(notes=self.notes, sched=self.goal_sched, synth=self.goal_synth, channel=1, tempo_map=self.goal_tempo_map) self.goal_music.update_tempo(self.goal_values[0]) self.goal_music.update_instrument(self.goal_values[1]) self.goal_music.update_pitch(self.goal_values[2]) self.goal_music.update_volume(self.goal_values[3]) self.goal_music_seq = self.goal_music.generate() ############################################ ### GEAR MUSIC ### ############################################ self.gear_audio = Audio(2) self.gear_synth = Synth('./data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.gear_tempo_map = SimpleTempoMap(120) self.gear_sched = AudioScheduler(self.gear_tempo_map) # connect scheduler into audio system self.gear_audio.set_generator(self.gear_sched) self.gear_sched.set_generator(self.gear_synth) # generate gear music self.gear_music = MusicPlayer(notes=self.notes, sched=self.gear_sched, synth=self.gear_synth, channel=1, tempo_map=self.gear_tempo_map) self.gear_music_seq = None ############################################ ### BACKGROUND UI COMPONENTS ### ############################################ self.gear_area = GearArea() self.canvas.add(self.gear_area) self.music_box_area = MusicBoxArea() self.canvas.add(self.music_box_area) self.options = LevelOptions( level=level, goal_music_seq=self.goal_music_seq, duration=self.goal_music.duration, edit_goal_play_status=self.edit_goal_play_status) self.canvas.add(self.options) self.label = Label(text=kwargs['name'], font_name='./fonts/PassionOne-Regular', color=(.165, .718, .792, 1)) self.add_widget(self.label) ########################################### ### GEAR LABELS ### ########################################### self.tempo_label = Label(text='Tempo (bpm)', font_name='./fonts/PassionOne-Regular', color=(0.7254901960784313, 0.5529411764705883, 0.8196078431372549, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (0.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.instrument_label = Label( text='Instrument', font_name='./fonts/PassionOne-Regular', color=(0.996078431372549, 0.8431372549019608, 0.4, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (1.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.pitch_label = Label(text='Pitch (semitones)', font_name='./fonts/PassionOne-Regular', color=(1.0, 0.6509803921568628, 0.09019607843137255, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (2.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.volume_label = Label( text='Volume', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (3.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.add_widget(self.volume_label) self.add_widget(self.pitch_label) self.add_widget(self.instrument_label) self.add_widget(self.tempo_label) ########################################### ### GEAR CONSTRUCTION ### ########################################### self.gears = [] self.gear_centers = [] self.gears_group = AnimGroup() self.canvas.add(self.gears_group) ########################################### ### GEARS ### ########################################### self.gear_storage_locations = [] self.gear_music_locations = [] self.gear_labels = [] self.set_up_gears() ########################################### ### PARTICLE EFFECT ### ########################################### self.win_ps = ParticleSystem('particles/star_particle.pex') self.win_ps.emitter_x = Window.width / 2 self.win_ps.emitter_y = Window.height / 2 self.add_widget(self.win_ps) self.you_win_label = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.5843137254901961, 0.796078431372549, 0.37254901960784315, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 10) + 'sp') self.add_widget(self.you_win_label) self.lose_ps = ParticleSystem('particles/lose_particle.pex') self.lose_ps.emitter_x = Window.width / 2 self.lose_ps.emitter_y = Window.height / 2 self.add_widget(self.lose_ps) self.you_lose_label = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 10) + 'sp') self.add_widget(self.you_lose_label) ########################################### ### ERROR MESSAGE ### ########################################### self.error_msg = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 20) + 'sp') self.add_widget(self.error_msg) # ########################################### # ### ADD TUTORIAL OVERLAYS ### # ########################################### if self.use_tutorial: self.canvas.add(Color(rgba=(0, 0, 0, 0.85))) self.canvas.add(self.tutorial_full_overlay) self.tutorial_label = Label( markup=True, text= "[font=./fonts/lato-bold]Welcome to the\n[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/PassionOne-Regular]Play It By Gear Tutorial[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/lato-bold]\n\nThe goal of this game is to make the \n goal song match the song you create by \nplacing the correct gears in a music box \n\n[/font] [font=./fonts/lato-light] (click to see the next \nstep of the tutorial)[/font]", color=(86 / 255, 189 / 255, 205 / 255, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 40) + 'sp', halign='center') self.add_widget(self.tutorial_label) self.canvas.add(self.tutorial_skip_button) self.on_layout((Window.width, Window.height)) def clear_overlays(self): if self.tutorial_full_overlay in self.canvas.children: self.canvas.remove(self.tutorial_full_overlay) if self.tutorial_options_overlay in self.canvas.children: self.canvas.remove(self.tutorial_options_overlay) if self.tutorial_gearbox_overlay in self.canvas.children: self.canvas.remove(self.tutorial_gearbox_overlay) if self.tutorial_musicbox_overlay in self.canvas.children: self.canvas.remove(self.tutorial_musicbox_overlay) while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def activate_overlays(self, overlay_names): self.clear_overlays() self.canvas.add(Color(rgba=(0, 0, 0, 0.85))) if 'full' in overlay_names: self.canvas.add(self.tutorial_full_overlay) if 'options' in overlay_names: self.canvas.add(self.tutorial_options_overlay) if 'gearbox' in overlay_names: self.canvas.add(self.tutorial_gearbox_overlay) if 'musicbox' in overlay_names: self.canvas.add(self.tutorial_musicbox_overlay) def get_scaled_x_y(self, winsize, x, y): width, height = winsize # scaled_x = width/8 * (x+1) scaled_x = width / (len(self.gear_values) // 4 * 2) * (x + 0.5) scaled_y = height / 5.25 * (y + 0.5) + self.gear_area.position[1] return scaled_x, scaled_y def set_up_gears(self): self.gears = [] self.gears_group.remove_all() center_gear_location = (Window.width / 6 * 4.5, Window.height / 4 * 2.5) center_gear_size = min(Window.width / 10, Window.height / 10) self.center_gear = Gear(None, None, center_gear_size, 10, 'center', 0, center_gear_location, center_gear_location, 1, colors['dark_grey']) self.canvas.add(self.center_gear.color) self.gears_group.add(self.center_gear) self.center_gear_center = GearCenter(None, None, center_gear_location, center_gear_location, 'center', center_gear_size / 2, colors['dark_grey']) self.canvas.add(colors['dark_grey']) self.canvas.add(self.center_gear_center) self.play_center_gear = False self.music_gears = [] tempo_location = (center_gear_location[0], center_gear_location[1] + center_gear_size + center_gear_size / 5) instrument_location = (center_gear_location[0], center_gear_location[1] - center_gear_size - center_gear_size / 5) pitch_location = (center_gear_location[0] + center_gear_size + center_gear_size / 5, center_gear_location[1]) volume_location = (center_gear_location[0] - center_gear_size - center_gear_size / 5, center_gear_location[1]) self.music_box_gear_locations = [ tempo_location, instrument_location, pitch_location, volume_location ] counter = 0 label_font_size = min(Window.width // 80, Window.height // 80) for y in range(0, 4): for x in range(len(self.gear_values) // 4): gear_type = gear_type_map[y] size = min(Window.width / 10, Window.height / 10) music_pos = self.music_box_gear_locations[y] scaled_x, scaled_y = self.get_scaled_x_y( (Window.width, Window.height), x, y) gear = Gear(x, y, size, 8, gear_type, self.gear_values[counter], (scaled_x, scaled_y), music_pos, 0, colors['dark_grey']) self.gears.append(gear) self.canvas.add(gear.color) self.gears_group.add(gear) gear_center = GearCenter(x, y, (scaled_x, scaled_y), music_pos, gear_type, size / 2, colors['dark_grey']) self.gear_centers.append(gear_center) self.canvas.add(gear_center) ## white dots for storage purposes gear_loc = GearLocation((scaled_x, scaled_y), size / 2, x, y, gear_type) self.gear_storage_locations.append(gear_loc) self.canvas.add(gear_loc) text = str(self.gear_values[counter]) font_name = './fonts/PassionOne-Regular' if y == 3: # get volume as percent text = str(100 * self.gear_values[counter] // 127) + '%' if y == 1: # get icon for instrument font_name = './fonts/music-instruments' text = instruments[self.gear_values[counter]] label = Label(text=text, font_name=font_name, color=(0, 0, 0, 1), center_x=scaled_x, center_y=scaled_y, font_size=str(label_font_size) + 'sp') self.gear_labels.append(label) self.add_widget(label) counter += 1 for indx, loc in enumerate(self.music_box_gear_locations): gear_type = gear_type_map[indx % 4] gear_loc = GearLocation(loc, center_gear_size / 2, None, None, gear_type) self.gear_music_locations.append(gear_loc) self.canvas.add(gear_loc) def edit_goal_play_status(self, value): if self.use_tutorial: if self.goal_play_status == None and value == 'started': self.goal_play_status = 'started' elif self.goal_play_status == 'started' and value == 'finished': self.goal_play_status = 'finished' def _can_add_gear(self, new_gear): for gear in self.music_gears: if gear.type == new_gear.type: return False return True def _check_music_gears(self): all_types = ['volume', 'pitch', 'tempo', 'instrument'] for gear in self.music_gears: if gear.type in all_types: all_types.remove(gear.type) return len(all_types) == 0 def update_center_gear_on_layout(self, winsize): width, height = winsize center_gear_location = (width / 6 * 4.5, height / 4 * 2.5) center_gear_size = min(Window.width / 10, Window.height / 10) self.center_gear_center.update_storage_pos(center_gear_location) self.center_gear_center.update_music_pos(center_gear_location) self.center_gear.update_storage_pos(center_gear_location) self.center_gear.update_music_pos(center_gear_location) self.center_gear.on_layout(center_gear_location, center_gear_size) self.center_gear_center.on_layout(center_gear_location, center_gear_size / 2) tempo_location = (center_gear_location[0], center_gear_location[1] + center_gear_size + center_gear_size / 5) instrument_location = (center_gear_location[0], center_gear_location[1] - center_gear_size - center_gear_size / 5) pitch_location = (center_gear_location[0] + center_gear_size + center_gear_size / 5, center_gear_location[1]) volume_location = (center_gear_location[0] - center_gear_size - center_gear_size / 5, center_gear_location[1]) self.music_box_gear_locations = [ tempo_location, instrument_location, pitch_location, volume_location ] for indx, loc in enumerate(self.gear_music_locations): loc.on_layout(self.music_box_gear_locations[indx], center_gear_size / 2) def on_enter(self): if not self.use_tutorial and self.level == 1: while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def on_layout(self, winsize): mapped_dic = {'tempo': 0, 'instrument': 1, 'pitch': 2, 'volume': 3} self.update_center_gear_on_layout(winsize) self.size_dim = min(Window.width / 6, Window.height / 6) size = min(Window.width / 10, Window.height / 10) label_font_size = min(Window.width // 80, Window.height // 80) for loc in self.gear_storage_locations: scaled_x, scaled_y = self.get_scaled_x_y(winsize, loc.x, loc.y) loc.on_layout((scaled_x, scaled_y), size / 2) for indx, gear in enumerate(self.gears): scaled_x, scaled_y = self.get_scaled_x_y(winsize, gear.x, gear.y) gear.update_storage_pos((scaled_x, scaled_y)) gear.update_music_pos( self.music_box_gear_locations[mapped_dic[gear.type]]) gear.on_layout((scaled_x, scaled_y), size) self.gear_labels[indx].center_x = scaled_x self.gear_labels[indx].center_y = scaled_y self.gear_labels[indx].font_size = str(label_font_size) + 'sp' for center in self.gear_centers: scaled_x, scaled_y = self.get_scaled_x_y(winsize, center.x, center.y) center.update_storage_pos((scaled_x, scaled_y)) center.update_music_pos( self.music_box_gear_locations[mapped_dic[center.type]]) center.on_layout((scaled_x, scaled_y), size / 2) # update level label self.label.center_x = self.size_dim * 1.5 self.label.center_y = self.size_dim * 3 / 5 self.label.font_size = str(Window.width // 20) + 'sp' # update you win label self.you_win_label.center_x = (Window.width / 2) self.you_win_label.center_y = (Window.height / 2) self.you_win_label.font_size = str(Window.width // 10) + 'sp' self.tempo_label.center_x = (Window.width / 4) self.tempo_label.center_y = (Window.height / 5.25 * (0.5 + 0.5) + self.gear_area.position[1]) self.tempo_label.font_size = str(Window.width // 50) + 'sp' self.instrument_label.center_x = (Window.width / 4) self.instrument_label.center_y = (Window.height / 5.25 * (1.5 + 0.5) + self.gear_area.position[1]) self.instrument_label.font_size = str(Window.width // 50) + 'sp' self.pitch_label.center_x = (Window.width / 4) self.pitch_label.center_y = (Window.height / 5.25 * (2.5 + 0.5) + self.gear_area.position[1]) self.pitch_label.font_size = str(Window.width // 50) + 'sp' self.volume_label.center_x = (Window.width / 4) self.volume_label.center_y = (Window.height / 5.25 * (3.5 + 0.5) + self.gear_area.position[1]) self.volume_label.font_size = str(Window.width // 50) + 'sp' # update child components self.gear_area.on_layout((Window.width, Window.height)) self.music_box_area.on_layout((Window.width, Window.height)) self.options.on_layout((Window.width, Window.height)) # update particle effect and win/lose labels self.win_ps.emitter_x = Window.width / 2 self.win_ps.emitter_y = Window.height / 2 self.you_win_label.center_x = Window.width / 2 self.you_win_label.center_y = Window.height / 2 self.you_win_label.font_size = str(Window.width // 10) + 'sp' self.lose_ps.emitter_x = Window.width / 2 self.lose_ps.emitter_y = Window.height / 2 self.you_lose_label.center_x = Window.width / 2 self.you_lose_label.center_y = Window.height / 2 self.you_lose_label.font_size = str(Window.width // 10) + 'sp' # update error message location self.error_msg.center_x = Window.width / 2 self.error_msg.center_y = Window.height / 2 self.error_msg.font_size = str(Window.width // 20) + 'sp' # update tutorial overlays, label, and skip button if self.use_tutorial: self.update_tutorial_screen(self.tutorial_screen) self.tutorial_full_overlay.cpos = (Window.width / 2, Window.height / 2) self.tutorial_full_overlay.csize = (Window.width, Window.height) self.tutorial_options_overlay.size = (Window.width, self.size_dim + Window.height / 25) self.tutorial_musicbox_overlay.pos = (Window.width // 2, self.size_dim + Window.height / 25) self.tutorial_musicbox_overlay.size = ( Window.width / 2, Window.height - (self.size_dim + Window.height / 25)) self.tutorial_gearbox_overlay.pos = (0, self.size_dim + Window.height / 25) self.tutorial_gearbox_overlay.size = ( Window.width / 2, Window.height - (self.size_dim + Window.height / 25)) self.tutorial_skip_button.pos = ( 0.98 * Window.width - (self.skip_image.width * self.size_dim / 300), 0.98 * Window.height - self.skip_image.height * self.size_dim / 300) self.tutorial_skip_button.size = (self.skip_image.width * self.size_dim / 300, self.skip_image.height * self.size_dim / 300) def reset(self): for gear in self.gears: # reset gear position gear.reset() # stop gear from rotating gear.stop() # stop center gear from rotating self.center_gear.stop() # stop music self.goal_music_seq.stop() if self.gear_music_seq: self.gear_music_seq.stop() # end tutorial if self.use_tutorial: self.tutorial_label.text = '' self.use_tutorial = False self.clear_overlays() while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def skip_tutorial_pressed(self, touch): if self.use_tutorial: if 0.98 * Window.width - ( self.skip_image.width * self.size_dim / 300 ) < touch.pos[ 0] < 0.98 * Window.width and 0.98 * Window.height - self.skip_image.height * self.size_dim / 300 < touch.pos[ 1] < 0.98 * Window.height: return True return False def on_touch_up(self, touch): # if click is on one of the lower menu buttons, perform the appropriate action self.options.on_touch_up(self.switch_to, self.gear_music_seq, self.check_level_complete(), self.win_ps, self.you_win_label, self.lose_ps, self.you_lose_label, self.reset, touch) for index, gear in enumerate(self.gears): # response is true if you click the current gear response = gear.on_touch_up(touch.pos, self.gear_area.max_x, self._can_add_gear) self.gear_centers[index].on_touch_up(touch.pos, self.gear_area.max_x, self._can_add_gear) if response: if gear not in self.music_gears: self.music_gears.append(gear) # update the gear music based on the gear that is selected function = 'self.gear_music.update_' + gear.type + '(' + str( gear.value) + ')' eval(function) else: if gear in self.music_gears: self.music_gears.remove(gear) self.center_gear.on_touch_up(touch.pos, self.gear_area.max_x, self._can_add_gear) if self.use_tutorial: # if skip button pressed, quit out of tutorial mode if self.skip_tutorial_pressed(touch): self.use_tutorial = False self.remove_widget(self.tutorial_label) self.clear_overlays() while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) elif self.tutorial_screen == 'A': # show screen B (musicbox and gearbox covered) self.tutorial_screen = 'B' self.update_tutorial_screen('B') elif self.tutorial_screen == 'B': # show screen C (musicbox and options covered) self.tutorial_screen = 'C' self.update_tutorial_screen('C') elif self.tutorial_screen == 'C': # show screen D (gearbox and options covered) self.tutorial_screen = 'D' self.update_tutorial_screen('D') elif self.tutorial_screen == 'D': # show screen E (options covered) self.tutorial_screen = 'E' self.update_tutorial_screen('E') elif self.tutorial_screen == 'E' and self._check_music_gears(): # if all gears have been placed, show screen F (gearbox covered) self.tutorial_screen = 'F' self.update_tutorial_screen('F') elif self.tutorial_screen == 'F' and self.gear_play_status == 'finished' and self.goal_play_status == 'finished': # if both tunes have been played show screen G (gearbox and musicbox covered) self.tutorial_screen = 'G' self.update_tutorial_screen('G') elif self.tutorial_screen == 'G': # end tutorial self.use_tutorial = False self.remove_widget(self.tutorial_label) self.clear_overlays() while self.tutorial_skip_button in self.canvas.children: self.canvas.remove(self.tutorial_skip_button) def update_tutorial_screen(self, screen): if not self.use_tutorial: return self.remove_widget(self.tutorial_label) if self.tutorial_screen == 'A': self.activate_overlays(['full']) self.tutorial_label.center_x = Window.width / 2 self.tutorial_label.center_y = Window.height / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Welcome to the\n[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/PassionOne-Regular]Play It By Gear Tutorial[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/lato-bold]\n\nThe goal of this game is to make the \n goal song match the song you create by \nplacing the correct gears in a music box \n\n[/font] [font=./fonts/lato-light] (click to see the next \nstep of the tutorial)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 40) + 'sp' if self.tutorial_screen == 'B': self.activate_overlays(['musicbox', 'gearbox']) self.tutorial_label.center_x = 1 / 2 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]\nAt the bottom of the screen is the menu\nbar which contains some helpful buttons\n\nthe [/font] [font=./fonts/options-icons]^[/font] [font=./fonts/lato-bold] button brings\nyou back to the level select menu\n\nthe [/font] [font=./fonts/options-icons]`[/font] [font=./fonts/lato-bold] button resets the level\n\n[/font] [font=./fonts/lato-light](click to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 50) + 'sp' elif self.tutorial_screen == 'C': self.activate_overlays(['musicbox', 'options']) self.tutorial_label.center_x = 3 / 4 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]The left side of the screen\nhas all the gears you can choose\nfrom in order to make the\nmusic box sound correct\n\n[/font] [font=./fonts/lato-light](click to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'D': self.activate_overlays(['gearbox', 'options']) self.tutorial_label.center_x = 1 / 4 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]The right side of the screen\nis the music box. Gears in\nthe music box modify the song.\n\nYou need one gear of each\ntype/color in the music box in\norder for the song to play. \n\n[/font] [font=./fonts/lato-light](click to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'E': self.activate_overlays(['options']) self.tutorial_label.center_x = Window.width / 2 self.tutorial_label.center_y = (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Now drag one gear of each type/color into the music box\n[/font] [font=./fonts/lato-light](when there are 4 gears in the music box, the tutorial will continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'F': self.activate_overlays(['gearbox']) self.tutorial_label.center_x = 1 / 4 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Play the goal sound by pressing\nthe [/font] [font=./fonts/options-icons]_[/font] [font=./fonts/lato-bold] button, then press\nthe [/font] [font=./fonts/options-icons]~[/font] [font=./fonts/lato-bold] in the center gear to\nplay the song you created\nwith the gears\n\n[/font] [font=./fonts/lato-light](after you play both songs,\nclick again to continue)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 60) + 'sp' elif self.tutorial_screen == 'G': self.activate_overlays(['musicbox', 'gearbox']) self.tutorial_label.center_x = 1 / 2 * Window.width self.tutorial_label.center_y = Window.height / 2 + (min( Window.width / 6, Window.height / 6) + Window.height / 25) / 2 self.tutorial_label.text = "[font=./fonts/lato-bold]Did the two songs sound the same?\n\nIf yes, you can press the [/font] [font=./fonts/options-icons]{[/font] [font=./fonts/lato-bold] button\n to see if you're correct\n\n If no, you can switch the gears in the music box\nuntil you think both songs sound the same\n\n[/font] [font=./fonts/lato-light](click to exit the tutorial)[/font]" self.tutorial_label.font_size = font_size = str( Window.width // 55) + 'sp' self.add_widget(self.tutorial_label) self.canvas.add(self.tutorial_skip_button) def on_touch_down(self, touch): other_gears = False for index, gear in enumerate(self.gears): cur_gear = gear.on_touch_down(touch.pos) other_gears = other_gears or cur_gear self.gear_centers[index].on_touch_down(touch.pos) if other_gears: if self.gear_music_seq: for gear in self.music_gears: gear.stop() self.center_gear.stop() self.gear_music_seq.stop() self.gear_music_seq = None else: response = self.center_gear.on_touch_down(touch.pos) if response: if self._check_music_gears(): for gear in self.music_gears: gear.toggle_rotate() is_rotating = self.center_gear.toggle_rotate() if is_rotating: self.gear_music_seq = self.gear_music.generate() self.gear_music_seq.start() if self.use_tutorial and self.gear_play_status == None: self.gear_play_status = 'started' else: if self.gear_music_seq: self.gear_music_seq.stop() self.gear_music_seq = None else: if not self.use_tutorial: self.error_msg.text = 'Place all 4 gears' else: self.error_msg.text = ' ' return self.error_msg.text = ' ' def on_touch_move(self, touch): for index, gear in enumerate(self.gears): gear.on_touch_move(touch.pos) self.gear_centers[index].on_touch_move(touch.pos) def on_update(self): self.goal_audio.on_update() self.gear_audio.on_update() self.options.on_update() for gear in self.gears: gear.on_update(1) self.center_gear.on_update(1) if self.gear_music_seq and not self.gear_music_seq.playing: for gear in self.music_gears: gear.stop() self.center_gear.stop() self.gear_music_seq = None if self.use_tutorial and self.gear_play_status == 'started': self.gear_play_status = 'finished' def check_level_complete(self): return self.goal_music.is_equal(self.gear_music)
class IconBar(InstructionGroup): def __init__(self, modes, label): super(IconBar, self).__init__() self.modes = modes self.label = label self.icons = [] self.icon_group = AnimGroup() self.add(self.icon_group) self.generate_icons() def generate_icons(self): if photo_editor.DARK_MODE: self.icon_group.add(Color(rgb=(1, 1, 1))) else: self.icon_group.add(Color(rgb=(0, 0, 0))) for i in range(len(self.modes)): icon = Rectangle( pos=(0.02 * Window.width, Window.height * (0.1 + 0.8 * i / len(self.modes))), size=(0.8 * Window.height / len(self.modes), 0.8 * Window.height / len(self.modes)), texture=CoreImage('icons/' + self.modes[i] + '.png').texture) self.icons.append(icon) self.icon_group.add(icon) def change_mode(self, mode_index): self.icon_group.remove_all() for i in range(len(self.icons)): if photo_editor.DARK_MODE: self.icon_group.add(Color(rgb=(1, 1, 1))) else: self.icon_group.add(Color(rgb=(0, 0, 0))) if i == mode_index: # change color before adding self.icon_group.add(Color(rgb=(136 / 255, 195 / 255, 0))) self.icon_group.add(self.icons[i]) def on_layout(self): for i in range(len(self.modes)): self.icons[i].pos = (0.02 * Window.width, Window.height * (0.1 + 0.8 * i / len(self.modes))) self.icons[i].size = (0.8 * Window.height / len(self.modes), 0.8 * Window.height / len(self.modes)) # returns mode of icon at screen position (x, y) def identify_icon(self, x, y): # if x-position is reasonable if 0.02 * Window.width < x < 0.02 * Window.width + 0.5 * Window.height / len( self.modes): # check for icon y-position match for i in range(len(self.modes)): if self.icons[i].pos[ 1] < y < self.icons[i].pos[1] + self.icons[i].size[1]: return self.modes[i] return None def show_label(self, icon): i = self.modes.index(icon) self.label.text = icon self.label.center_x = self.icons[i].pos[0] + self.icons[i].size[ 0] + self.label.texture_size[0] * 0.6 self.label.center_y = self.icons[i].pos[1] + self.icons[i].size[1] / 2
def __init__(self, level, notes, goal_values, gear_values, **kwargs): super(LevelEasyMediumScreen, self).__init__(**kwargs) # set up notes for the level self.notes = notes # set up gear values for the levels self.goal_values = goal_values # set up gear values for the levels self.gear_values = gear_values self.level = level # only turn on tutorial for level 1 if self.level == 1: self.use_tutorial = True self.tutorial_screen = 'A' self.goal_play_status = None self.gear_play_status = None self.size_dim = min(Window.width / 6, Window.height / 6) self.tutorial_full_overlay = CRectangle(cpos=(Window.width / 2, Window.height / 2), csize=(Window.width, Window.height)) self.tutorial_options_overlay = Rectangle( pos=(0, 0), size=(Window.width, self.size_dim + Window.height / 25)) self.tutorial_musicbox_overlay = Rectangle( pos=(Window.width // 2, self.size_dim + Window.height / 25), size=(Window.width / 2, Window.height - (self.size_dim + Window.height / 25))) self.tutorial_gearbox_overlay = Rectangle( pos=(0, self.size_dim + Window.height / 25), size=(Window.width / 2, Window.height - (self.size_dim + Window.height / 25))) self.skip_image = CoreImage('images/skip_tutorial.png') self.tutorial_skip_button = Rectangle( pos=(0.98 * Window.width - (self.skip_image.width * self.size_dim / 300), 0.98 * Window.height - self.skip_image.height * self.size_dim / 300), size=(self.skip_image.width * self.size_dim / 300, self.skip_image.height * self.size_dim / 300), texture=self.skip_image.texture) else: self.use_tutorial = False ############################################ ### GOAL MUSIC ### ############################################ self.goal_audio = Audio(2) self.goal_synth = Synth('./data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.goal_tempo_map = SimpleTempoMap(120) self.goal_sched = AudioScheduler(self.goal_tempo_map) # connect scheduler into audio system self.goal_audio.set_generator(self.goal_sched) self.goal_sched.set_generator(self.goal_synth) # generate goal music self.goal_music = MusicPlayer(notes=self.notes, sched=self.goal_sched, synth=self.goal_synth, channel=1, tempo_map=self.goal_tempo_map) self.goal_music.update_tempo(self.goal_values[0]) self.goal_music.update_instrument(self.goal_values[1]) self.goal_music.update_pitch(self.goal_values[2]) self.goal_music.update_volume(self.goal_values[3]) self.goal_music_seq = self.goal_music.generate() ############################################ ### GEAR MUSIC ### ############################################ self.gear_audio = Audio(2) self.gear_synth = Synth('./data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.gear_tempo_map = SimpleTempoMap(120) self.gear_sched = AudioScheduler(self.gear_tempo_map) # connect scheduler into audio system self.gear_audio.set_generator(self.gear_sched) self.gear_sched.set_generator(self.gear_synth) # generate gear music self.gear_music = MusicPlayer(notes=self.notes, sched=self.gear_sched, synth=self.gear_synth, channel=1, tempo_map=self.gear_tempo_map) self.gear_music_seq = None ############################################ ### BACKGROUND UI COMPONENTS ### ############################################ self.gear_area = GearArea() self.canvas.add(self.gear_area) self.music_box_area = MusicBoxArea() self.canvas.add(self.music_box_area) self.options = LevelOptions( level=level, goal_music_seq=self.goal_music_seq, duration=self.goal_music.duration, edit_goal_play_status=self.edit_goal_play_status) self.canvas.add(self.options) self.label = Label(text=kwargs['name'], font_name='./fonts/PassionOne-Regular', color=(.165, .718, .792, 1)) self.add_widget(self.label) ########################################### ### GEAR LABELS ### ########################################### self.tempo_label = Label(text='Tempo (bpm)', font_name='./fonts/PassionOne-Regular', color=(0.7254901960784313, 0.5529411764705883, 0.8196078431372549, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (0.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.instrument_label = Label( text='Instrument', font_name='./fonts/PassionOne-Regular', color=(0.996078431372549, 0.8431372549019608, 0.4, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (1.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.pitch_label = Label(text='Pitch (semitones)', font_name='./fonts/PassionOne-Regular', color=(1.0, 0.6509803921568628, 0.09019607843137255, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (2.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.volume_label = Label( text='Volume', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=(Window.width / 4), center_y=(Window.height / 5.25 * (3.5 + 0.5) + self.gear_area.position[1]), font_size=str(Window.width // 50) + 'sp') self.add_widget(self.volume_label) self.add_widget(self.pitch_label) self.add_widget(self.instrument_label) self.add_widget(self.tempo_label) ########################################### ### GEAR CONSTRUCTION ### ########################################### self.gears = [] self.gear_centers = [] self.gears_group = AnimGroup() self.canvas.add(self.gears_group) ########################################### ### GEARS ### ########################################### self.gear_storage_locations = [] self.gear_music_locations = [] self.gear_labels = [] self.set_up_gears() ########################################### ### PARTICLE EFFECT ### ########################################### self.win_ps = ParticleSystem('particles/star_particle.pex') self.win_ps.emitter_x = Window.width / 2 self.win_ps.emitter_y = Window.height / 2 self.add_widget(self.win_ps) self.you_win_label = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.5843137254901961, 0.796078431372549, 0.37254901960784315, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 10) + 'sp') self.add_widget(self.you_win_label) self.lose_ps = ParticleSystem('particles/lose_particle.pex') self.lose_ps.emitter_x = Window.width / 2 self.lose_ps.emitter_y = Window.height / 2 self.add_widget(self.lose_ps) self.you_lose_label = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 10) + 'sp') self.add_widget(self.you_lose_label) ########################################### ### ERROR MESSAGE ### ########################################### self.error_msg = Label(text=' ', font_name='./fonts/PassionOne-Regular', color=(0.9254901960784314, 0.32941176470588235, 0.3176470588235294, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 20) + 'sp') self.add_widget(self.error_msg) # ########################################### # ### ADD TUTORIAL OVERLAYS ### # ########################################### if self.use_tutorial: self.canvas.add(Color(rgba=(0, 0, 0, 0.85))) self.canvas.add(self.tutorial_full_overlay) self.tutorial_label = Label( markup=True, text= "[font=./fonts/lato-bold]Welcome to the\n[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/PassionOne-Regular]Play It By Gear Tutorial[/font] [font=./fonts/options-icons]|[/font] [font=./fonts/lato-bold]\n\nThe goal of this game is to make the \n goal song match the song you create by \nplacing the correct gears in a music box \n\n[/font] [font=./fonts/lato-light] (click to see the next \nstep of the tutorial)[/font]", color=(86 / 255, 189 / 255, 205 / 255, 1), center_x=Window.width / 2, center_y=Window.height / 2, font_size=str(Window.width // 40) + 'sp', halign='center') self.add_widget(self.tutorial_label) self.canvas.add(self.tutorial_skip_button) self.on_layout((Window.width, Window.height))
class MainWidget(BaseWidget): def __init__(self): super(MainWidget, self).__init__() self.audio = Audio(2, input_func=self.receive_audio, num_input_channels=1) self.mixer = Mixer() self.audio.set_generator(self.mixer) self.pitch = PitchDetector() self.recorder = VoiceAudioWriter('data') self.info = topleft_label() self.add_widget(self.info) self.anim_group = AnimGroup() self.mic_meter = MeterDisplay((50, 25), 150, (-96, 0), (.1, .9, .3)) self.mic_graph = GraphDisplay((110, 25), 150, 300, (-96, 0), (.1, .9, .3)) self.pitch_meter = MeterDisplay((50, 200), 150, (30, 90), (.9, .1, .3)) self.pitch_graph = GraphDisplay((110, 200), 150, 300, (30, 90), (.9, .1, .3)) self.canvas.add(self.mic_meter) self.canvas.add(self.mic_graph) self.canvas.add(self.pitch_meter) self.canvas.add(self.pitch_graph) # Record button self.record_button = InteractiveImage() self.record_button.source = "../data/mic.png" self.record_button.x = 400 self.record_button.y = 400 self.record_button.size = (100, 100) self.record_button.set_callback(self.init_recording) self.add_widget(self.record_button) # Play button self.play_button = InteractiveImage() self.play_button.source = "../data/play.png" self.play_button.x = 600 self.play_button.y = 400 self.play_button.size = (100, 100) self.play_button.set_callback(self.play_recording) self.add_widget(self.play_button) self.canvas.add(self.anim_group) self.onset_disp = None self.onset_x = 0 self.cur_pitch = 0 # Note Scheduler self.synth = Synth('../data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.tempo_map = SimpleTempoMap(120) self.sched = AudioScheduler(self.tempo_map) # connect scheduler into audio system self.mixer.add(self.sched) self.sched.set_generator(self.synth) # Note Sequencers self.seq = [] # live Generator self.live_wave = None def on_update(self): self.audio.on_update() self.anim_group.on_update() self.info.text = 'fps:%d\n' % kivyClock.get_fps() self.info.text += 'load:%.2f\n' % self.audio.get_cpu_load() self.info.text += "pitch: %.1f\n" % self.cur_pitch self.info.text += 'max delta: %.3f\n' % self.onset_detector.get_max_delta( ) self.info.text += 'onset delta thresh (up/down): %.3f\n' % self.onset_detector.onset_thresh if self.recorder.active: self.info.text += 'RECORDING' def receive_audio(self, frames, num_channels): assert (num_channels == 1) # Microphone volume level, take RMS, convert to dB. # display on meter and graph rms = np.sqrt(np.mean(frames**2)) rms = np.clip(rms, 1e-10, 1) # don't want log(0) db = 20 * np.log10(rms) # convert from amplitude to decibels self.mic_meter.set(db) self.mic_graph.add_point(db) # pitch detection: get pitch and display on meter and graph self.cur_pitch = self.pitch.write(frames) self.pitch_meter.set(self.cur_pitch) self.pitch_graph.add_point(self.cur_pitch) # record audio self.recorder.add_audio(frames, num_channels) # onset detection and classification self.onset_detector.write(frames) def init_recording(self): data = self.recorder.toggle() if data: print(data) wave_gen, filename, duration_midi = data for i in range(len(duration_midi)): if duration_midi[i][0] < 0.12: duration_midi[i] = (duration_midi[i][0], 0) duration_midi = harmony.harmonize(duration_midi) self.live_wave = wave_gen print([[i[1] for i in j] for j in duration_midi]) tempo = 120 multiplier = 1 / 60 * tempo * 480 converted_midi_duration = [[(i * multiplier, j) for i, j in k] for k in duration_midi] for i in converted_midi_duration: self.seq.append( NoteSequencer(self.sched, self.synth, 1, (0, 0), i, True)) def play_recording(self): print("hello") for i in self.seq: i.start() if self.live_wave: self.mixer.add(self.live_wave) def on_key_down(self, keycode, modifiers): t = lookup(keycode[1], ['up', 'down'], [.001, -.001]) if t is not None: self.onset_detector.onset_thresh += t if keycode[1] == "w": self.init_recording() if keycode[1] == "s" and self.seq: self.play_recording()
def __init__(self, norm, sandbox, mixer, client, client_id, block_handler): self.norm = norm self.module_name = 'PhysicsBubble' self.sandbox = sandbox self.mixer = mixer self.client = client self.cid = client_id self.block_handler = block_handler # many variables here are dicts because a user's module handler needs to keep track of # not just its own variables, but other users' variables as well! so we use dictionaries # with client ids as the keys. self.hold_line = {} self.hold_point = {} self.hold_shape = {} self.text = {} self.text_color = Color(0, 0, 0) # this mysterious variable is needed for a race condition in which touch_up events are # sometimes registered before touch_down events when the user clicks too fast, causing # touch_down and touch_up to occur at roughly the same time. if touch_up happens first, # it returns early. when touch_down is called later, it **skips** (hence the name) # adding shapes to the sandbox, after which this is toggled to False again. basically, # nothing is drawn to the screen, which indirectly prompts the user to try again. self.skip = {} self.color_dict = { 'red': (201 / 255, 108 / 255, 130 / 255), 'orange': (214 / 255, 152 / 255, 142 / 255), 'yellow': (238 / 255, 234 / 255, 202 / 255), 'green': (170 / 255, 220 / 255, 206 / 255), 'teal': (159 / 255, 187 / 255, 208 / 255), 'blue': (44 / 255, 85 / 255, 123 / 255), 'turquoise': (50 / 255, 147 / 255, 140 / 255), 'indigo': (46 / 255, 40 / 255, 90 / 255), 'violet': (147 / 255, 127 / 255, 159 / 255), 'pink': (199 / 255, 150 / 255, 170 / 255), 'peach': (238 / 255, 142 / 255, 154 / 255), 'magenta': (172 / 255, 69 / 255, 133 / 255), 'grey': (140 / 255, 143 / 255, 148 / 255), 'white': (239 / 255, 226 / 255, 222 / 255) } self.pitch_list = [60, 62, 64, 65, 67, 69, 71, 72] self.default_color = self.color_dict['red'] self.default_pitch = self.pitch_list[0] self.default_timbre = 'sine' self.default_bounces = 5 self.color = {} self.pitch = {} self.timbre = {} self.bounces = {} self.gravity = {} # flag used to only display controls when this module is synced # see on_update() and sync_state() self.display = False self.bubbles = AnimGroup() self.sandbox.add(self.bubbles) # GUI elements self.gui = BubbleGUI(self.norm, pos=self.norm.nt((50, 100)), pitch_callback=self.update_pitch, bounce_callback=self.update_bounces, gravity_callback=self.update_gravity, timbre_callback=self.update_timbre)
def __init__(self): super(MainWidget, self).__init__() self.audio = Audio(2, input_func=self.receive_audio, num_input_channels=1) self.mixer = Mixer() self.audio.set_generator(self.mixer) self.pitch = PitchDetector() self.recorder = VoiceAudioWriter('data') self.info = topleft_label() self.add_widget(self.info) self.anim_group = AnimGroup() self.mic_meter = MeterDisplay((50, 25), 150, (-96, 0), (.1, .9, .3)) self.mic_graph = GraphDisplay((110, 25), 150, 300, (-96, 0), (.1, .9, .3)) self.pitch_meter = MeterDisplay((50, 200), 150, (30, 90), (.9, .1, .3)) self.pitch_graph = GraphDisplay((110, 200), 150, 300, (30, 90), (.9, .1, .3)) self.canvas.add(self.mic_meter) self.canvas.add(self.mic_graph) self.canvas.add(self.pitch_meter) self.canvas.add(self.pitch_graph) # Record button self.record_button = InteractiveImage() self.record_button.source = "../data/mic.png" self.record_button.x = 400 self.record_button.y = 400 self.record_button.size = (100, 100) self.record_button.set_callback(self.init_recording) self.add_widget(self.record_button) # Play button self.play_button = InteractiveImage() self.play_button.source = "../data/play.png" self.play_button.x = 600 self.play_button.y = 400 self.play_button.size = (100, 100) self.play_button.set_callback(self.play_recording) self.add_widget(self.play_button) self.canvas.add(self.anim_group) self.onset_disp = None self.onset_x = 0 self.cur_pitch = 0 # Note Scheduler self.synth = Synth('../data/FluidR3_GM.sf2') # create TempoMap, AudioScheduler self.tempo_map = SimpleTempoMap(120) self.sched = AudioScheduler(self.tempo_map) # connect scheduler into audio system self.mixer.add(self.sched) self.sched.set_generator(self.synth) # Note Sequencers self.seq = [] # live Generator self.live_wave = None
class PianoPuzzle(Puzzle): def __init__(self, prev_room, level=0, on_finished_puzzle=None): super().__init__() self.door_sources = { (4, 9): "./data/Door_up.png", # UP (4, -1): "./data/door_down.png", # DOWN (-1, 4): "./data/door_left.png", # LEFT (9, 4): "./data/Door_right.png", # RIGHT } self.prev_room = prev_room self.on_finished_puzzle = on_finished_puzzle self.animations = AnimGroup() self.level = level self.notes, self.actual_key = levels[level] duration = choice(durations) pitch_shift = choice(range(-3, 4)) self.user_notes = [ Note(duration, n.get_pitch() + pitch_shift) for n in self.notes ] render_user_notes = self.level < 2 self.actual_sound = PuzzleSound(self.notes) self.user_sound = PuzzleSound(self.user_notes) self.music_bar = MusicBar(self.notes, self.user_notes, render_user_notes, self.actual_sound) self.animations.add(self.music_bar) self.add(self.animations) self.user_key = "C" self.place_objects() self.key_label = CLabelRect( (Window.width // 30, 23 * Window.height // 32), f"Key: {self.user_key}", 34) self.add(Color(rgba=(1, 1, 1, 1))) self.add(self.key_label) self.create_instructions((Window.width, Window.height)) self.add(self.instructions_text_color) self.add(self.instructions_text) self.objects = {} def play(self, actual=False): if actual: print("Should be setting the cb_ons") self.actual_sound.set_cb_ons([self.music_bar.play]) self.actual_sound.toggle() else: self.user_sound.set_cb_ons([self.music_bar.play]) self.user_sound.toggle() def create_instructions(self, win_size): self.instructions_text_color = Color(rgba=(1, 1, 1, 1)) self.instructions_text = CLabelRect( (win_size[0] // 8, win_size[1] * 4 / 7), "Press + to hear your note sequence.\nPress - to hear the sequence\nyou are trying to match.", 20, ) def on_pitch_change(self, pitch_index): offset = 1 if self.character.direction == Button.RIGHT.value else -1 for note in self.user_notes: pitch = note.get_pitch() note.set_note(pitch + offset) self.user_sound.set_notes(self.user_notes) def on_duration_change(self, dur_index): for note in self.user_notes: note.set_dur(durations[dur_index]) self.user_sound.set_notes(self.user_notes) def on_key_change(self, key_index): self.user_key = key_names[key_index] self.update_key() self.user_sound.set_notes(self.user_notes) def update_key(self): key_sig = keys[self.user_key] for note in self.user_notes: base_letter = note.get_letter()[0] letter_before = names[names.index(base_letter) - 1] if base_letter not in key_sig["#"]: note.remove_sharp() if base_letter not in key_sig["b"]: note.remove_flat() if base_letter in key_sig["#"] and not (note.get_letter()[:-1] == base_letter + "#"): note.add_sharp() if base_letter in key_sig["b"] and not (note.get_letter()[:-1] == letter_before + "#"): note.add_flat() """ Mandatory Puzzle methods """ def is_game_over(self): same_key = self.user_key == self.actual_key same_dur = (self.music_bar.user_notes[0].get_dur() == self.music_bar.actual_notes[0].get_dur()) same_pitch = (self.music_bar.user_notes[0].get_pitch() == self.music_bar.actual_notes[0].get_pitch()) return same_key and same_dur and same_pitch def create_objects(self): size = (self.grid.tile_side_len, self.grid.tile_side_len) # PITCH self.objects[(4, 7)] = MovingBlock( size, self.grid.grid_to_pixel((4, 7)), ((1, 7), (8, 7)), "./data/pitch_slider.png", self.on_pitch_change, ) # RHYTHM duration = self.user_notes[0].get_dur() self.objects[(durations.index(duration) + 3, 2)] = MovingBlock( size, self.grid.grid_to_pixel((durations.index(duration) + 3, 2)), ((3, 2), (7, 2)), "./data/rhythm_slider.png", self.on_duration_change, ) # KEY self.objects[(key_names.index(self.user_key) + 1, 5)] = MovingBlock( size, self.grid.grid_to_pixel((key_names.index(self.user_key) + 1, 5)), ((1, 5), (7, 5)), "./data/key_slider.png", self.on_key_change, ) self.objects[(9, 4)] = DoorTile( size, self.grid.grid_to_pixel((9, 4)), self.prev_room, source=self.door_sources[(9, 4)], ) def place_objects(self): # rhythm_color = Color(rgba=(0, 0.5, 0.5, 1)) for i in range(len(durations)): # rhythm self.grid.get_tile( (i + 3, 2)).set_color(color=Tile.base_color, source="./data/trackv2.png") # pitch_color = Color(rgba=(0.2, 0.5, 1, 1)) for i in range(7): # rhythm self.grid.get_tile( (i + 1, 7)).set_color(color=Tile.base_color, source="./data/trackv2.png") # key_color = Color(rgba=(0.2, 0.5, 0, 1)) for i in range(len(key_names)): self.grid.get_tile( (i + 1, 5)).set_color(color=Tile.base_color, source="./data/trackv2.png") self.add(PushMatrix()) self.add(Translate(*self.grid.pos)) for pos, obj in self.objects.items(): self.add(obj) self.add(PopMatrix()) def move_block(self, new_location, x, y): obj_loc = (new_location[0] + x, new_location[1] + y) if self.is_valid_pos(obj_loc) and self.valid_block_move( obj_loc, self.objects[new_location].move_range): self.remove(self.objects[new_location]) obj = MovingBlock( self.objects[new_location].size, self.grid.grid_to_pixel(obj_loc), self.objects[new_location].move_range, self.objects[new_location].icon_source, self.objects[new_location].callback, ) del self.objects[new_location] self.add(PushMatrix()) self.add(Translate(*self.grid.pos)) self.add(obj) self.add(PopMatrix()) self.objects[obj_loc] = obj self.objects[obj_loc].on_block_placement(obj_loc) return True else: return False def valid_block_move(self, pos, move_range): return (move_range[0][0] <= pos[0] < move_range[1][0] and move_range[0][1] <= pos[1] <= move_range[1][1]) def on_player_input(self, button): player_pos = self.character.grid_pos if button in [Button.UP, Button.DOWN, Button.LEFT, Button.RIGHT]: move_possible = True x, y = button.value new_pos = (player_pos[0] + x, player_pos[1] + y) if new_pos in self.objects: if isinstance(self.objects[new_pos], DoorTile): if not isinstance(self.objects[new_pos].other_room, Puzzle): # instantiate class when we enter the door self.objects[new_pos].other_room = self.objects[ new_pos].other_room(self, self.level + 1, self.on_finished_puzzle) next_room_pos = (8 - new_pos[0] + x, 8 - new_pos[1] + y) self.objects[ new_pos].other_room.character.change_direction( button.value) self.objects[new_pos].other_room.character.move_player( next_room_pos) return self.objects[new_pos].other_room self.character.change_direction(button.value) if new_pos in self.objects: if self.objects[new_pos].moveable: move_possible = self.move_block(new_pos, x, y) if move_possible: self.character.move_player(new_pos) player_pos = self.character.grid_pos if button == Button.MINUS: self.play(actual=True) elif button == Button.PLUS: self.play(actual=False) def on_update(self): self.animations.on_update() self.actual_sound.on_update() self.user_sound.on_update() self.key_label.set_text(f"Key: {self.user_key}") if not self.game_over and self.is_game_over(): for pos, obj in self.objects.items(): if isinstance(obj, MovingBlock): obj.moveable = False if self.level == max(levels.keys()): self.on_finished_puzzle() self.on_game_over() else: if (-1, 4) not in self.objects: size = (self.grid.tile_side_len, self.grid.tile_side_len) self.objects[(-1, 4)] = DoorTile( size, self.grid.grid_to_pixel((-1, 4)), PianoPuzzle, source=self.door_sources[(-1, 4)], ) self.add(PushMatrix()) self.add(Translate(*self.grid.pos)) self.add(self.objects[(-1, 4)]) self.add(PopMatrix()) def on_layout(self, win_size): self.remove(self.character) self.remove(self.grid) self.remove(self.instructions_text_color) self.remove(self.instructions_text) self.grid.on_layout((win_size[0], 0.75 * win_size[1])) for pos, obj in self.objects.items(): self.remove(obj) self.add(self.grid) if not self.objects: self.create_objects() self.place_objects() self.character.on_layout(win_size) self.add(self.character) self.music_bar.on_layout(win_size) self.remove(self.key_label) self.key_label = CLabelRect( (win_size[0] // 30, 23 * win_size[1] // 32), f"Key: {self.user_key}", 34) self.add(Color(rgba=(1, 1, 1, 1))) self.add(self.key_label) self.create_instructions(win_size) self.add(self.instructions_text_color) self.add(self.instructions_text) self.create_game_over_text(win_size) if self.game_over: self.remove(self.game_over_window_color) self.remove(self.game_over_window) self.remove(self.game_over_text_color) self.remove(self.game_over_text) self.on_game_over()