diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..63f7ca04081e597efcaef8bdb337dc0f5b44b5b9 --- /dev/null +++ b/__init__.py @@ -0,0 +1,313 @@ +import sys +import media, math, random +from st3m.ui.colours import * +from st3m.input import InputController +from st3m.ui.view import BaseView, ViewManager, ViewTransitionSwipeLeft +import leds + +sys.path.append('/flash/apps/PetalHero') + +import midi + +AMAZING_DIFFICULTY = 0 +MEDIUM_DIFFICULTY = 1 +EASY_DIFFICULTY = 2 +SUPAEASY_DIFFICULTY = 3 + +noteMap = { # difficulty, note + 0x60: (AMAZING_DIFFICULTY, 0), + 0x61: (AMAZING_DIFFICULTY, 1), + 0x62: (AMAZING_DIFFICULTY, 2), + 0x63: (AMAZING_DIFFICULTY, 3), + 0x64: (AMAZING_DIFFICULTY, 4), + 0x54: (MEDIUM_DIFFICULTY, 0), + 0x55: (MEDIUM_DIFFICULTY, 1), + 0x56: (MEDIUM_DIFFICULTY, 2), + 0x57: (MEDIUM_DIFFICULTY, 3), + 0x58: (MEDIUM_DIFFICULTY, 4), + 0x48: (EASY_DIFFICULTY, 0), + 0x49: (EASY_DIFFICULTY, 1), + 0x4a: (EASY_DIFFICULTY, 2), + 0x4b: (EASY_DIFFICULTY, 3), + 0x4c: (EASY_DIFFICULTY, 4), + 0x3c: (SUPAEASY_DIFFICULTY, 0), + 0x3d: (SUPAEASY_DIFFICULTY, 1), + 0x3e: (SUPAEASY_DIFFICULTY, 2), + 0x3f: (SUPAEASY_DIFFICULTY, 3), + 0x40: (SUPAEASY_DIFFICULTY, 4), +} + +reverseNoteMap = dict([(v, k) for k, v in list(noteMap.items())]) + +dim = lambda x, y: tuple(map(lambda x: x * y, x)) + +class Event: + def __init__(self, length): + self.length = length + +class Note(Event): + def __init__(self, number, length, special = False, tappable = False): + Event.__init__(self, length) + self.number = number + self.played = False + self.special = special + self.tappable = tappable + + def __repr__(self): + return "Note <#%d> length %d" % (self.number, self.length) + +class Tempo(Event): + def __init__(self, bpm): + super().__init__(0) + self.bpm = bpm + + def __repr__(self): + return "<%d bpm>" % self.bpm + + +class MidiReader(midi.MidiOutStream.MidiOutStream): + def __init__(self, song): + super().__init__() + self.song = song + self.bpm = 0 + self.heldNotes = {} + self.velocity = {} + self.ticksPerBeat = 480 + self.tempoMarkers = [] + + def addEvent(self, track, event, time = None): + if time is None: + time = self.abs_time() + assert time >= 0 + #print('addEvent', track, event, time) + #if track is None: + # for t in self.song.tracks: + # t.addEvent(time, event) + #elif track < len(self.song.tracks): + # self.song.tracks[track].addEvent(time, event) + + def abs_time(self): + def ticksToBeats(ticks, bpm): + return (60000.0 * ticks) / (bpm * self.ticksPerBeat) + + if self.bpm: + currentTime = midi.MidiOutStream.MidiOutStream.abs_time(self) + + # Find out the current scaled time. + # Yeah, this is reeally slow, but fast enough :) + scaledTime = 0.0 + tempoMarkerTime = 0.0 + currentBpm = self.bpm + for i, marker in enumerate(self.tempoMarkers): + time, bpm = marker + if time > currentTime: + break + scaledTime += ticksToBeats(time - tempoMarkerTime, currentBpm) + tempoMarkerTime, currentBpm = time, bpm + return scaledTime + ticksToBeats(currentTime - tempoMarkerTime, currentBpm) + return 0.0 + + def header(self, format, nTracks, division): + self.ticksPerBeat = division + + def tempo(self, value): + bpm = 60.0 * 10.0**6 / value + self.tempoMarkers.append((midi.MidiOutStream.MidiOutStream.abs_time(self), bpm)) + if not self.bpm: + self.bpm = bpm + #self.song.setBpm(bpm) + #print('bpm', bpm) + self.addEvent(None, Tempo(bpm)) + + def note_on(self, channel, note, velocity): + if self.get_current_track() > 1: 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 + 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(number, endTime - startTime, special = self.velocity[note] == 127), time = startTime) + else: + print("MIDI note 0x%x at %d does not map to any game note." % (note, self.abs_time())) + pass + except KeyError: + print("MIDI note 0x%x on channel %d ending at %d was never started." % (note, channel, self.abs_time())) + + +from st3m.application import Application, ApplicationContext +import st3m.run +import leds + + +class Flower: + def __init__(self, x: float, y: float, z: float) -> None: + self.x = x + self.y = y + self.z = z + self.rot = 0.0 + self.rot_speed = 1 / 800 #(((random.getrandbits(16) - 32767) / 32767.0) - 0.5) / 800 + + def draw(self, ctx: Context) -> None: + ctx.save() + ctx.rotate(self.rot) + ctx.translate(-78 + self.x, -70 + self.y) + #ctx.translate(50, 40) + + #ctx.translate(-50, -40) + #ctx.scale(100 / self.z, 100.0 / self.z) + ctx.move_to(76.221727, 3.9788409).curve_to( + 94.027758, 31.627675, 91.038918, 37.561293, 94.653428, 48.340473 + ).rel_curve_to( + 25.783102, -3.90214, 30.783332, -1.52811, 47.230192, 4.252451 + ).rel_curve_to( + -11.30184, 19.609496, -21.35729, 20.701768, -35.31018, 32.087063 + ).rel_curve_to( + 5.56219, 12.080061, 12.91196, 25.953973, 9.98735, 45.917643 + ).rel_curve_to( + -19.768963, -4.59388, -22.879866, -10.12216, -40.896842, -23.93099 + ).rel_curve_to( + -11.463256, 10.23025, -17.377386, 18.2378, -41.515124, 25.03533 + ).rel_curve_to( + 0.05756, -29.49286, 4.71903, -31.931936, 10.342734, -46.700913 + ).curve_to( + 33.174997, 77.048676, 19.482194, 71.413009, 8.8631648, 52.420793 + ).curve_to( + 27.471602, 45.126773, 38.877997, 45.9184, 56.349456, 48.518302 + ).curve_to( + 59.03275, 31.351935, 64.893201, 16.103886, 76.221727, 3.9788409 + ).close_path().fill() + ctx.restore() + +class SecondScreen(BaseView): + def draw(self, ctx: Context) -> None: + # Paint the background black + ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + # Green square + ctx.rgb(0, 255, 0).rectangle(-20, -20, 40, 40).fill() + + def think(self, ins: InputState, delta_ms: int) -> None: + self.input.think(ins, delta_ms) + media.think(delta_ms) + + def on_enter(self, vm: Optional[ViewManager]) -> None: + super().on_enter(vm) + #self._vm = vm + # Ignore the button which brought us here until it is released + #self.input._ignore_pressed() + media.load('/sd/song.mp3') + +class App(Application): + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx) + self.input = InputController() + self.path = getattr(app_ctx, 'bundle_path', '/flash/apps/PetalHero') + if not self.path: + self.path = '/flash/apps/PetalHero' + print(self.path) + midiIn = midi.MidiInFile(MidiReader(None), self.path + '/notes.mid') + #midiIn.read() + + self.flower = Flower(0, 0, 0.01) + self.time = 0 + self.repeats = 0 + + def draw(self, ctx: Context): + ctx.rgb(*GO_GREEN) + ctx.rgb(*PUSH_RED) + + ctx.linear_gradient(-120, -120, 120, 120) + + ctx.add_stop(0.0, [94, 0, 0], 1.0) + ctx.add_stop(1.0, [51, 0, 0], 1.0) + + ctx.rectangle(-120, -120, 240, 240) + ctx.fill() + + ctx.save() + ctx.translate(0, -10) + + ctx.rgba(0.1, 0.4, 0.3, 0.42) + self.flower.draw(ctx) + + ctx.linear_gradient(-50, 0, 50, 0) + ctx.add_stop(0.0, [145, 37, 0], 1.0) + ctx.add_stop(0.5, [245, 111, 0], 0.75) + ctx.add_stop(1.0, [151, 42, 0], 1.0) + + #ctx.rgb(0.1, 0.4, 0.3) + ctx.font = "Camp Font 2" + ctx.font_size = 90 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + ctx.move_to (0, -30) + ctx.text("PETAL") + ctx.move_to (0, 30) + ctx.text("HERO") + + ctx.restore() + + ctx.font = "Camp Font 3" + ctx.font_size = 16 + ctx.text_align = ctx.CENTER + ctx.text_baseline = ctx.MIDDLE + ctx.gray(1.0) + ctx.move_to(0, 70 + math.sin(self.time * 4) * 4) + ctx.text("Press the button...") + + def think(self, ins: InputState, delta_ms: int) -> None: + self.input.think(ins, delta_ms) + media.think(delta_ms) + for c in [self.flower]: + c.rot += float(delta_ms) * c.rot_speed + self.time += delta_ms / 1000 + + if self.time // 18 > self.repeats and self.repeats >= 0: + media.load(self.path + '/menu.mp3') + self.repeats += 1 + + if self.input.buttons.app.middle.pressed: + self.vm.push(SecondScreen(), ViewTransitionSwipeLeft()) + + if self.exiting: + return + + #leds.set_brightness(32 - int(math.cos(self.time) * 32)) + + led = -3 + for col in [RED, (1.0, 1.0, 0.0), BLUE, PUSH_RED, GO_GREEN]: + for i in range(7): + leds.set_rgb(led if led >= 0 else led + 40, *dim(col, -math.cos(self.time) / 2 + 0.5)) + led += 1 + leds.set_rgb(led, 0, 0, 0) + led += 1 + + leds.update() + + def on_enter(self, vm: Optional[ViewManager]) -> None: + super().on_enter(vm) + #self._vm = vm + # Ignore the button which brought us here until it is released + #self.input._ignore_pressed() + media.load(self.path + '/menu.mp3') + self.repeats = 0 + self.time = 0 + self.exiting = False + leds.set_brightness(69) + + def on_exit(self): + super().on_exit() + media.stop() + self.exiting = True + leds.set_all_rgb(0, 0, 0) + leds.set_brightness(69) + leds.update() + + +if __name__ == '__main__': + st3m.run.run_view(App(ApplicationContext())) diff --git a/flow3r.toml b/flow3r.toml new file mode 100644 index 0000000000000000000000000000000000000000..c8d926ce541de5999acf8d8781fae39951a99172 --- /dev/null +++ b/flow3r.toml @@ -0,0 +1,10 @@ +[app] +name = "Petal Hero" +menu = "Music" + +[entry] +class = "App" + +[metadata] +author = "dos" +license = "GPL-3.0-or-later" diff --git a/menu.mp3 b/menu.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4c0d34a490eac2b9eb0cabb571b3b517b37e7132 Binary files /dev/null and b/menu.mp3 differ