diff --git a/__init__.py b/__init__.py index 63f7ca04081e597efcaef8bdb337dc0f5b44b5b9..baafce5a4d11aa7a1782010cfe98a30e5de4ea9b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,226 +1,47 @@ -import sys -import media, math, random +import media, math 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.ui.view import ViewTransitionSwipeLeft from st3m.application import Application, ApplicationContext import st3m.run import leds +import time +import bl00mbox +# TODO: FIXME +import sys +sys.path.append('/flash/apps/PetalHero') -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) +import song +import flower +import utils - 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): +class PetalHero(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.flower = 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) + self.blm = bl00mbox.Channel("Petal Hero") + + self.in_sound = self.blm.new(bl00mbox.patches.sampler, self.path + "/in.wav") + self.in_sound.signals.output = self.blm.mixer + + self.out_sound = self.blm.new(bl00mbox.patches.sampler, self.path + "/out.wav") + self.out_sound.signals.output = self.blm.mixer + self.crunch_sound = [] + for i in range(3): + self.crunch_sound.append(self.blm.new(bl00mbox.patches.sampler, self.path + "/crunch" + str(i+1) + ".wav")) + self.crunch_sound[i].signals.output = self.blm.mixer + + def draw(self, ctx: Context): ctx.linear_gradient(-120, -120, 120, 120) ctx.add_stop(0.0, [94, 0, 0], 1.0) @@ -263,6 +84,7 @@ class App(Application): 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 @@ -272,7 +94,11 @@ class App(Application): self.repeats += 1 if self.input.buttons.app.middle.pressed: - self.vm.push(SecondScreen(), ViewTransitionSwipeLeft()) + self.in_sound.signals.trigger.start() + self.vm.push(song.SongView(self), ViewTransitionSwipeLeft()) + + if self.input.buttons.os.middle.pressed: + self.out_sound.signals.trigger.start() if self.exiting: return @@ -282,14 +108,14 @@ class App(Application): 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)) + leds.set_rgb(led if led >= 0 else led + 40, *utils.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: + def on_enter(self, vm) -> None: super().on_enter(vm) #self._vm = vm # Ignore the button which brought us here until it is released @@ -308,6 +134,5 @@ class App(Application): leds.set_brightness(69) leds.update() - if __name__ == '__main__': - st3m.run.run_view(App(ApplicationContext())) + st3m.run.run_app(PetalHero) diff --git a/crunch1.wav b/crunch1.wav new file mode 100644 index 0000000000000000000000000000000000000000..345b996cd5f711cc6b10bdac550f61ba938b07b8 Binary files /dev/null and b/crunch1.wav differ diff --git a/crunch2.wav b/crunch2.wav new file mode 100644 index 0000000000000000000000000000000000000000..3b1ae8564006dffcf6c659206080ea0d82cc1255 Binary files /dev/null and b/crunch2.wav differ diff --git a/crunch3.wav b/crunch3.wav new file mode 100644 index 0000000000000000000000000000000000000000..cc801005d913221b6c9a385953322d621c459a16 Binary files /dev/null and b/crunch3.wav differ diff --git a/flow3r.toml b/flow3r.toml index c8d926ce541de5999acf8d8781fae39951a99172..f3aba575ef3131ad59b5df87fee0955e0f37cefc 100644 --- a/flow3r.toml +++ b/flow3r.toml @@ -3,7 +3,7 @@ name = "Petal Hero" menu = "Music" [entry] -class = "App" +class = "PetalHero" [metadata] author = "dos" diff --git a/flower.py b/flower.py new file mode 100644 index 0000000000000000000000000000000000000000..29bf1615ef5820a820d5db7db888fc45794a3210 --- /dev/null +++ b/flower.py @@ -0,0 +1,38 @@ +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() diff --git a/in.wav b/in.wav new file mode 100644 index 0000000000000000000000000000000000000000..43307dda201211c4217a42f9a8a51e9e329cdcb0 Binary files /dev/null and b/in.wav differ diff --git a/midireader.py b/midireader.py new file mode 100644 index 0000000000000000000000000000000000000000..8494132352e3b0820a1ef364683873cd851958bb --- /dev/null +++ b/midireader.py @@ -0,0 +1,117 @@ +import midi + +AMAZING_DIFFICULTY = 0 +MEDIUM_DIFFICULTY = 1 +EASY_DIFFICULTY = 2 +SUPAEASY_DIFFICULTY = 3 + +baseNotes = { + 0x60: AMAZING_DIFFICULTY, + 0x54: MEDIUM_DIFFICULTY, + 0x48: EASY_DIFFICULTY, + 0x3c: SUPAEASY_DIFFICULTY +} + +noteMap = {} +for basenote, diff in baseNotes.items(): + for note in range(5): + noteMap[basenote + note] = (diff, note) + +reverseNoteMap = dict([(v, k) for k, v in list(noteMap.items())]) + +class Event: + def __init__(self, length): + self.length = length + +class Note(Event): + def __init__(self, number, length, special = False, tappable = False): + super().__init__(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())) diff --git a/out.wav b/out.wav new file mode 100644 index 0000000000000000000000000000000000000000..03396dcd839a5165d4be59c67018e648fea85fd5 Binary files /dev/null and b/out.wav differ diff --git a/song.py b/song.py new file mode 100644 index 0000000000000000000000000000000000000000..f799c0ff020a69ecd8fd30173e499e0054fcf020 --- /dev/null +++ b/song.py @@ -0,0 +1,35 @@ +from st3m.input import InputController +from st3m.ui.view import BaseView, ViewManager, ViewTransitionSwipeLeft +from st3m.application import Application, ApplicationContext +import media + +import midi +import midireader + +class SongView(BaseView): + def __init__(self, app): + super().__init__() + self.app = app + midiIn = midi.MidiInFile(midireader.MidiReader(None), self.app.path + '/notes.mid') + midiIn.read() + + 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') + + def on_exit(self): + super().on_exit() + self.app.out_sound.signals.trigger.start() diff --git a/start.mp3 b/start.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8968a6911ccdd522fbfdfc48c73ece8362a924bf Binary files /dev/null and b/start.mp3 differ diff --git a/utils.py b/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..54289b98b81d52b967a9ccccd3db187e19529156 --- /dev/null +++ b/utils.py @@ -0,0 +1 @@ +dim = lambda x, y: tuple(map(lambda x: x * y, x))