class MainWidget3(BaseWidget): def __init__(self): super(MainWidget3, self).__init__() # create a clock and TempoMap self.clock = Clock() self.tempo = SimpleTempoMap(120) # create a Scheduler self.sched = Scheduler(self.clock, self.tempo) # and text to display our status self.label = topleft_label() self.add_widget(self.label) # to see accumulated output: self.output_text = '' def on_key_down(self, keycode, modifiers): if keycode[1] == 'c': self.clock.toggle() if keycode[1] == 'a': now = self.sched.get_tick() later = now + (2 * kTicksPerQuarter) self.output_text += "now={}. post at tick={}\n".format(now, later) self.cmd = self.sched.post_at_tick(self._do_it, later, 'hello') if keycode[1] == 'b': self.sched.remove(self.cmd) def _do_it(self, tick, msg): self.output_text += "{} (at {})\n".format(msg, tick) def on_update(self): # scheduler gets called every frame to process events self.sched.on_update() self.label.text = self.sched.now_str() + '\n' self.label.text += 'c: toggle clock\n' self.label.text += 'a: post event\n' self.label.text += 'b: remove event\n' self.label.text += self.output_text
class DataPlayer(object): """Plays a steady click every beat. """ def __init__(self, callback=None): super(DataPlayer, self).__init__() self.sched = Scheduler(Clock(), tempo_map=[(0, 0), (100000000000, 100000000000 / 60 * 480)]) self.beat_len = kTicksPerQuarter self.on_cmd = None self.off_cmd = None self.playing = False self.callback = None self.barData = None def start(self): if self.playing: return self.playing = True now = self.sched.get_tick() next_beat = quantize_tick_up(now, self.beat_len) self.on_cmd = self.sched.post_at_tick(self.tick, next_beat) def stop(self): if not self.playing: return self.playing = False if self.off_cmd: self.off_cmd.execute() self.sched.remove(self.on_cmd) self.sched.remove(self.off_cmd) self.on_cmd = None self.off_cmd = None # def set_bar(self, bar_data): # self.barData = bar_data def toggle(self): if self.playing: self.stop() else: self.start() def tick(self, tick, ignore): next_beat = tick + self.beat_len self.on_cmd = self.sched.post_at_tick(self.tick, next_beat) # class ScoreScreen(BaseWidget): # def __init__(self, scoreCard=ScoreCard(), callBack=None): # super(ScoreScreen, self).__init__() # w = Window.width//3 # buttonSize = (w, w//3) # buttonAnchor = (3*Window.width//4, Window.height//4) # self.scoreCard = scoreCard # titleString= "Score Card" # title = Rectangle(pos=(Window.width//4, 3*Window.height//4), size=(2*Window.width//4, Window.height//5)) # label = CoreLabel(text=titleString, font_size=56) # label.refresh() # text = label.texture # title.texture = text # self.objects = AnimGroup() # self.cards = self.generateTiles(self.scoreCard.getAllStrings()) # self.canvas.add(Rectangle(size=(Window.width,Window.height))) # self.canvas.add(self.objects) # self.canvas.add(Color(*(.05,.28,.1))) # self.canvas.add(title) # [self.objects.add(card) for card in self.cards] # print('ScoreScreen Initialized') # def generateTiles(self, scoreStrings): # print(scoreStrings) # xa,ya = (10,0) # x = xa # y = ya # padding = 10 # tile = Card(scoreStrings[0]) # w,h = tile.getSize() # outputs = [tile] # for i in range(1, len(scoreStrings)): # if i % 5 == 0: # y += h + padding # x = xa - w- padding # x += w + padding # outputs.append(Card(scoreStrings[i], pos=(x,y), size=(w,h))) # return outputs # def on_update(self): # self.objects.on_update() # # print('homescreenUpdate') # def on_key_down(self, keycode, modifiers): # pass # run(ScoreScreen)
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