diff --git a/tulip/fs/app/voices/voices.py b/tulip/fs/app/voices/voices.py new file mode 100644 index 000000000..2663f7443 --- /dev/null +++ b/tulip/fs/app/voices/voices.py @@ -0,0 +1,240 @@ +# voices.py +# drums.py +# lvgl drum machine for Tulip + +import tulip +import lvgl as lv +import amy +from patches import patches + +def redraw(app): + # draw bg_x stuff, like the piano + (app.screen_w, app.screen_h) = tulip.screen_size() + + # piano size + app.piano_w = app.screen_w - 100 # initial, will get adjusted slightly + app.piano_x = 50 # initial, will get adjusted slightly + app.piano_h = 250 + app.white_key_w = 50 + + # computed + app.piano_y = app.screen_h - app.piano_h + app.black_key_w = int(app.white_key_w/2) + app.black_key_h = int(float(app.piano_h) * (2.0/3.0)) + app.white_keys = int(app.piano_w / app.white_key_w)-1 + app.piano_w = app.white_keys * app.white_key_w # reset + app.piano_x = int( (app.screen_w - app.piano_w)/2) + tulip.bg_rect(app.piano_x, app.piano_y, app.piano_w, app.piano_h, 255, 1) + app.black_idx = [1,2,4,5,6,8,9,11,12,13,15,16,18,19,20,22,23] # etc + for i in range(app.white_keys)[1:]: + white_line_x = app.piano_x+(i*app.white_key_w) + tulip.bg_line(white_line_x, app.piano_y, white_line_x, app.piano_y+app.piano_h, 0) + if i in app.black_idx: + tulip.bg_rect(white_line_x-int(app.white_key_w/4), app.piano_y, app.black_key_w, app.black_key_h, 0, 1) + +class Settings(tulip.UIElement): + def __init__(self, width=350, height=300): + super().__init__() + self.group.set_size(width, height) + self.group.remove_flag(lv.obj.FLAG.SCROLLABLE) + self.label = lv.label(self.group) + self.label.set_text("sequencer and arpeggiator") + self.rect = lv.obj(self.group) + self.rect.set_style_bg_color(tulip.pal_to_lv(9), 0) + self.rect.remove_flag(lv.obj.FLAG.SCROLLABLE) + + self.rect.set_size(width-25,height-20) + self.rect.align_to(self.label,lv.ALIGN.OUT_BOTTOM_LEFT,0,0) + + self.tempo = lv.slider(self.rect) + self.tempo.set_style_bg_opa(lv.OPA.COVER, lv.PART.MAIN) + self.tempo.set_width(180) + self.tempo.set_style_bg_color(tulip.pal_to_lv(255), lv.PART.INDICATOR) + self.tempo.set_style_bg_color(tulip.pal_to_lv(255), lv.PART.MAIN) + self.tempo.set_style_bg_color(tulip.pal_to_lv(129), lv.PART.KNOB) + self.tempo.align_to(self.rect, lv.ALIGN.TOP_LEFT,0,0) + self.tempo_label = lv.label(self.rect) + self.tempo.set_value(int(tulip.seq_bpm() / 2.4),lv.ANIM.OFF) + self.tempo_label.set_text("%d BPM" % (tulip.seq_bpm())) + self.tempo_label.align_to(self.tempo, lv.ALIGN.OUT_RIGHT_MID,10,0) + self.tempo.add_event_cb(self.tempo_cb, lv.EVENT.VALUE_CHANGED, None) + + alabel = lv.label(self.rect) + alabel.set_text("Arpeggiator:") + alabel.align_to(self.tempo, lv.ALIGN.OUT_BOTTOM_LEFT,0,30) + self.arpegg = lv.switch(self.rect) + self.arpegg.set_style_border_width(1, lv.PART.MAIN) + self.arpegg.set_style_border_color(tulip.pal_to_lv(129), 0) + self.arpegg.align_to(alabel, lv.ALIGN.OUT_RIGHT_MID,10,0) + self.arpegg.add_event_cb(self.arpegg_cb, lv.EVENT.VALUE_CHANGED, None) + hlabel = lv.label(self.rect) + hlabel.set_text("Hold:") + hlabel.align_to(self.arpegg, lv.ALIGN.OUT_RIGHT_MID,10,0) + self.hold = lv.switch(self.rect) + self.hold.set_style_border_width(1, lv.PART.MAIN) + self.hold.set_style_border_color(tulip.pal_to_lv(129), 0) + self.hold.align_to(hlabel, lv.ALIGN.OUT_RIGHT_MID,10,0) + self.hold.add_event_cb(self.hold_cb, lv.EVENT.VALUE_CHANGED, None) + + self.mode = ListColumn("mode", ["Up", "Down", "U&D", "Rand"], width=130, height=160, selected=0) + self.mode.group.set_parent(self.rect) + self.mode.group.set_style_bg_color(tulip.pal_to_lv(9),0) + self.mode.group.align_to(alabel, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 20) + self.range = ListColumn("range", ["1", "2", "3"], width=130, height=160, selected=0) + self.range.group.set_parent(self.rect) + self.range.group.set_style_bg_color(tulip.pal_to_lv(9),0) + self.range.group.align_to(self.mode.group, lv.ALIGN.OUT_RIGHT_TOP, 10, 0) + + def tempo_cb(self,e): + new_bpm = self.tempo.get_value()*2.4 + if(new_bpm < 1.0): new_bpm = 1 + tulip.seq_bpm(new_bpm) + self.tempo_label.set_text("%d BPM" % (tulip.seq_bpm())) + def hold_cb(self,e): + if(self.hold.get_state()==3): + self.arpegg_hold = True + else: + self.arpegg_hold = False + def arpegg_cb(self,e): + if(self.arpegg.get_state()==3): + self.arpegg_on = True + else: + self.arpegg_on = False + + +class ListColumn(tulip.UIElement): + def __init__(self, name, items=None, selected=None, width=175, height=300): + super().__init__() + self.name = name + self.selected = selected + self.group.set_size(width,height) + self.group.remove_flag(lv.obj.FLAG.SCROLLABLE) + self.label = lv.label(self.group) + self.label.set_text(name) + self.list = lv.list(self.group) + self.list.set_size(width-25,height-20) + self.list.align_to(self.label,lv.ALIGN.OUT_BOTTOM_LEFT,0,0) + self.buttons = [] + tulip.lv_depad(self.list) + tulip.lv_depad(self.group) + tulip.lv_depad(self.label) + self.replace_items(items) + self.default_bg = 36 + if(self.selected is not None): + self.buttons[self.selected].set_style_bg_color(tulip.pal_to_lv(129), 0) + + def replace_items(self, items): + if items is not None: + self.list.clean() + self.buttons = [] + for i in items: + button = self.list.add_button(lv.SYMBOL.PLUS, i) + button.add_event_cb(self.list_cb, lv.EVENT.CLICKED, None) + self.buttons.append(button) + + def list_cb(self, e): + if(self.selected is not None): + self.buttons[self.selected].set_style_bg_color(tulip.pal_to_lv(self.default_bg), 0) + button = e.get_target_obj() + text = button.get_child(1).get_text() + self.selected = button.get_index() + self.buttons[self.selected].set_style_bg_color(tulip.pal_to_lv(129), 0) + if(self.name=='synth'): + update_patches(text) + + print('list_db ' + self.name + ' ' + text) + +def play_note_from_coord(app, x, y): + white_key = int((x-app.piano_x)/app.white_key_w) + if(white_key in app.black_idx and y= app.piano_x and x[i] <= app.piano_x+app.piano_w and y[i] >= app.piano_y and y[i] <= app.piano_y+app.piano_h): + if(not up): + play_note_from_coord(app, x[i], y[i]) + #print("got points up %d : %d,%d %d,%d %d,%d" % (up, points[0], points[1], points[2], points[3], points[4], points[5] )) + +def process_key(key): + global app + # play kb notes from keyboard? + pass + +def step(t): + global app + if(app.redraw_ticks is not None): + app.redraw_ticks = app.redraw_ticks - 1 + if(app.redraw_ticks == 0): + redraw(app) + app.redraw_ticks = None + +def quit(screen): + deactivate(app) + tulip.seq_remove_callback(step) + +def activate(screen): + # Re-draw grid -- for tulip bg_X commands, you have to defer this as LVGL will write for a few frames and would overwrite your BG + # so i just use the sequencer to wait for the next tick and redraw then. adds a tiny bit of lag on activation + app.redraw_ticks = 2 + # start listening to the keyboard again + tulip.keyboard_callback(process_key) + tulip.touch_callback(touch) + +def deactivate(screen): + # i am being switched away -- keep running but clear and close any active callbacks + tulip.bg_clear() + tulip.keyboard_callback() + tulip.touch_callback() + +def update_patches(synth): + global app + if(synth=='DX7'): + app.patches.replace_items(patches[128:256]) + if(synth=='Juno-6'): + app.patches.replace_items(patches[0:128]) + if(synth=='Custom'): + app.patches.replace_items([("Custom %d" % x) for x in range(32)]) + if(synth=='Misc'): + app.patches.replace_items([]) + + app.patches.label.set_text("%s patches" % (synth)) + + +def run(screen): + global app + app = screen # we can use the screen obj passed in as a general "store stuff here" class, as well as inspect the UI + app.set_bg_color(0) + app.offset_y = 25 + app.offset_x = 50 + app.redraw_ticks = None + app.quit_callback = quit + app.activate_callback = activate + app.deactivate_callback = deactivate + tulip.seq_add_callback(step, int(tulip.seq_ppq()/2)) + + app.channels = ListColumn('channel',[str(i+1) for i in range(16)], selected=0, width=100) + app.add(app.channels, direction=lv.ALIGN.OUT_BOTTOM_LEFT) + + app.synths = ListColumn('synth', ["Juno-6", "DX7", "Misc", "Custom"]) + app.add(app.synths) + + app.patches = ListColumn('Patches') + update_patches("Juno-6") + app.add(app.patches) + + app.polyphony = ListColumn('polyphony', [str(x+1) for x in range(6)], width=100) + app.add(app.polyphony) + + app.settings = Settings() + app.add(app.settings) + + app.present() + + diff --git a/tulip/shared/py/patches.py b/tulip/shared/py/patches.py new file mode 100644 index 000000000..e380be960 --- /dev/null +++ b/tulip/shared/py/patches.py @@ -0,0 +1,263 @@ +# patches.py +# maybe one day automated list of patches in AMY + +patches = [ +"A11 Brass Set 1", +"A12 Brass Swell", +"A13 Trumpet", +"A14 Flutes", +"A15 Moving Strings", +"A16 Brass & Strings", +"A17 Choir", +"A18 Piano I", +"A21 Organ I", +"A22 Organ II", +"A23 Combo Organ", +"A24 Calliope", +"A25 Donald Pluck", +"A26 Celeste* (1 oct.up)", +"A27 Elect. Piano I", +"A28 Elect. Piano II", +"A31 Clock Chimes* (1 oct. up)", +"A32 Steel Drums", +"A33 Xylophone", +"A34 Brass III", +"A35 Fanfare", +"A36 String III ", +"A37 Pizzicato", +"A38 High Strings ", +"A41 Bass clarinet", +"A42 English Horn", +"A43 Brass Ensemble", +"A44 Guitar", +"A45 Koto", +"A46 Dark Pluck", +"A47 Funky I", +"A48 Synth Bass I (unison)", +"A51 Lead I", +"A52 Lead II", +"A53 Lead III ", +"A54 Funky II", +"A55 Synth Bass II", +"A56 Funky III", +"A57 Thud Wah", +"A58 Going Up", +"A61 Piano II", +"A62 Clav", +"A63 Frontier Organ ", +"A64 Snare Drum (unison)", +"A65 Tom Toms (unison)", +"A66 Timpani (unison)", +"A67 Shaker", +"A68 Synth Pad ", +"A71 Sweep I", +"A72 Pluck Sweep", +"A73 Repeater ", +"A74 Sweep II ", +"A75 Pluck Bell", +"A76 Dark Synth Piano", +"A77 Sustainer", +"A78 Wah Release", +"A81 Gong (play low chords)", +"A82 Resonance Funk", +"A83 Drum Booms* (1 oct. down)", +"A84 Dust Storm", +"A85 Rocket Men ", +"A86 Hand Claps", +"A87 FX Sweep", +"A88 Caverns ", +"B11 Strings ", +"B12 Violin ", +"B13 Chorus Vibes ", +"B14 Organ 1 ", +"B15 Harpsichord 1 ", +"B16 Recorder", +"B17 Perc. Pluck", +"B18 Noise Sweep", +"B21 Space Chimes ", +"B22 Nylon Guitar ", +"B23 Orchestral Pad ", +"B24 Bright Pluck ", +"B25 Organ Bell", +"B26 Accordion ", +"B27 FX Rise 1 ", +"B28 FX Rise 2", +"B31 Brass ", +"B32 Helicopter", +"B33 Lute ", +"B34 Chorus Funk ", +"B35 Tomita ", +"B36 FX Sweep 1", +"B37 Sharp Reed", +"B38 Bass Pluck", +"B41 Resonant Rise", +"B42 Harpsichord 2", +"B43 Dark Ensemble", +"B44 Contact Wah ", +"B45 Noise Sweep 2 ", +"B46 Glassy Wah", +"B47 Phase Ensemble", +"B48 Chorused Bell", +"B51 Clav", +"B52 Organ 2 ", +"B53 Bassoon ", +"B54 Auto Release Noise Sweep ", +"B55 Brass Ensemble ", +"B56 Ethereal", +"B57 Chorus Bell 2", +"B58 Blizzard ", +"B61 E. Piano with Tremolo", +"B62 Clarinet", +"B63 Thunder", +"B64 Reedy Organ", +"B65 Flute / Horn ", +"B66 Toy Rhodes", +"B67 Surf's Up", +"B68 OW Bass", +"B71 Piccolo", +"B72 Melodic Taps", +"B73 Meow Brass", +"B74 Violin (high)", +"B75 High Bells", +"B76 Rolling Wah ", +"B77 Ping Bell", +"B78 Brassy Organ", +"B81 Low Dark Strings", +"B82 Piccolo Trumpet", +"B83 Cello", +"B84 High Strings", +"B85 Rocket Men", +"B86 Forbidden Planet", +"B87 Froggy ", +"B88 Owgan", +"BRASS 1 ", +"BRASS 2 ", +"BRASS 3 ", +"STRINGS 1 ", +"STRINGS 2 ", +"STRINGS 3 ", +"ORCHESTRA ", +"PIANO 1 ", +"PIANO 2 ", +"PIANO 3 ", +"E.PIANO 1 ", +"GUITAR 1 ", +"GUITAR 2 ", +"SYN-LEAD 1", +"BASS 1 ", +"BASS 2 ", +"E.ORGAN 1 ", +"PIPES 1 ", +"HARPSICH 1", +"CLAV 1 ", +"VIBE 1 ", +"MARIMBA ", +"KOTO ", +"FLUTE 1 ", +"ORCH-CHIME", +"TUB BELLS ", +"STEEL DRUM", +"TIMPANI ", +"REFS WHISL", +"VOICE 1 ", +"TRAIN ", +"TAKE OFF ", +"PIANO 4 ", +"PIANO 5 ", +"E.PIANO 2 ", +"E.PIANO 3 ", +"E.PIANO 4 ", +"PIANO 5THS", +"CELESTE ", +"TOY PIANO ", +"HARPSICH 2", +"HARPSICH 3", +"CLAV 2 ", +"CLAV 3 ", +"E.ORGAN 2 ", +"E.ORGAN 3 ", +"E.ORGAN 4 ", +"E.ORGAN 5 ", +"PIPES 2 ", +"PIPES 3 ", +"PIPES 4 ", +"CALIOPE ", +"ACCORDION ", +"SITAR ", +"GUITAR 3 ", +"GUITAR 4 ", +"GUITAR 5 ", +"GUITAR 6 ", +"LUTE ", +"BANJO ", +"HARP 1 ", +"HARP 2 ", +"BASS 3 ", +"BASS 4 ", +"PICCOLO ", +"FLUTE 2 ", +"OBOE ", +"CLARINET ", +"SAX BC ", +"BASSOON ", +"STRINGS 4 ", +"STRINGS 5 ", +"STRINGS 6 ", +"STRINGS 7 ", +"STRINGS 8 ", +"BRASS 4 ", +"BRASS 5 ", +"BRASS 6 BC", +"BRASS 7 ", +"BRASS 8 ", +"RECORDER ", +"HARMONICA1", +"HRMNCA2 BC", +"VOICE 2 ", +"VOICE 3 ", +"GLOKENSPL ", +"VIBE 2 ", +"XYLOPHONE ", +"CHIMES ", +"GONG 1 ", +"GONG 2 ", +"BELLS ", +"COW BELL ", +"BLOCK ", +"FLEXATONE ", +"LOG DRUM ", +"SYN-LEAD 2", +"SYN-LEAD 3", +"SYN-LEAD 4", +"SYN-LEAD 5", +"SYN-CLAV 1", +"SYN-CLAV 2", +"SYN-CLAV 3", +"SYN-PIANO ", +"SYNBRASS 1", +"SYNBRASS 2", +"SYNORGAN 1", +"SYNORGAN 2", +"SYN-VOX ", +"SYN-ORCH ", +"SYN-BASS 1", +"SYN-BASS 2", +"HARP-FLUTE", +"BELL-FLUTE", +"E.P-BRS BC", +"T.BL-EXPA ", +"CHIME-STRG", +"B.DRM-SNAR", +"SHIMMER ", +"EVOLUTION ", +"WATER GDN ", +"WASP STING", +"LASER GUN ", +"DESCENT ", +"OCTAVE WAR", +"GRAND PRIX", +"ST.HELENS ", +"EXPLOSION " + + +] \ No newline at end of file diff --git a/tulip/shared/py/tulip.py b/tulip/shared/py/tulip.py index d47a45f6a..efbab4d82 100644 --- a/tulip/shared/py/tulip.py +++ b/tulip/shared/py/tulip.py @@ -472,10 +472,9 @@ def run(module_string): # Import the app module and call module.run(screen) exec('import %s' % (module_string)) actual_module = sys.modules[module_string] - try: + if(hasattr(actual_module, 'run')): actual_module.run(screen) - except (AttributeError, TypeError) as e: - # This is a modal style app that doesn't use a screen + else: screen.quit_callback(None) # Save the modules we imported so we can delete them on quit. This saves RAM on MP diff --git a/tulip/shared/py/ui.py b/tulip/shared/py/ui.py index 187adf393..73d866469 100644 --- a/tulip/shared/py/ui.py +++ b/tulip/shared/py/ui.py @@ -33,6 +33,10 @@ def pal_to_lv(pal): (r,g,b) = tulip.rgb(pal, wide=True) # todo -- not sure if we use wide or not return lv.color_make(r,g,b) +# Convert tulip rgb332 pal idx into lv color +def lv_to_pal(lvcolor): + return tulip.color(lvcolor.red, lvcolor.green, lvcolor.blue) + # Remove padding from an LVGL object. Sometimes useful. def lv_depad(obj): obj.set_style_pad_left(0,0)