"""A Sequencer! Annoy your friends! Annoy your enemies! Annoy yourself! Maybe (maybe) make music! """ ___title___ = "Sequencer" ___license___ = "MIT" ___categories___ = ["Sound"] ___dependencies___ = ["speaker", "buttons", "ugfx_helper", "app", "shared/sequencer_info.png"] import ugfx, speaker, ugfx_helper from tilda import Buttons from buttons import * from app import restart_to_default ugfx_helper.init() speaker.enabled(True) rows_per_block = 4 cols_per_block = 3 num_v_blocks = 2 num_h_blocks = 4 total_rows = rows_per_block*num_v_blocks total_cols = cols_per_block*num_h_blocks line_width = 5 row_height = int(ugfx.height() / (rows_per_block*num_v_blocks)) col_width = int(ugfx.width() / (cols_per_block*num_h_blocks)) block_height = row_height*rows_per_block block_width = col_width*cols_per_block active = True notes = [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] active_colour = ugfx.html_color(0x800080) inactive_colour = ugfx.html_color(0xd29cd1) current_active_colour = ugfx.html_color(0x00cf3a) current_inactive_colour = ugfx.html_color(0x88c89a) start_time = time.ticks_ms() time_per_row = 500 # mS previous_current_row = 0 # TODO: find less stupid variable name debounce_time = 100 active_states = [[False for col in range(total_cols)] for row in range(total_rows)] last_pressed = [[time.ticks_ms() for col in range(total_cols)] for row in range(total_rows)] joy_last_pressed = [time.ticks_ms() for col in range(6)] active_v_block = 0 active_h_block = 0 def mode_buttons(): global previous_current_row global active_states global active global start_time global active_v_block global active_h_block print("mode: buttons") coords = { Buttons.BTN_1: (0,0), Buttons.BTN_2: (0,1), Buttons.BTN_3: (0,2), Buttons.BTN_4: (1,0), Buttons.BTN_5: (1,1), Buttons.BTN_6: (1,2), Buttons.BTN_7: (2,0), Buttons.BTN_8: (2,1), Buttons.BTN_9: (2,2), Buttons.BTN_Star: (3,0), Buttons.BTN_0: (3,1), Buttons.BTN_Hash: (3,2), } render_ui() alive = True while alive: ui_changed = False row_changed = False current_row = int((time.ticks_ms() - start_time) / time_per_row) % total_rows if current_row != previous_current_row: previous_current_row = current_row row_changed = True for btn, coord in coords.items(): if is_pressed(btn): row = active_v_block*rows_per_block + coord[0] col = active_h_block*cols_per_block + coord[1] if (last_pressed[row][col] + debounce_time < time.ticks_ms()): last_pressed[row][col] = time.ticks_ms() active_states[row][col] = not active_states[row][col] # only one note per frame if active_states[row][col]: for check_col in range(total_cols): if check_col != col: active_states[row][check_col] = False ui_changed = True break if is_triggered(Buttons.JOY_Center): if (joy_last_pressed[5] + debounce_time < time.ticks_ms()): joy_last_pressed[5] = time.ticks_ms() active = not active if not active: speaker.stop() else: start_time = time.ticks_ms() ui_changed = True if is_triggered(Buttons.JOY_Up): if (joy_last_pressed[0] + debounce_time < time.ticks_ms()): joy_last_pressed[0] = time.ticks_ms() active_v_block -= 1 if active_v_block < 0: active_v_block += num_v_blocks ui_changed = True if is_triggered(Buttons.JOY_Down): if (joy_last_pressed[1] + debounce_time < time.ticks_ms()): joy_last_pressed[1] = time.ticks_ms() active_v_block += 1 active_v_block %= num_v_blocks ui_changed = True if is_triggered(Buttons.JOY_Left): if (joy_last_pressed[2] + debounce_time < time.ticks_ms()): joy_last_pressed[2] = time.ticks_ms() active_h_block -= 1 if active_h_block < 0: active_h_block += num_h_blocks ui_changed = True if is_triggered(Buttons.JOY_Right): if (joy_last_pressed[3] + debounce_time < time.ticks_ms()): joy_last_pressed[3] = time.ticks_ms() active_h_block += 1 active_h_block %= num_h_blocks ui_changed = True if is_triggered(Buttons.BTN_B): if (joy_last_pressed[4] + debounce_time < time.ticks_ms()): joy_last_pressed[4] = time.ticks_ms() for row in range(total_rows): for col in range(total_cols): active_states[row][col] = False ui_changed = True if is_triggered(Buttons.BTN_A): if (joy_last_pressed[5] + debounce_time < time.ticks_ms()): joy_last_pressed[5] = time.ticks_ms() speaker.stop() display_help() ui_changed = True if is_triggered(Buttons.BTN_Menu): break if ui_changed or (active and row_changed): render_ui() if active and row_changed: play_notes(current_row) def render_ui(): ugfx.clear(ugfx.html_color(0xffffff)) # draw squares current_row = int((time.ticks_ms() - start_time) / time_per_row) % total_rows for row in range(total_rows): for col in range(total_cols): colour = inactive_colour if active and row == current_row: if active_states[row][col] == True: colour = current_active_colour else: colour = current_inactive_colour elif active_states[row][col] == True: colour = active_colour ugfx.area(col_width*col + line_width, row_height*row + line_width, col_width - line_width, row_height - line_width, colour) # highlight working area ugfx.area(active_h_block*block_width, active_v_block*block_height, line_width, block_height, ugfx.RED) ugfx.area((active_h_block+1)*block_width, active_v_block*block_height, line_width, block_height, ugfx.RED) ugfx.area(active_h_block*block_width, active_v_block*block_height, block_width, line_width, ugfx.RED) ugfx.area(active_h_block*block_width, (active_v_block+1)*block_height, block_width+line_width, line_width, ugfx.RED) def play_notes(row): note = "" for col in range(total_cols): if active_states[row][col] == True: note = notes[col] if note == "": speaker.stop() else: speaker.stop() speaker.note("{}{}".format(note, 5)) def display_help(): global start_time ugfx.display_image(0, 0, "shared/sequencer_info.png") wait_until = time.ticks_ms() + 5000 while time.ticks_ms() < wait_until: time.sleep(0.1) if Buttons.is_pressed(Buttons.BTN_A) or Buttons.is_pressed(Buttons.BTN_B) or Buttons.is_pressed(Buttons.BTN_Menu): break start_time = time.ticks_ms() display_help() mode_buttons() # Todo: Allow different modes and allow users to switch between them via joystick or something restart_to_default()