Пример #1
0
    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))
Пример #2
0
    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 = {}
Пример #3
0
 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
Пример #4
0
    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()
Пример #5
0
    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())
Пример #6
0
    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()
Пример #7
0
    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 = {}
Пример #8
0
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)
Пример #9
0
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)))
Пример #10
0
    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 = {}
Пример #11
0
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)
Пример #12
0
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)
Пример #13
0
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)
Пример #14
0
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()
Пример #15
0
    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]
Пример #16
0
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'
Пример #17
0
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)
Пример #18
0
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()
Пример #19
0
    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)
Пример #20
0
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)
Пример #21
0
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
Пример #22
0
    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))
Пример #23
0
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()
Пример #24
0
    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)
Пример #25
0
    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
Пример #26
0
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()