diff --git a/loading.py b/loading.py index 7688dda92ce2f46e4f77e73d976f3ac6da5cb529..72b11dfb270e1135d4cd408796ed57210181b328 100644 --- a/loading.py +++ b/loading.py @@ -26,3 +26,4 @@ class LoadingView(BaseView): if self.vm.transitioning or not self.vm.is_active(self): return gc.collect() self.vm.replace(song.SongView(self.app, self.song, self.difficulty), ViewTransitionBlend()) + gc.collect() diff --git a/midi/EventDispatcher.py b/midi/EventDispatcher.py index 9014d265497b794f6c057de96003e960ac955634..5e5530284ef9dcb60f49e0345376932c765dded1 100644 --- a/midi/EventDispatcher.py +++ b/midi/EventDispatcher.py @@ -53,18 +53,20 @@ class EventDispatcher: def start_of_track(self, current_track): + pass "Triggers the start of track event" # I do this twice so that users can overwrite the # start_of_track event handler without worrying whether the # track number is updated correctly. - self.outstream.set_current_track(current_track) - self.outstream.start_of_track(current_track) + #self.outstream.set_current_track(current_track) + #self.outstream.start_of_track(current_track) def sysex_event(self, data): "Dispatcher for sysex events" - self.outstream.sysex_event(data) + pass + #self.outstream.sysex_event(data) def eof(self): @@ -78,8 +80,9 @@ class EventDispatcher: def reset_time(self): + pass "Updates relative/absolute time." - self.outstream.reset_time() + #self.outstream.reset_time() # Event dispatchers for similar types of events @@ -104,8 +107,10 @@ class EventDispatcher: elif (NOTE_OFF & 0xF0) == hi_nible: note, velocity = data stream.note_off(channel, note, velocity) + + return - elif (AFTERTOUCH & 0xF0) == hi_nible: + if (AFTERTOUCH & 0xF0) == hi_nible: note, velocity = data stream.aftertouch(channel, note, velocity) @@ -137,21 +142,22 @@ class EventDispatcher: def continuous_controllers(self, channel, controller, value): - + pass "Dispatches channel messages" - stream = self.outstream + #stream = self.outstream # I am not really shure if I ought to dispatch continuous controllers # There's so many of them that it can clutter up the OutStream # classes. # So I just trigger the default event handler - stream.continuous_controller(channel, controller, value) + #stream.continuous_controller(channel, controller, value) def system_commons(self, common_type, common_data): + return "Dispatches system common messages" @@ -185,6 +191,13 @@ class EventDispatcher: stream = self.outstream + if meta_type == TEMPO: + b1, b2, b3 = toBytes(data) + # uses 3 bytes to represent time between quarter + # notes in microseconds + stream.tempo((b1<<16) + (b2<<8) + b3) + return + # SEQUENCE_NUMBER = 0x00 (00 02 ss ss (seq-number)) if meta_type == SEQUENCE_NUMBER: number = readBew(data) diff --git a/midi/MidiFileParser.py b/midi/MidiFileParser.py index 97f28553cc1689688b2028c54de516ad6a9d7891..cc9ea037e677aea263848f2f2cad45afb920cc88 100644 --- a/midi/MidiFileParser.py +++ b/midi/MidiFileParser.py @@ -62,7 +62,6 @@ class MidiFileParser: self.dispatch.header(self.format, self.nTracks, self.division) - def parseMTrkChunk(self): "Parses a track chunk. This is the most important part of the parser." @@ -120,35 +119,6 @@ class MidiFileParser: if not meta_length: return dispatch.meta_event(meta_type, meta_data) if meta_type == END_OF_TRACK: return - - - # Is it a sysex_event ?? - elif status == SYSTEM_EXCLUSIVE: - # ignore sysex events - sysex_length = raw_in.readVarLen() - # don't read sysex terminator - sysex_data = raw_in.nextSlice(sysex_length-1) - # only read last data byte if it is a sysex terminator - # It should allways be there, but better safe than sorry - if raw_in.readBew(move_cursor=0) == END_OFF_EXCLUSIVE: - eo_sysex = raw_in.readBew() - dispatch.sysex_event(sysex_data) - # the sysex code has not been properly tested, and might be fishy! - - - # is it a system common event? - elif hi_nible == 0xF0: # Hi bits are set then - data_sizes = { - MTC:1, - SONG_POSITION_POINTER:2, - SONG_SELECT:1, - } - data_size = data_sizes.get(hi_nible, 0) - common_data = raw_in.nextSlice(data_size) - common_type = lo_nible - dispatch.system_common(common_type, common_data) - - # Oh! Then it must be a midi event (channel voice message) else: data_sizes = { diff --git a/midireader.py b/midireader.py index 26a16e263866783fd75d6b3bff111737e3a3a524..ea725a9e601652596e9db2ed3830b292abf3d168 100644 --- a/midireader.py +++ b/midireader.py @@ -68,7 +68,7 @@ class Tempo(Event): return "<%d bpm>" % self.bpm class Track: - granularity = 50 + granularity = 500 def __init__(self): self.events = [] @@ -83,12 +83,12 @@ class Track: self.events[t].add(event) self.allEvents.add(event) - def getEvents(self, startTime, endTime): - t1, t2 = [int(x) for x in [startTime / self.granularity, endTime / self.granularity]] + def getEvents(self, startTime, endTime, events): + t1, t2 = [int(x) for x in [startTime / self.granularity, endTime / self.granularity + 1]] if t1 > t2: t1, t2 = t2, t1 - events = set() + events.clear() for t in range(max(t1, 0), min(len(self.events), t2)): for event in self.events[t]: events.add(event) diff --git a/song.py b/song.py index 6d039a33363b30d8c243d66478846cc648ace614..50fe07781ec1310862d44b689bca52b412d8e798 100644 --- a/song.py +++ b/song.py @@ -6,6 +6,7 @@ import math import media import leds import sys_display +from micropython import const if __name__ == '__main__': import sys @@ -16,10 +17,12 @@ import midireader import utils import flower import score +import gc -AUDIO_DELAY = -90 -VIDEO_DELAY = 60 - AUDIO_DELAY -RADIUS = 22 +AUDIO_DELAY = const(-90) +VIDEO_DELAY = const(60 - AUDIO_DELAY) +RADIUS = const(22) +tau = const(6.283185307179586) class SongView(BaseView): def __init__(self, app, song, difficulty): @@ -41,7 +44,7 @@ class SongView(BaseView): self.started = False self.time = -self.delay self.flower = flower.Flower(0) - self.events = [] + self.events = set() self.petals = [None] * 5 self.demo_mode = False self.fps = False @@ -54,13 +57,19 @@ class SongView(BaseView): self.led_override = [0] * 5 self.laststreak = -1 self.scoreview = None + self.notes = set() + self.events_in_margin = set() + self.petal_events = [set() for i in range(5)] self.good = 0.0 self.bad = 0.0 self.missed = [0.0] * 5 self.miss = 0.0 + self.oldmem = 0 + def draw(self, ctx: Context) -> None: + #mem = gc.mem_alloc() self.time += VIDEO_DELAY other = int(self.time / 2 / self.data.period) % 2 @@ -71,7 +80,8 @@ class SongView(BaseView): ctx.gray(0.25) - for i in [1, 0]: + i = 1 + while i >= 0: #ctx.gray(0.4 + i * 0.12 + (1-(self.time/2 / self.data.period) % 1) * 0.12) #ctx.line_width = 1.75 + i * 0.12 + (1-(self.time/2 / self.data.period) % 1) * 0.12 ctx.gray((0.1 if other ^ (i == 1) else 0.0) + self.miss * 0.15) @@ -83,6 +93,7 @@ class SongView(BaseView): else: ctx.arc(0, 0, RADIUS + pos, 0, tau, 0) ctx.fill() + i -= 1 self.time -= VIDEO_DELAY ctx.line_width = 2 @@ -115,7 +126,7 @@ class SongView(BaseView): utils.circle(ctx, 0, 0, RADIUS) ctx.save() - ctx.rotate(tau / 10 + tau / 5) + ctx.rotate(const(tau / 10 + tau / 5)) start = self.time + self.data.period * 4 stop = self.time for i in range(5): @@ -212,8 +223,13 @@ class SongView(BaseView): ctx.font_size = 16 ctx.move_to(0, 105) ctx.text(f"{sys_display.fps():.2f}") + #print("draw", gc.mem_alloc() - mem) def think(self, ins: InputState, delta_ms: int) -> None: + #mem = gc.mem_alloc() + #print(mem - self.oldmem) + #self.oldmem = mem + super().think(ins, delta_ms) if self.first_think: @@ -275,24 +291,27 @@ class SongView(BaseView): self.miss = max(0, self.miss - delta_ms / self.data.period) for i in range(5): self.missed[i] = max(0, self.missed[i] - delta_ms / 1500) + self.petal_events[i].clear() earlyMargin = 60000.0 / self.data.bpm / 3.5 lateMargin = 60000.0 / self.data.bpm / 3.5 - notes = set() - events_in_margin = set() + self.notes.clear() + self.events_in_margin.clear() if self.app and not self.finished: - self.events = self.data.track.getEvents(self.time - self.data.period / 2, self.time + self.data.period * 4) + self.data.track.getEvents(self.time - self.data.period / 2, self.time + self.data.period * 4, self.events) else: - self.events = [] + self.events.clear() for event in self.events: if isinstance(event, midireader.Note): if event.time <= self.time <= event.time + event.length: - notes.add(event.number) + self.notes.add(event.number) if event.time - earlyMargin <= self.time <= event.time + lateMargin: - events_in_margin.add(event) + self.events_in_margin.add(event) + if not event.played: + self.petal_events[event.number].add(event) if event.time + lateMargin < self.time and not event.played and not event.missed: event.missed = True self.streak = 0 @@ -309,7 +328,7 @@ class SongView(BaseView): for petal in range(5): p = 4 if petal == 0 else petal - 1 pressed = ins.captouch.petals[p*2].pressed - events = set(filter(lambda x: x.number == petal and not x.played, events_in_margin)) + events = self.petal_events[petal] self.led_override[petal] = max(0, self.led_override[petal] - delta_ms) @@ -319,7 +338,11 @@ class SongView(BaseView): self.bad = 1.0 self.streak = 0 else: - event = sorted(events, key = lambda x: x.time)[0] + event = events.pop() + for e in events: + if e.time < event.time: + event = e + #event = sorted(events, key = lambda x: x.time)[0] event.played = True self.led_override[petal] = 120 if event.time > self.laststreak: @@ -334,11 +357,12 @@ class SongView(BaseView): self.petals[petal] = None active = self.petals[petal] is not None - d = 1.0 if (active and self.petals[petal].time + self.petals[petal].length >= self.time) or self.led_override[petal] else (0.15 if pressed else (1.0 if petal in notes and self.demo_mode else 0.069)) + d = 1.0 if (active and self.petals[petal].time + self.petals[petal].length >= self.time) or self.led_override[petal] else (0.15 if pressed else (1.0 if petal in self.notes and self.demo_mode else 0.069)) if d: utils.petal_leds(petal, d) leds.update() + #print("think", gc.mem_alloc() - mem) def on_enter(self, vm: Optional[ViewManager]) -> None: super().on_enter(vm) diff --git a/utils.py b/utils.py index eaec8da317d15bcbb4ce90563bc59f212cf70301..d456ba06846c1482cde3965d090c6234292f8a65 100644 --- a/utils.py +++ b/utils.py @@ -3,7 +3,11 @@ import leds import random import os -dim = lambda x, y: tuple(map(lambda x: x * y, x)) +def dim(color, val): + res = [] + for i in range(len(color)): + res.append(color[i] * val) + return res def background(ctx): ctx.linear_gradient(-120, -120, 120, 120) @@ -34,9 +38,8 @@ def petal_leds(petal, val, color = None): if not color: color = PETAL_COLORS[petal] color = dim(color, val) - start = -11 + petal * 8 - for i in range(start, start + 7): - led = i + for i in range(7): + led = i - 11 + petal * 8 if led < 0: led += 40 leds.set_rgb(led, *color)