From 69d5fce84aad37b0cc05c39a57988b3180e06597 Mon Sep 17 00:00:00 2001 From: Cass May Date: Sun, 2 Sep 2018 15:11:44 +0100 Subject: [PATCH] Add sequencer app --- sequencer/main.py | 220 ++++++++++++++++++++++++++++++++++++++ shared/sequencer_info.png | Bin 0 -> 3396 bytes 2 files changed, 220 insertions(+) create mode 100644 sequencer/main.py create mode 100644 shared/sequencer_info.png diff --git a/sequencer/main.py b/sequencer/main.py new file mode 100644 index 0000000..573242f --- /dev/null +++ b/sequencer/main.py @@ -0,0 +1,220 @@ +"""A Sequencer! + +Annoy your friends! Annoy your enemies! Annoy yourself! Maybe (maybe) make music! +""" + +___name___ = "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 = False + if is_triggered(Buttons.BTN_A): + if (joy_last_pressed[5] + debounce_time < time.ticks_ms()): + joy_last_pressed[5] = time.ticks_ms() + 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() diff --git a/shared/sequencer_info.png b/shared/sequencer_info.png new file mode 100644 index 0000000000000000000000000000000000000000..80cb34e5feae441ede8782fcd46e45a8fd3cdc71 GIT binary patch literal 3396 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