Exemple #1
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)
Exemple #2
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)))
Exemple #3
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()
Exemple #4
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)
Exemple #5
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)
Exemple #6
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()
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()
Exemple #8
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)