From 0f962a858f000bd21e3aa6d9da01cfd6196ee71e Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak <dos@dosowisko.net> Date: Sat, 23 Sep 2023 03:33:15 +0200 Subject: [PATCH] Stuff --- __init__.py | 16 +++++++++-- difficulty.py | 5 ++-- loading.py | 2 ++ midi/EventDispatcher.py | 1 - midi/MidiFileParser.py | 12 ++++++-- midireader.py | 36 ++++++++++++++++-------- readme.py | 2 +- score.py | 1 + select.py | 11 ++++---- song.py | 61 ++++++++++++++++++++++++++++++++--------- songinfo.py | 25 +---------------- utils.py | 30 ++++++++++++++++++-- 12 files changed, 137 insertions(+), 65 deletions(-) diff --git a/__init__.py b/__init__.py index 1850f04..62b5e27 100644 --- a/__init__.py +++ b/__init__.py @@ -6,6 +6,7 @@ import st3m.run import leds import bl00mbox from time import sleep +import sys_display UNSUPPORTED = False try: import media @@ -36,9 +37,13 @@ class PetalHero(Application): self.after_score = False #self.blm_extra = bl00mbox.Channel("Petal Hero Extra") #self.blm_extra.background_mute_override = True + + self.blm_timeout = 1 readme.install() + #def show_icons(self): return True + def load(self): if self.loaded: return @@ -60,8 +65,13 @@ class PetalHero(Application): self.loaded = True def load_fiba(self): + if not self.loaded: + return + if self.app.fiba_sound: return + + utils.blm_wake(self, 1) self.app.fiba_sound = [] for i in range(6): @@ -135,6 +145,7 @@ class PetalHero(Application): media.think(delta_ms) self.flower.think(delta_ms) + utils.blm_timeout(self, delta_ms) if self.time < 0: self.time = 0 @@ -167,15 +178,16 @@ class PetalHero(Application): self.load() media.load(self.path + '/sounds/menu.mp3') self.time = -1 - leds.set_brightness(69) + leds.set_slew_rate(255) + leds.set_auto_update(0) def on_exit(self): super().on_exit() if UNSUPPORTED: return media.stop() + leds.set_slew_rate(255) leds.set_all_rgb(0, 0, 0) - leds.set_brightness(69) leds.update() if self.vm.direction == ViewTransitionDirection.BACKWARD: utils.play_back(self.app) diff --git a/difficulty.py b/difficulty.py index 1b420fd..05e26ef 100644 --- a/difficulty.py +++ b/difficulty.py @@ -104,16 +104,17 @@ class DifficultyView(BaseView): self._sc.think(ins, delta_ms) media.think(delta_ms) self.flower.think(delta_ms) + utils.blm_timeout(self, delta_ms) self._scroll_pos += delta_ms / 1000 if not self.is_active(): return - if self.input.buttons.app.left.pressed: + if self.input.buttons.app.left.pressed or self.input.buttons.app.left.repeated: self._sc.scroll_left() self._scroll_pos = 0.0 utils.play_crunch(self.app) - elif self.input.buttons.app.right.pressed: + elif self.input.buttons.app.right.pressed or self.input.buttons.app.right.repeated: self._sc.scroll_right() self._scroll_pos = 0.0 utils.play_crunch(self.app) diff --git a/loading.py b/loading.py index 043d105..05f0bfb 100644 --- a/loading.py +++ b/loading.py @@ -3,6 +3,7 @@ import gc import sys_display import song +import utils class LoadingView(BaseView): def __init__(self, app, song, difficulty): @@ -23,6 +24,7 @@ class LoadingView(BaseView): ctx.text("Loading...") def think(self, ins: InputState, delta_ms: int) -> None: + utils.blm_timeout(self, delta_ms) if self.vm.transitioning or not self.is_active(): return #gc.collect() self.vm.replace(song.SongView(self.app, self.song, self.difficulty), ViewTransitionBlend()) diff --git a/midi/EventDispatcher.py b/midi/EventDispatcher.py index 85982aa..1a61755 100644 --- a/midi/EventDispatcher.py +++ b/midi/EventDispatcher.py @@ -192,7 +192,6 @@ class EventDispatcher: # 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: diff --git a/midi/MidiFileParser.py b/midi/MidiFileParser.py index d0b6bd0..fed6e37 100644 --- a/midi/MidiFileParser.py +++ b/midi/MidiFileParser.py @@ -77,6 +77,7 @@ class MidiFileParser: "Parses a track chunk. This is the most important part of the parser." # set time to 0 at start of a track + self.ignored = False self.dispatch.reset_time() dispatch = self.dispatch @@ -126,20 +127,25 @@ class MidiFileParser: meta_type = raw_in.readBew() meta_length = raw_in.readVarLen() meta_data = raw_in.nextSlice(meta_length) + if self.ignored and meta_type != TEMPO: return if not meta_length: return - dispatch.meta_event(meta_type, meta_data) - if meta_type == END_OF_TRACK: return + res = dispatch.meta_event(meta_type, meta_data) + if meta_type == SEQUENCE_NAME: + self.ignored = res + if meta_type == END_OF_TRACK: + return # Oh! Then it must be a midi event (channel voice message) else: data_size = data_sizes.get(hi_nible, 0) channel_data = raw_in.nextSlice(data_size) + if self.ignored: return event_type, channel = hi_nible, lo_nible dispatch.channel_messages(event_type, channel, channel_data) def parseMTrkChunks(self): "Parses all track chunks." - for t in range(min(2, self.nTracks)): + for t in range(self.nTracks): self._current_track = t self.parseMTrkChunk() # this is where it's at! self.dispatch.eof() diff --git a/midireader.py b/midireader.py index dc631e8..b02c672 100644 --- a/midireader.py +++ b/midireader.py @@ -17,10 +17,10 @@ class Difficulty: return self.text difficulties = { - SUPAEASY_DIFFICULTY: Difficulty(SUPAEASY_DIFFICULTY, "Supaeasy"), - EASY_DIFFICULTY: Difficulty(EASY_DIFFICULTY, "Easy"), - MEDIUM_DIFFICULTY: Difficulty(MEDIUM_DIFFICULTY, "Medium"), - AMAZING_DIFFICULTY: Difficulty(AMAZING_DIFFICULTY, "Amazing"), + SUPAEASY_DIFFICULTY: Difficulty(SUPAEASY_DIFFICULTY, "Easy"), + EASY_DIFFICULTY: Difficulty(EASY_DIFFICULTY, "Medium"), + MEDIUM_DIFFICULTY: Difficulty(MEDIUM_DIFFICULTY, "Hard"), + AMAZING_DIFFICULTY: Difficulty(AMAZING_DIFFICULTY, "Expert"), } baseNotes = { @@ -37,6 +37,8 @@ for basenote, diff in baseNotes.items(): reverseNoteMap = dict([(v, k) for k, v in list(noteMap.items())]) +noteSet = set(noteMap.keys()) + class Event: __slots__ = ("length", "time") @@ -119,6 +121,8 @@ class MidiReader(midi.MidiOutStream): #self.tracks = [Track() for t in range(len(difficulties))] self.difficulty = difficulty self.track = Track() + self.nTracks = -1 + self.ignored = False def addEvent(self, track, event): time = event.time @@ -158,8 +162,10 @@ class MidiReader(midi.MidiOutStream): def header(self, format, nTracks, division): self.ticksPerBeat = division + self.nTracks = nTracks def tempo(self, value): + #if self.ignored: return bpm = 60.0 * 10.0**6 / value if not self.tempoMarkers or bpm != self.tempoMarkers[-1][1]: self.tempoMarkers.append((midi.MidiOutStream.abs_time(self), bpm)) @@ -170,24 +176,30 @@ class MidiReader(midi.MidiOutStream): #print('bpm', bpm) #self.addEvent(None, Tempo(bpm)) + def sequence_name(self, val): + name = ''.join(list(map(chr, val))) + #print(name) + self.ignored = name != "PART GUITAR" and self.nTracks > 2 + return self.ignored + def note_on(self, channel, note, velocity): - if self.get_current_track() > 1: return + if self.ignored: return #print("note_on", channel, note, velocity, self.abs_time()) + if not note in noteMap: + return self.velocity[note] = velocity self.heldNotes[(self.get_current_track(), channel, note)] = self.abs_time() def note_off(self, channel, note, velocity): - if self.get_current_track() > 1: return + if self.ignored: return #print("note_off", channel, note, velocity, self.abs_time()) + if not note in noteMap: + return try: startTime = self.heldNotes[(self.get_current_track(), channel, note)] endTime = self.abs_time() del self.heldNotes[(self.get_current_track(), channel, note)] - if note in noteMap: - track, number = noteMap[note] - self.addEvent(track, Note(startTime, number, endTime - startTime, special = self.velocity[note] == 127)) - else: - #print("MIDI note 0x%x at %d does not map to any game note." % (note, self.abs_time())) - pass + track, number = noteMap[note] + self.addEvent(track, Note(startTime, number, endTime - startTime, special = self.velocity[note] == 127)) except KeyError: print("MIDI note 0x%x on channel %d ending at %d was never started." % (note, channel, self.abs_time())) diff --git a/readme.py b/readme.py index 09e5e46..1de3855 100644 --- a/readme.py +++ b/readme.py @@ -7,7 +7,7 @@ Petal Hero is compatible with songs for Frets on Fire, but with one caveat: you need to mix audio tracks and save them as MP3. This should do: - sox -m *.ogg -c 1 -C 64 -r 48000 song.mp3 + sox -m *.ogg -c 1 -C 128 -r 48k song.mp3 norm -3 You need song.ini, song.mp3 and notes.mid in the song directory. """ diff --git a/score.py b/score.py index 3098f23..20a48ff 100644 --- a/score.py +++ b/score.py @@ -92,6 +92,7 @@ class ScoreView(BaseView): def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) media.think(delta_ms) + utils.blm_timeout(self, delta_ms) self.time += delta_ms / 1000 if self.time > 1.5 and not self.played: diff --git a/select.py b/select.py index a9080ff..9a5317c 100644 --- a/select.py +++ b/select.py @@ -182,6 +182,7 @@ class SelectView(BaseView): def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) self._sc.think(ins, delta_ms) + utils.blm_timeout(self, delta_ms) if not self.to_process and not self.processing_now: media.think(delta_ms) self.flower.think(delta_ms) @@ -191,11 +192,14 @@ class SelectView(BaseView): if cur_target < 0: cur_target = 0 if cur_target > len(self.songs) - 1: cur_target = len(self.songs) - 1 - if self.input.buttons.app.left.pressed: + if not self.is_active(): + return + + if self.input.buttons.app.left.pressed or self.input.buttons.app.left.repeated: self._sc.scroll_left() self._scroll_pos = 0.0 utils.play_crunch(self.app) - elif self.input.buttons.app.right.pressed: + elif self.input.buttons.app.right.pressed or self.input.buttons.app.right.repeated: self._sc.scroll_right() self._scroll_pos = 0.0 utils.play_crunch(self.app) @@ -204,9 +208,6 @@ class SelectView(BaseView): if pos < 0: pos = 0 if pos > len(self.songs) - 1: pos = len(self.songs) - 1 - if not self.is_active(): - return - if pos != cur_target: media.load(self.songs[pos].dirName + "/song.mp3") diff --git a/song.py b/song.py index 16ddd12..3bbea0d 100644 --- a/song.py +++ b/song.py @@ -5,7 +5,9 @@ import st3m.run import math import leds import sys_display +import sys_scope from micropython import const +from time import ticks_ms try: import media from st3m.ui.view import ViewTransitionDirection @@ -63,16 +65,20 @@ class SongView(BaseView): self.notes = set() self.events_in_margin = set() self.petal_events = [set() for i in range(5)] + self.last_played = 0 self.good = 0.0 self.bad = 0.0 self.missed = [0.0] * 5 self.miss = 0.0 - self.oldmem = 0 + #self.oldmem = 0 + #self.redraw = True + #self.acc = 0 def draw(self, ctx: Context) -> None: #mem = gc.mem_alloc() + #self.redraw = True self.time += VIDEO_DELAY other = int(self.time / 2 / self.data.period) % 2 @@ -211,16 +217,26 @@ class SongView(BaseView): ctx.save() ctx.rgb(0.5 + self.bad * 0.4, 0.5, 0.5) + #ctx.rectangle(-8, -8, 15, 15) + #ctx.clip() + ctx.line_width = 12 + ctx.scale(0.0625, 0.125 * 0.3) #ctx.rotate(wiggle) + ctx.begin_path() if self.started: - ctx.rectangle(-8, -8, 15, 15) - ctx.clip() # for firmwares that stroke the scope... - ctx.scale(0.0625, 0.125 * 0.3) - ctx.begin_path() - ctx.scope() - ctx.line_to(120, 0) - ctx.line_to(-120, 0) + buf = sys_scope.get_buffer_x() + ctx.move_to(-120, 0) + for i in range(0, len(buf), 32): + val = buf[i] / 32 + ctx.line_to(-120 + i, max(6, val)) + for i in range(len(buf) - 1, 0, -32): + val = buf[i] / 32 + ctx.line_to(-120 + i, min(-6, val)) ctx.fill() + else: + ctx.move_to(-120, 0) + ctx.line_to(120, 0) + ctx.stroke() ctx.restore() ctx.gray(0.8) @@ -248,13 +264,25 @@ class SongView(BaseView): ctx.font_size = 64 ctx.text("PAUSED") #print("draw", gc.mem_alloc() - mem) + #t = ticks_ms() + #print(ticks_ms() - t) def think(self, ins: InputState, delta_ms: int) -> None: #mem = gc.mem_alloc() #print(mem - self.oldmem) #self.oldmem = mem + + #self.acc += delta_ms + #if self.redraw: + # delta_ms = self.acc + # self.acc = 0 + # self.redraw = False + #else: + # return + #if (delta_ms > 100): print(delta_ms) super().think(ins, delta_ms) + utils.blm_timeout(self, delta_ms) if self.first_think: self.first_think = False @@ -262,7 +290,7 @@ class SongView(BaseView): media.think(delta_ms) - if self.input.buttons.os.middle.pressed and not self.is_active(): + if self.input.buttons.os.middle.pressed and not self.is_active(): # TODO: will that work with pending transitions? self.vm.push(self) self.vm.pop(ViewTransitionSwipeRight(), depth=2) return @@ -273,6 +301,7 @@ class SongView(BaseView): if self.song and self.started and media.get_position() == media.get_duration() and not self.finished: self.finished = True media.stop() + gc.collect() self.vm.replace(score.ScoreView(self.app, self.data, self.longeststreak), ViewTransitionBlend()) return @@ -316,7 +345,7 @@ class SongView(BaseView): self.petal_events[i].clear() earlyMargin = 60000.0 / self.data.bpm / 3.5 - lateMargin = 60000.0 / self.data.bpm / 3.5 + lateMargin = 60000.0 / self.data.bpm / 3.5 + delta_ms self.notes.clear() self.events_in_margin.clear() @@ -340,6 +369,8 @@ class SongView(BaseView): if not self.demo_mode: self.missed[event.number] = 1.0 self.miss = 1.0 + if event.time >= self.last_played: + media.set_volume(0.25) if event.played and event.time + event.length - lateMargin > self.time: p = 4 if event.number == 0 else event.number - 1 if not ins.captouch.petals[p*2].pressed and not event.missed: @@ -366,7 +397,7 @@ class SongView(BaseView): event = e #event = sorted(events, key = lambda x: x.time)[0] event.played = True - self.led_override[petal] = 120 + self.led_override[petal] = 50 if event.time > self.laststreak: self.streak += 1 self.laststreak = event.time @@ -374,6 +405,8 @@ class SongView(BaseView): print(self.time - event.time) self.petals[petal] = event self.good = 1.0 + self.last_played = event.time + media.set_volume(1.0) if not pressed: self.petals[petal] = None @@ -384,6 +417,7 @@ class SongView(BaseView): utils.petal_leds(petal, d) leds.update() + #gc.collect() #print("think", gc.mem_alloc() - mem) def on_enter(self, vm: Optional[ViewManager]) -> None: @@ -393,7 +427,8 @@ class SongView(BaseView): return if self.app: media.load(self.app.path + '/sounds/start.mp3') - self.app.blm.volume = 10000 + utils.volume(self.app, 10000) + leds.set_slew_rate(238) def on_enter_done(self): #sys_display.set_mode(sys_display.get_mode() | 512) @@ -410,7 +445,7 @@ class SongView(BaseView): #gc.enable() if self.app and not self.finished: - self.app.blm.volume = 14000 + utils.volume(self.app, 14000) utils.play_back(self.app) if self.song and not self.started: diff --git a/songinfo.py b/songinfo.py index 2a1ec67..23d174a 100644 --- a/songinfo.py +++ b/songinfo.py @@ -3,30 +3,7 @@ import os import midi import midireader -AMAZING_DIFFICULTY = 0 -MEDIUM_DIFFICULTY = 1 -EASY_DIFFICULTY = 2 -SUPAEASY_DIFFICULTY = 3 - -class Difficulty: - def __init__(self, id, text): - self.id = id - self.text = text - - def __str__(self): - return self.text - - def __repr__(self): - return self.text - -difficulties = { - SUPAEASY_DIFFICULTY: Difficulty(SUPAEASY_DIFFICULTY, "Supaeasy"), - EASY_DIFFICULTY: Difficulty(EASY_DIFFICULTY, "Easy"), - MEDIUM_DIFFICULTY: Difficulty(MEDIUM_DIFFICULTY, "Medium"), - AMAZING_DIFFICULTY: Difficulty(AMAZING_DIFFICULTY, "Amazing"), -} - -noteSet = set(midireader.noteMap.keys()) +from midireader import difficulties, noteSet class MidiInfoReader(midi.MidiOutStream): __slots__ = ("notes", ) diff --git a/utils.py b/utils.py index d7fb6ae..2ae4ec2 100644 --- a/utils.py +++ b/utils.py @@ -3,6 +3,7 @@ import leds import random import os import time +import sys_bl00mbox def dim(color, val): res = [] @@ -45,25 +46,46 @@ def petal_leds(petal, val, color = None): led += 40 leds.set_rgb(led, *color) +def blm_wake(app, timeout): + if not app or not app.loaded: return + if app.blm_timeout == 0: + sys_bl00mbox.channel_enable(app.blm.channel_num) + if timeout > app.blm_timeout: + app.blm_timeout = timeout + +def blm_timeout(view, delta_ms): + app = view.app + if not app: return + if not view.is_active(): return + if app.blm_timeout == 0: return + app.blm_timeout -= delta_ms + if app.blm_timeout <= 0: + sys_bl00mbox.channel_disable(app.blm.channel_num) + app.blm_timeout = 0 + def play_crunch(app): if not app: return if not app.crunch_sound: return app.crunch_sound[random.randint(0, 2)].signals.trigger.start() + blm_wake(app, 600) def play_fiba(app): if not app: return if not app.fiba_sound: return app.fiba_sound[random.randint(0, 5)].signals.trigger.start() + blm_wake(app, 1400) def play_go(app): - if not app: return + if not app or not app.loaded: return app.in_sound.signals.trigger.start() + blm_wake(app, 1200) def play_back(app): - if not app: return + if not app or not app.loaded: return app.out_sound.signals.trigger.start() + blm_wake(app, 800) def circle(ctx, x, y, r): c = 0.55191502449 @@ -92,3 +114,7 @@ def timed_function(f, *args, **kwargs): print('Function {} Time = {:6.3f}ms'.format(myname, delta/1000)) return result return new_func + +def volume(app, vol): + if app and app.loaded: + app.blm.volume = vol -- GitLab