Ejemplo n.º 1
0
class InstrumentManager(GameObject):

	def __init__(self, sched):
		super(InstrumentManager, self).__init__()
		self.synth = Synth("data/FluidR3_GM.sf2")
		self.add_generator(self.synth)

		self.metro = Metronome(sched, self.synth)
		self.inst_count = 1

		self.instruments = []


	# Adds instrument to manager. Each instrument is added to 
	# a new channel.
	def add(self, ins):
		self.inst_count += 1
		self.instruments.append(ins)
		ins.manager = self
		ins.channel = self.inst_count
		self.synth.program(self.inst_count, *ins.patch)

	def program(self, ins):
		self.synth.program(ins.channel, *ins.patch)

	def noteon(self, *args):
		self.synth.noteon(*args)

	def noteoff(self, *args):
		self.synth.noteoff(*args)
Ejemplo n.º 2
0
class MainWidget1(BaseWidget):
    def __init__(self):
        super(MainWidget1, self).__init__()

        self.audio = Audio(2)
        self.synth = Synth('../data/FluidR3_GM.sf2')
        self.audio.set_generator(self.synth)

        self.label = topleft_label()
        self.add_widget(self.label)

        cc_range = range(0, 128, 8)
        self.modifier = Modifier()
        self.modifier.add('a', "program", range(128),
                          lambda x: self.synth.program_change(0, x))
        self.modifier.add('s', "vibrato", cc_range,
                          lambda x: self.synth.cc(0, 1, x))
        self.modifier.add('d', "volume", cc_range,
                          lambda x: self.synth.cc(0, 7, x))
        self.modifier.add('f', "sustain", (0, 127),
                          lambda x: self.synth.cc(0, 64, x))
        self.modifier.add('z', "bend", range(-8192, 8192, 256),
                          lambda x: self.synth.pitch_bend(0, int(x)))

    def on_key_down(self, keycode, modifiers):
        self.modifier.on_key_down(keycode[1])

        pitch = lookup(keycode[1], kKeys, kPitches)
        if pitch:
            self.synth.noteon(0, pitch, 100)

    def on_key_up(self, keycode):
        self.modifier.on_key_up(keycode[1])

        pitch = lookup(keycode[1], kKeys, kPitches)
        if pitch:
            self.synth.noteoff(0, pitch)

    def on_update(self):
        self.audio.on_update()
        self.modifier.on_update()
        self.label.text = self.modifier.get_txt() + '\n'
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
class Ticker(object):
    ''' 
    Maps beat patterns to ticks (time stamps) and plays the call of the call and response (if enabled)
    This class assumes quarter notes as beats.
    becase every loop in this game is 4 measures, we will assume a loop to last 16 beats.  (4/4)
    '''
    bpm = 400
    numRepeats = 3
    measuresPerCall = 1
    channel = 0
    vel = 80
    playQueues = 1
    noteDuration = 1
    nowBarHeight = Window.height // 2
    endBarTicks = kTicksPerQuarter * .5
    barLenTicks = kTicksPerQuarter * 4 + endBarTicks
    gemRadius = 100
    padding = gemRadius + 50

    def __init__(self, songPattern, key, clock):
        self.slack_timout = beats_to_time(.5, self.bpm)
        self.gems, self.bars = self._initialize_bars(songPattern, key)
        self.synth = Synth('../bank/FluidR3_GM.sf2')
        totalBeats = self.measuresPerCall * self.numRepeats * len(
            self.bars) * 4
        totalTime = beats_to_time(totalBeats, self.bpm)
        totalOffsetTicks = self.endBarTicks * totalBeats / 4
        totalTicks = totalBeats / 4 * self.barLenTicks
        data = [(0, 0),
                ((totalTime + ticks_to_time(totalOffsetTicks, self.bpm)) * 2,
                 totalTicks * 2)]
        self.tempo = TempoMap(data=data)
        self.num_bars = len(data)
        self.scheduler = Scheduler(clock, self.tempo)
        self.increment_bar = None
        self.catch_passes = None
        self.on_commands = []  # commands for a bar of call and response
        self.off_commands = []
        self.active_gems = []
        self.gem_commands = []
        self.bar_tick = 0
        self.bar_index = 0
        print(songPattern)

    def reset(self):
        #TODO
        for i in range(len(self.num_bars)):
            self.clear_bar(i)
        self.bar_index = 0

    def create_bar(self, barIndex):
        self.bar_tick = quantize_tick_up(self.scheduler.get_tick())
        self.active_gems = self.gems[barIndex]
        # print(list(map(lambda x: x.beat, self.active_gems)))
        # [gem.activate() for gem in self.active_gems]
        self._initializeBarAudio(barIndex)
        self._initializeBarGems(barIndex)
        # self.increment_bar

    def clear_bar(self, barIndex):
        self._clearBarAudio(barIndex)
        self._clearBarGems()

    def getRelativeTick(self):
        tick = quantize_tick_up(self.scheduler.get_tick())
        tick = (tick - self.bar_tick) % self.barLenTicks
        return tick

    def getTick(self):
        return quantize_tick_up(self.scheduler.get_tick())

    def getTargetGem(self):
        tick = self.getRelativeTick()
        beatApprox = (tick / self.barLenTicks) * 4
        #find gem by tick
        targetGem = None
        minDist = 9999999999
        for gem in self.active_gems:
            if gem.hit or gem.miss:
                # print("skipping hit gem (target) ", tick)
                continue
            gemDist = min(abs(gem.beat - beatApprox),
                          abs(beatApprox - gem.beat))
            if gemDist < minDist:
                minDist = gemDist
                targetGem = gem
        # print("minimum dist: ", minDist)
        # print("targetGem: ", gem.beat)
        # print("beat approx: ", beatApprox)
        return targetGem

    def on_update(self):
        self.scheduler.on_update()
        tick = self.getTick()
        ticksEllapsed = (tick - self.bar_tick) % (self.barLenTicks * 4)
        if ticksEllapsed <= self.barLenTicks:
            return "call"
        elif ticksEllapsed <= self.numRepeats * self.barLenTicks:
            return "response"
        else:
            # print("tick %f, ticksEll %f, barTick %f, barLen %f " % (tick, ticksEllapsed, self.bar_tick, self.barLenTicks))
            print('nextStatus')
            return "next"

    def bars_remaining(self):
        tick = self.getTick()
        ticksEllapsed = (tick - self.bar_tick) % (self.barLenTicks * 4)
        barNum = round((self.numRepeats * self.barLenTicks - ticksEllapsed) /
                       self.barLenTicks)
        barsRemaining = np.clip(self.numRepeats - barNum - 1, 0,
                                self.numRepeats - 1)
        return barsRemaining

    def _initialize_bars(self, pattern, key):
        gem_bars = []
        chord_bars = []

        for bar in pattern:
            numGems = len(bar)
            assert numGems > 1
            w = (Window.width) // 5
            x = self.padding
            y = self.nowBarHeight
            chords_and_ticks = []
            gems = []
            for b in bar:
                assert b[1] <= self.measuresPerCall * 4 and b[1] > 0
                chord = key.generateChord(b[0])
                chords_and_ticks.append((chord, b[1]))
                x = w * b[1]
                gem = Gem(chord, (x, y), self.gemRadius, self.slack_timout,
                          b[1])
                gems.append(gem)

            chord_bars.append(chords_and_ticks)
            gem_bars.append(gems)
        return gem_bars, chord_bars

    def _initializeBarAudio(self, barIndex):
        bar_tick = int(self.bar_tick)
        bar = self.bars[barIndex]
        # print(self.bar_tick)
        assert self.numRepeats >= self.playQueues
        for i in range(self.playQueues):
            for chord, beat in bar:
                tick = bar_tick + beat * kTicksPerQuarter
                # print('audio ', tick)
                self.on_commands.append(
                    self.scheduler.post_at_tick(self._playChord, tick, chord))
                self.off_commands.append(
                    self.scheduler.post_at_tick(
                        self._endChord,
                        tick + kTicksPerQuarter * self.noteDuration, chord))
            bar_tick += self.barLenTicks

    def _clearBarAudio(self, barIndex):
        for c in self.on_commands:
            self.scheduler.remove(c)
        for c in self.off_commands:
            self.scheduler.remove(c)
            c.execute()

    def _initializeBarGems(self, barIndex):
        slackWinOffset = quantize_tick_up(
            float(self.slack_timout) / 2 * kTicksPerQuarter)
        bar_tick = int(self.bar_tick)
        bar = self.gems[barIndex]
        for i in range(self.numRepeats):
            for gem in bar:
                tick = bar_tick + gem.beat * kTicksPerQuarter
                if i > 0:
                    self.gem_commands.append(
                        self.scheduler.post_at_tick(self._startGemTimer,
                                                    tick - slackWinOffset,
                                                    gem))
                    ticks_to_time(tick - slackWinOffset, self.bpm)
            bar_tick += self.barLenTicks
            self.gem_commands.append(
                self.scheduler.post_at_tick(self._onCompleteMeasure, bar_tick))

    def _refreshBarGems(self):
        for gem in self.active_gems:
            gem.on_reset()
            # gem.activate()

    def _clearBarGems(self):
        for gem in self.active_gems:
            # pass
            gem.exit()

    def _onCompleteMeasure(self, tick, temp=None):
        allHit = True
        for gem in self.active_gems:
            allHit = allHit and gem.hit
        if allHit and not self.on_update() == "call":
            self.increment_bar(perfect=allHit)
            print('increment bar')
        else:
            print("measure over, resetting gems - %f" % self.getRelativeTick())
            self.catch_passes(True)
            self._refreshBarGems()

    def _startGemTimer(self, tick, gem):
        ''' starts the gem timer'''
        # print("start gem timer %f " % self.getRelativeTick())
        if not gem.hit:
            gem.activate()

    def _playChord(self, tick, chord):
        for note in chord._getMidiTones():
            self.synth.noteon(self.channel, note, self.vel)

    def _endChord(self, tick, chord):
        for note in chord._getMidiTones():
            self.synth.noteoff(self.channel, note)

    def initialize_callbacks(self, increment, catch_passes):
        self.increment_bar = increment
        self.catch_passes = catch_passes