#include "rng.c" #include "text.h" #include "assets.c" #include "patterns.c" #include "save.c" #include "drawing.c" #include "clipboard.c" void set_time(int bpm); void clear_pattern(size_t idx) { Pattern *pat = &patterns[idx]; *pat = (Pattern){default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0, false}; for (size_t i = 0; i < 16; i++) { pat->ch1.notes[i] = (TriggerNote){false, NOTE_C_4}; pat->ch2.notes[i] = (TriggerNote){false, NOTE_C_4}; pat->ch3.notes[i] = (TriggerNote){false, NOTE_C_4}; pat->ch4.notes[i] = (TriggerNote){false, NOTE_C_4}; } redraw_pattern_buttons = true; redraw_channels = true; redraw_trigs = true; redraw_bpm = true; redraw_params = true; } void gate_off(void) { SIO_MODE = SIO_MODE_GP | SIO_SC_OUT(1) | SIO_SD_OUT(1) | SIO_SI_OUT(0) | SIO_SO_OUT(1) | SIO_SC(0) | SIO_SD(0) | SIO_SO(0); TIMER_CTRL_3 = 0; } void gate_on(void) { SIO_MODE = SIO_MODE_GP | SIO_SC_OUT(1) | SIO_SD_OUT(1) | SIO_SI_OUT(0) | SIO_SO_OUT(1) | SIO_SC(1) | SIO_SD(0) | SIO_SO(1); int n_ticks = -244181 / 600; irs_set(IRQ_TIMER_3, gate_off); TIMER_DATA_3 = n_ticks; TIMER_CTRL_3 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3; } u8 find_next_pattern(void) { u8 idx = (chain.current + 1) % 16; for (size_t i = 0; i < MAX_CHAIN; i++) { if (chain.active[idx] == 1) { break; } idx = (idx + 1) % 16; } return idx; } u8 find_prev_pattern(void) { u8 idx = chain.current == 0 ? 15 : (chain.current - 1); for (size_t i = 0; i < MAX_CHAIN; i++) { if (chain.active[idx] == 1) { break; } idx = chain.current == 0 ? 15 : (chain.current - 1); } return idx; } bool should_play(u8 prob) { switch (prob) { case PROB_100: { return true; } break; case PROB_80: { return rng16() < 52427; } break; case PROB_60: { return rng16() < 39320; } break; case PROB_40: { return rng16() < 26213; } break; case PROB_20: { return rng16() < 13106; } break; case PROB_FIRST: { return bar_counter == 0; } break; case PROB_NOT_FIRST: { return bar_counter != 0; } break; case PROB_ONE_TWO: { return bar_counter % 2 == 0; } break; case PROB_TWO_TWO: { return bar_counter % 2 == 1; } break; case PROB_ONE_THREE: { return bar_counter % 3 == 0; } break; case PROB_TWO_THREE: { return bar_counter % 3 == 1; } break; case PROB_THREE_THREE: { return bar_counter % 3 == 2; } break; case PROB_ONE_FOUR: { return bar_counter % 4 == 0; } break; case PROB_TWO_FOUR: { return bar_counter % 4 == 1; } break; case PROB_THREE_FOUR: { return bar_counter % 4 == 2; } break; case PROB_FOUR_FOUR: { return bar_counter % 4 == 3; } break; default: break; } return true; } void select_bank(int i) { chain.current = 15; chain.current = find_next_pattern(); current_pattern = 0; next_pattern = 0; next_bank = i; clipboard.type = CLIP_EMPTY; if (settings.auto_save) { save_bank(current_bank); } metadata.current_pattern = current_pattern; metadata.current_bank = i; save_metadata(); if (current_bank != i) { load_bank(i); } current_bank = i; redraw_pattern_buttons = true; redraw_trigs = true; redraw_channels = true; redraw_bank_buttons = true; redraw_bpm = true; } void play_step(void) { static s8 pan[4] = {0}; Pattern *pat = &patterns[current_pattern]; if (current_pattern != next_pattern && step_counter == 15) { current_pattern = next_pattern; redraw_pattern_buttons = true; update_bpm = true; } else if (current_bank != next_bank && step_counter == 15) { select_bank(next_bank); update_bpm = true; } else if (chain.len != 0 && step_counter == 15 && chain.enabled) { redraw_pattern_buttons = true; update_bpm = true; if (!chain.playing) { next_pattern = chain.chain[chain.current]; current_pattern = next_pattern; } else { chain.current = find_next_pattern(); next_pattern = chain.chain[chain.current]; current_pattern = next_pattern; } chain.playing = true; } bool ch1_active = settings.global_mute ? !settings.mutes[0] : pat->ch1.active; if (ch1_active && !pat->empty) { TriggerNote *trig = &pat->ch1.notes[step_counter]; ChannelSquareParams *params = &pat->ch1.params[step_counter]; if (trig->active && should_play(params->prob)) { if (params->sweep_time == 0) { SOUND_SQUARE1_SWEEP = SOUND_SWEEP_DIR(1); } else { SOUND_SQUARE1_SWEEP = SOUND_SWEEP_NUMBER(params->sweep_number) | SOUND_SWEEP_DIR(params->sweep_direction) | SOUND_SWEEP_TIME(params->sweep_time); } SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE1_CTRL = 0; asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(params->env_volume) | SOUND_SQUARE_ENV_TIME(params->env_time) | SOUND_SQUARE_ENV_DIR(params->env_direction) | SOUND_SQUARE_DUTY(params->duty_cycle); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET | sound_rates[trig->note]; SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(params->env_volume) | SOUND_SQUARE_ENV_TIME(params->env_time) | SOUND_SQUARE_ENV_DIR(params->env_direction) | SOUND_SQUARE_DUTY(params->duty_cycle); // Re-initialize the sound after 8 cycles if using sweeps. if (params->sweep_time != 0) { asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET | sound_rates[trig->note]; } pan[0] = params->pan; } } else { SOUND_SQUARE1_SWEEP = SOUND_SWEEP_DIR(1); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE1_CTRL = 0; } bool ch2_active = settings.global_mute ? !settings.mutes[1] : pat->ch2.active; if (ch2_active && !pat->empty) { TriggerNote *trig = &pat->ch2.notes[step_counter]; ChannelSquareParams *params = &pat->ch2.params[step_counter]; if (trig->active && should_play(params->prob)) { SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE2_CTRL = 0; asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE2_CTRL = SOUND_SQUARE_ENV_VOL(params->env_volume) | SOUND_SQUARE_ENV_TIME(params->env_time) | SOUND_SQUARE_ENV_DIR(params->env_direction) | SOUND_SQUARE_DUTY(params->duty_cycle); SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET | sound_rates[trig->note]; SOUND_SQUARE2_CTRL = SOUND_SQUARE_ENV_VOL(params->env_volume) | SOUND_SQUARE_ENV_TIME(params->env_time) | SOUND_SQUARE_ENV_DIR(params->env_direction) | SOUND_SQUARE_DUTY(params->duty_cycle); pan[1] = params->pan; } } else { SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE2_CTRL = 0; } bool ch3_active = settings.global_mute ? !settings.mutes[2] : pat->ch3.active; if (ch3_active && !pat->empty) { TriggerNote *trig = &pat->ch3.notes[step_counter]; ChannelWaveParams *params = &pat->ch3.params[step_counter]; if (trig->active && should_play(params->prob)) { switch (params->wave_mode) { case 0: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, waves[params->shape_a][params->type_a], 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(0); } break; case 1: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy32(SOUND_WAVE_RAM, waves[params->shape_b][params->type_b], 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(1); } break; case 2: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy32(SOUND_WAVE_RAM, waves[params->shape_b][params->type_b], 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, waves[params->shape_a][params->type_a], 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(1) | SOUND_WAVE_BANK_SELECT(0); } break; } SOUND_WAVE_MODE |= SOUND_WAVE_ENABLE; SOUND_WAVE_FREQ = SOUND_FREQ_RESET | sound_rates[trig->note]; switch (params->wave_volume) { case 0: { SOUND_WAVE_CTRL = SOUND_WAVE_MUTE; } break; case 1: { SOUND_WAVE_CTRL = SOUND_WAVE_VOL_25; } break; case 2: { SOUND_WAVE_CTRL = SOUND_WAVE_VOL_50; } break; case 3: { SOUND_WAVE_CTRL = SOUND_WAVE_VOL_75; } break; case 4: { SOUND_WAVE_CTRL = SOUND_WAVE_VOL_100; } break; } pan[2] = params->pan; } else { // NOTE: Maybe in the future we want an AD, but for now this is // probably better than holding the notes. Or perhaps add a hold // mode? SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_CTRL = SOUND_WAVE_MUTE; } } else { SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_CTRL = SOUND_WAVE_MUTE; } bool ch4_active = settings.global_mute ? !settings.mutes[3] : pat->ch4.active; if (ch4_active && !pat->empty) { TriggerNote *trig = &pat->ch4.notes[step_counter]; ChannelNoiseParams *params = &pat->ch4.params[step_counter]; if (trig->active && should_play(params->prob)) { static const u8 div_freq[] = { 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const u8 pre_freq[] = { 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; SOUND_NOISE_CTRL = SOUND_NOISE_ENV_VOL(params->env_volume) | SOUND_NOISE_ENV_TIME(params->env_time) | SOUND_NOISE_ENV_DIR(params->env_direction); SOUND_NOISE_FREQ = SOUND_FREQ_RESET | SOUND_NOISE_PRESTEP_FREQ(pre_freq[trig->note]) | SOUND_NOISE_DIV_FREQ(div_freq[trig->note]) | SOUND_NOISE_COUNTER_STAGE(params->bit_mode) | SOUND_NOISE_TIMED_MODE(0); SOUND_NOISE_CTRL = SOUND_NOISE_ENV_VOL(params->env_volume) | SOUND_NOISE_ENV_TIME(params->env_time) | SOUND_NOISE_ENV_DIR(params->env_direction); } pan[3] = params->pan; } else { SOUND_NOISE_FREQ = SOUND_FREQ_RESET; SOUND_NOISE_CTRL = 0; } switch (settings.sync) { case SYNC_OUT_LINK_16: { gate_on(); } break; case SYNC_OUT_LINK_8: { if (step_counter % 2 == 0) { gate_on(); } } break; case SYNC_OUT_LINK_4: { if (step_counter % 4 == 0) { gate_on(); } } break; case SYNC_OUT_AUDIO_16: { audio_sync_click = true; } break; case SYNC_OUT_AUDIO_8: { if (step_counter % 2 == 0) { audio_sync_click = true; } } break; case SYNC_OUT_AUDIO_4: { if (step_counter % 4 == 0) { audio_sync_click = true; } } break; case SYNC_OUT_LINK_AUDIO_16: { gate_on(); audio_sync_click = true; } break; case SYNC_OUT_LINK_AUDIO_8: { if (step_counter % 2 == 0) { gate_on(); audio_sync_click = true; } } break; case SYNC_OUT_LINK_AUDIO_4: { if (step_counter % 4 == 0) { gate_on(); audio_sync_click = true; } } break; default: break; } // Adjust per-channel panning. switch (settings.sync) { case SYNC_OUT_LINK_AUDIO_4: case SYNC_OUT_LINK_AUDIO_8: case SYNC_OUT_LINK_AUDIO_16: case SYNC_OUT_AUDIO_4: case SYNC_OUT_AUDIO_8: case SYNC_OUT_AUDIO_16: break; default: { SOUND_DMG_MASTER = dmg_stereo_vol(3) | channel_vol(SOUND_SQUARE1, pan[0]) | channel_vol(SOUND_SQUARE2, pan[1]) | channel_vol(SOUND_WAVE, pan[2]) | channel_vol(SOUND_NOISE, pan[3]); } break; } if (step_counter == 15) { bar_counter++; } step_counter = (step_counter + 1) % 16; } void set_time(int bpm) { if (settings.sync == SYNC_IN_LINK) { return; } // The number of ticks of a 1024 cycle clock in a step based on the BPM can // be calculated as: // X bpm -> 60000 / 4 / bpm = Y ms = Ye-3 s // Y ms -> Ye-3 / 59.99e-9 / 1024 = Z ticks // We have to operate on integer values, so the numbers have been // precalculated to `n_ticks = 244181 / bmp` int n_ticks = -244181 / bpm; irs_set(IRQ_TIMER_2, play_step); TIMER_DATA_2 = n_ticks; TIMER_CTRL_2 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3; } TriggerNote * get_current_trig(void) { Pattern *pat = &patterns[pattern_selection_loc]; switch (channel_selection_loc) { case 0: { return &pat->ch1.notes[trig_selection_loc]; } break; case 1: { return &pat->ch2.notes[trig_selection_loc]; } break; case 2: { return &pat->ch3.notes[trig_selection_loc]; } break; case 3: { return &pat->ch4.notes[trig_selection_loc]; } break; } return NULL; } void handle_channel_selection(void) { Pattern *pat = &patterns[pattern_selection_loc]; if (key_tap(KEY_B)) { if (key_hold(KEY_SELECT)) { clipboard_copy(); } else { if (patterns[pattern_selection_loc].empty) { clear_pattern(pattern_selection_loc); } switch (channel_selection_loc) { case 0: { if (settings.global_mute) { settings.mutes[0] ^= 1; } else { pat->ch1.active ^= 1; } } break; case 1: { if (settings.global_mute) { settings.mutes[1] ^= 1; } else { pat->ch2.active ^= 1; } } break; case 2: { if (settings.global_mute) { settings.mutes[2] ^= 1; } else { pat->ch3.active ^= 1; } } break; case 3: { if (settings.global_mute) { settings.mutes[3] ^= 1; } else { pat->ch4.active ^= 1; } } break; } redraw_channels = true; } } else if (key_tap(KEY_A)) { if (key_hold(KEY_SELECT)) { clipboard_paste(); redraw_bpm = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; } else { switch (channel_selection_loc) { case 0: { input_handler = handle_param_selection_ch1; } break; case 1: { input_handler = handle_param_selection_ch2; } break; case 2: { input_handler = handle_param_selection_ch3; } break; case 3: { input_handler = handle_param_selection_ch4; } break; } } redraw_params = true; } else if (key_tap(KEY_L)) { s32 inc = -1; if (key_hold(KEY_SELECT)) { inc = -12; } for (size_t i = 0; i < 16; i++) { TriggerNote *trig; switch (channel_selection_loc) { case 0: { trig = &pat->ch1.notes[i]; } break; case 1: { trig = &pat->ch2.notes[i]; } break; case 2: { trig = &pat->ch3.notes[i]; } break; case 3: { trig = &pat->ch4.notes[i]; } break; default: {trig = &pat->ch1.notes[i]; } break; } trig->note = scale_note(trig->note, inc); } redraw_trigs = true; } else if (key_tap(KEY_R)) { s32 inc = 1; if (key_hold(KEY_SELECT)) { inc = 12; } for (size_t i = 0; i < 16; i++) { TriggerNote *trig; switch (channel_selection_loc) { case 0: { trig = &pat->ch1.notes[i]; } break; case 1: { trig = &pat->ch2.notes[i]; } break; case 2: { trig = &pat->ch3.notes[i]; } break; case 3: { trig = &pat->ch4.notes[i]; } break; default: {trig = &pat->ch1.notes[i]; } break; } trig->note = scale_note(trig->note, inc); } redraw_trigs = true; } if (key_tap(KEY_RIGHT)) { trig_selection_loc = 0; param_selection_loc = 0; input_handler = handle_trigger_selection; redraw_params = true; } else if (key_tap(KEY_LEFT)) { input_handler = handle_pattern_selection; param_selection_loc = 0; redraw_params = true; } else if (key_tap(KEY_UP)) { if (channel_selection_loc == 0) { channel_selection_loc = SEQ_N_CHANNELS - 1; } else { channel_selection_loc = MAX(channel_selection_loc - 1, 0); } param_selection_loc = 0; redraw_trigs = true; redraw_params = true; } else if (key_tap(KEY_DOWN)) { if (channel_selection_loc == SEQ_N_CHANNELS - 1) { channel_selection_loc = 0; } else { channel_selection_loc = MIN(channel_selection_loc + 1, SEQ_N_CHANNELS); } param_selection_loc = 0; redraw_trigs = true; redraw_params = true; } } void stop_sound(void) { play_status = 0; TIMER_CTRL_2 = 0; SOUND_SQUARE1_SWEEP = SOUND_SWEEP_DIR(1); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_NOISE_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; SOUND_NOISE_CTRL = 0; redraw_play_pause = true; redraw_pattern_buttons = true; } void stop_playing(void) { step_counter = 0; chain.current = 15; chain.current = find_next_pattern(); chain.playing = false; bar_counter = 0; stop_sound(); } void toggle_playing(void) { play_status ^= 1; step_counter = 0; chain.current = 15; chain.current = find_next_pattern(); chain.playing = false; bar_counter = 0; if (current_pattern == next_pattern && chain.len > 0) { chain.playing = true; next_pattern = chain.chain[chain.current]; current_pattern = next_pattern; } SOUND_SQUARE1_SWEEP = SOUND_SWEEP_DIR(1); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_NOISE_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; SOUND_NOISE_CTRL = 0; redraw_play_pause = true; redraw_pattern_buttons = true; if (settings.sync == SYNC_IN_LINK) { return; } if ((TIMER_CTRL_2 & TIMER_CTRL_ENABLE) == 0) { if (current_pattern != next_pattern) { current_pattern = next_pattern; redraw_pattern_buttons = true; } if (current_bank != next_bank) { select_bank(next_bank); } if (settings.global_bpm) { set_time(settings.bpm); } else { set_time(patterns[current_pattern].bpm); } play_step(); } else { TIMER_CTRL_2 ^= TIMER_CTRL_ENABLE; } } void pause_playing(void) { play_status ^= 1; SOUND_SQUARE1_SWEEP = SOUND_SWEEP_DIR(1); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_NOISE_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; SOUND_NOISE_CTRL = 0; chain.playing = false; if (current_pattern == next_pattern && chain.len > 0) { chain.playing = true; next_pattern = chain.chain[chain.current]; current_pattern = next_pattern; } redraw_play_pause = true; if (settings.sync == SYNC_IN_LINK) { return; } if ((TIMER_CTRL_2 & TIMER_CTRL_ENABLE) == 0) { if (current_pattern != next_pattern && step_counter == 0) { current_pattern = next_pattern; redraw_pattern_buttons = true; } if (settings.global_bpm) { set_time(settings.bpm); } else { set_time(patterns[current_pattern].bpm); } play_step(); } else { TIMER_CTRL_2 ^= TIMER_CTRL_ENABLE; } } void handle_right_col_selection(void) { if (key_tap(KEY_LEFT)) { switch (right_col_selection_loc) { case R_COL_STOP: case R_COL_BANK_B: case R_COL_BANK_D: case R_COL_BANK_F: { right_col_selection_loc--; } break; case R_COL_BANK_A: case R_COL_BANK_C: case R_COL_BANK_E: case R_COL_SCALE: case R_COL_BPM: case R_COL_SETTINGS: case R_COL_PLAY: { input_handler = handle_trigger_selection; redraw_params = true; } break; } if (right_col_selection_loc == R_COL_PLAY || right_col_selection_loc == R_COL_SETTINGS) { trig_selection_loc = 15; } else { trig_selection_loc = 7; } } else if (key_tap(KEY_RIGHT)) { switch (right_col_selection_loc) { case R_COL_PLAY: case R_COL_BANK_A: case R_COL_BANK_C: case R_COL_BANK_E: { right_col_selection_loc++; } break; case R_COL_BANK_B: case R_COL_BANK_D: case R_COL_BANK_F: case R_COL_SCALE: case R_COL_BPM: case R_COL_SETTINGS: case R_COL_STOP: { input_handler = handle_pattern_selection; } break; } } else if (key_tap(KEY_UP)) { switch (right_col_selection_loc) { case R_COL_BANK_A: { right_col_selection_loc = R_COL_PLAY; } break; case R_COL_BANK_B: { right_col_selection_loc = R_COL_STOP; } break; case R_COL_BANK_C: case R_COL_BANK_D: case R_COL_BANK_E: case R_COL_BANK_F: case R_COL_STOP: case R_COL_SCALE: { right_col_selection_loc -= 2; } break; default: { right_col_selection_loc--; } break; } } else if (key_tap(KEY_DOWN)) { switch (right_col_selection_loc) { case R_COL_BANK_A: case R_COL_BANK_B: case R_COL_BANK_C: case R_COL_BANK_D: case R_COL_BANK_E: { right_col_selection_loc += 2; } break; case R_COL_PLAY: { right_col_selection_loc = R_COL_BANK_A; } break; case R_COL_STOP: { right_col_selection_loc = R_COL_BANK_B; } break; default: { right_col_selection_loc++; } break; } } else if (key_tap(KEY_L)) { switch (right_col_selection_loc) { case R_COL_BPM: { s32 bpm_inc = -1; if (key_pressed(KEY_SELECT)) { bpm_inc = -10; } if (settings.global_bpm) { settings.bpm = CLAMP(settings.bpm + bpm_inc, 10, 300); } else { patterns[pattern_selection_loc].bpm = CLAMP(patterns[pattern_selection_loc].bpm + bpm_inc, 10, 300); } if ((TIMER_CTRL_2 & TIMER_CTRL_ENABLE) != 0 && current_pattern == pattern_selection_loc) { if (settings.global_bpm) { set_time(settings.bpm); } else { set_time(patterns[current_pattern].bpm); } } redraw_bpm = true; if (patterns[pattern_selection_loc].empty) { clear_pattern(pattern_selection_loc); } } break; case R_COL_SCALE: { if (key_hold(KEY_SELECT)) { current_scale_root--; if (current_scale_root < 0) { current_scale_root = 11; } } else { current_scale--; if (current_scale < 0) { current_scale = SCALE_NUM - 1; } redraw_scale = true; } } break; } } else if (key_tap(KEY_R)) { switch (right_col_selection_loc) { case R_COL_BPM: { s32 bpm_inc = 1; if (key_pressed(KEY_SELECT)) { bpm_inc = 10; } if (settings.global_bpm) { settings.bpm = CLAMP(settings.bpm + bpm_inc, 10, 300); } else { patterns[pattern_selection_loc].bpm = CLAMP(patterns[pattern_selection_loc].bpm + bpm_inc, 10, 300); } if ((TIMER_CTRL_2 & TIMER_CTRL_ENABLE) != 0 && current_pattern == pattern_selection_loc) { if (settings.global_bpm) { set_time(settings.bpm); } else { set_time(patterns[current_pattern].bpm); } } redraw_bpm = true; if (patterns[pattern_selection_loc].empty) { clear_pattern(pattern_selection_loc); } } break; case R_COL_SCALE: { if (key_hold(KEY_SELECT)) { current_scale_root = (current_scale_root + 1) % 12; } else { current_scale++; if (current_scale >= SCALE_NUM) { current_scale = 0; } redraw_scale = true; } } break; } } else if (key_tap(KEY_B)) { switch (right_col_selection_loc) { case R_COL_SETTINGS: { next_scene = SCENE_SETTINGS; } break; // case R_COL_SCALE: { stop_playing(); } break; // TODO: Should scale be toggleable? case R_COL_STOP: { stop_playing(); } break; case R_COL_PLAY: { pause_playing(); } break; case R_COL_BANK_A: { if (play_status) { next_bank = 0; } else { select_bank(0); } redraw_bank_buttons = true; } break; case R_COL_BANK_B: { if (play_status) { next_bank = 1; } else { select_bank(1); } redraw_bank_buttons = true; } break; case R_COL_BANK_C: { if (play_status) { next_bank = 2; } else { select_bank(2); } redraw_bank_buttons = true; } break; case R_COL_BANK_D: { if (play_status) { next_bank = 3; } else { select_bank(3); } redraw_bank_buttons = true; } break; case R_COL_BANK_E: { if (play_status) { next_bank = 4; } else { select_bank(4); } redraw_bank_buttons = true; } break; case R_COL_BANK_F: { if (play_status) { next_bank = 5; } else { select_bank(5); } redraw_bank_buttons = true; } break; } } } void handle_pattern_chain(void) { if (key_tap(KEY_A)) { input_handler = handle_pattern_selection; } else if (key_tap(KEY_LEFT)) { if (param_selection_loc == 8) { param_selection_loc = 15; } else if (param_selection_loc == 16) { param_selection_loc = 18; } else if (param_selection_loc > 0) { param_selection_loc--; } else if (param_selection_loc == 0) { param_selection_loc = 7; } } else if (key_tap(KEY_RIGHT)) { if (param_selection_loc < 15 && param_selection_loc != 7) { param_selection_loc++; } else if (param_selection_loc < 18 && param_selection_loc >= 16) { param_selection_loc++; } else if (param_selection_loc == 18) { param_selection_loc = 16; } else if (param_selection_loc == 7) { param_selection_loc = 0; } else if (param_selection_loc == 15) { param_selection_loc = 8; } } else if (key_tap(KEY_UP)) { if (param_selection_loc >= 1 && param_selection_loc <= 2) { param_selection_loc = 16; } else if (param_selection_loc >= 3 && param_selection_loc <= 4) { param_selection_loc = 17; } else if (param_selection_loc >= 5 && param_selection_loc <= 6) { param_selection_loc = 18; } else if (param_selection_loc >= 8 && param_selection_loc <= 15){ param_selection_loc -= 8; } } else if (key_tap(KEY_DOWN)) { if (param_selection_loc <= 7) { param_selection_loc += 8; } else if (param_selection_loc == 16){ param_selection_loc = 1; } else if (param_selection_loc == 17){ param_selection_loc = 3; } else if (param_selection_loc == 18){ param_selection_loc = 5; } } else if (key_tap(KEY_B)) { switch (param_selection_loc) { case CHAIN_BTN_ENABLE: { chain.enabled ^= 1; chain.playing ^= 1; } break; case CHAIN_BTN_CLEAR: { chain.len = 0; chain.playing = false; chain.enabled = 0; for (size_t i = 0; i < MAX_CHAIN; i++) { chain.active[i] = false; chain.chain[i] = 0; } chain.current = 15; chain.current = find_next_pattern(); } break; case CHAIN_BTN_RANDOM: { u8 cur = chain.current % 16; chain.len = 0; for (size_t i = 0; i < MAX_CHAIN; i++) { if (i == cur && chain.playing) { chain.len++; continue; } if (rng16() < 32765) { u16 idx = 0; for (size_t i = 0; i < 8; i++) { u16 val = rng16(); if (val < 1 * 65530 / 8 && !patterns[0].empty) { idx = 0; break; } if (val < 2 * 65530 / 8 && !patterns[1].empty) { idx = 1; break; } if (val < 3 * 65530 / 8 && !patterns[2].empty) { idx = 2; break; } if (val < 4 * 65530 / 8 && !patterns[3].empty) { idx = 3; break; } if (val < 5 * 65530 / 8 && !patterns[4].empty) { idx = 4; break; } if (val < 6 * 65530 / 8 && !patterns[5].empty) { idx = 5; break; } if (val < 7 * 65530 / 8 && !patterns[6].empty) { idx = 6; break; } if (val < 8 * 65530 / 8 && !patterns[7].empty) { idx = 7; break; } } chain.chain[i] = idx; if (!chain.playing && chain.len == 0) { chain.chain[i] = current_pattern; chain.current = current_pattern; } chain.active[i] = true; chain.len++; } else { chain.active[i] = false; } } // Make sure at least one chain step is set. if (chain.len == 0) { chain.chain[0] = current_pattern; chain.active[0] = true; chain.len++; } if (!chain.playing) { chain.current = 15; chain.current = find_next_pattern(); } chain.enabled = 1; chain.playing = true; } break; default: { if (chain.active[param_selection_loc]) { // Can't toggle current chain. if (!play_status || param_selection_loc != chain.current || chain.len == 1) { chain.active[param_selection_loc] = 0; chain.len--; } } else { if (chain.len == 0) { chain.current = param_selection_loc; } chain.active[param_selection_loc] = 1; chain.len++; } } break; } } else if (key_tap(KEY_L)) { if (chain.active[param_selection_loc] && chain.chain[param_selection_loc] > 0 && param_selection_loc <= 15 && (!play_status || param_selection_loc != chain.current)) { chain.chain[param_selection_loc]--; } } else if (key_tap(KEY_R)) { if (chain.active[param_selection_loc] && chain.chain[param_selection_loc] < 7 && param_selection_loc <= 15 && (!play_status || param_selection_loc != chain.current)) { chain.chain[param_selection_loc]++; } } } void handle_pattern_selection(void) { if (key_hold(KEY_SELECT)) { if ((key_pressed(KEY_L) && key_tap(KEY_R)) || (key_pressed(KEY_R) && key_tap(KEY_L))) { patterns[pattern_selection_loc].empty ^= 1; redraw_pattern_buttons = true; redraw_channels = true; redraw_trigs = true; } if (key_tap(KEY_B)) { clipboard_copy(); } if (key_tap(KEY_A)) { if (patterns[pattern_selection_loc].empty) { clear_pattern(pattern_selection_loc); } clipboard_paste(); redraw_bpm = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; } return; } if (key_tap(KEY_B)) { next_pattern = pattern_selection_loc; redraw_pattern_buttons = true; } if (key_tap(KEY_A)) { input_handler = handle_pattern_chain; } if (key_tap(KEY_RIGHT)) { param_selection_loc = 0; input_handler = handle_channel_selection; redraw_params = true; } else if (key_tap(KEY_UP)) { if (pattern_selection_loc > 0) { pattern_selection_loc = pattern_selection_loc - 1; } else { pattern_selection_loc = 7; } redraw_channels = true; redraw_trigs = true; redraw_bpm = true; } else if (key_tap(KEY_DOWN)) { if (pattern_selection_loc < 7) { pattern_selection_loc = pattern_selection_loc + 1; } else { pattern_selection_loc = 0; } redraw_channels = true; redraw_trigs = true; redraw_bpm = true; } if (key_tap(KEY_LEFT)) { redraw_params = true; input_handler = handle_right_col_selection; param_selection_loc = 0; right_col_selection_loc = R_COL_BPM; switch (pattern_selection_loc) { case 0: { right_col_selection_loc = R_COL_BANK_B; } break; case 1: { right_col_selection_loc = R_COL_BANK_D; } break; case 2: { right_col_selection_loc = R_COL_BANK_F; } break; case 3: { right_col_selection_loc = R_COL_SCALE; } break; case 4: { right_col_selection_loc = R_COL_SCALE; } break; case 5: { right_col_selection_loc = R_COL_BPM; } break; case 6: { right_col_selection_loc = R_COL_SETTINGS; } break; case 7: { right_col_selection_loc = R_COL_STOP; } break; } } if (key_tap(KEY_R)) { // Find next empty slot. s16 slot = -1; for (size_t i = 0; i < 16; i++) { if (chain.active[i] == 0) { slot = i; break; } } if (slot > -1) { if (chain.len == 0) { chain.current = slot; } chain.chain[slot] = pattern_selection_loc; chain.active[slot] = 1; chain.enabled = 1; chain.len++; } } if (key_tap(KEY_L)) { // Find last active slot. s16 slot = -1; bool update_current = false; for (size_t i = 0; i < 16; i++) { size_t idx = 15 - i; if (chain.active[idx] == 1) { // If the pattern is currently playing it can't be removed. if (play_status && idx == chain.current && chain.len > 1) { continue; } else if (idx == chain.current) { update_current = true; } slot = idx; break; } } if (slot > -1) { chain.active[slot] = 0; chain.len--; if (update_current) { chain.current = find_prev_pattern(); } } if (chain.len == 0) { chain.enabled = 0; } } } bool set_param_selection_sq1(ChannelSquareParams *params, InputHandler return_handler) { // Go back to trigger selection. if (key_released(KEY_A)) { input_handler = return_handler; redraw_params = true; return false; } // Cursor movement. if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { if (key_tap(KEY_RIGHT)) { if (param_selection_loc == 4) { param_selection_loc = 0; } else if (param_selection_loc == 9) { param_selection_loc = 5; } else if (param_selection_loc == 7) { param_selection_loc = 9; } else if (param_selection_loc < 7) { param_selection_loc++; } } else { if (param_selection_loc == 0) { param_selection_loc = 4; } else if (param_selection_loc == 5) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 7; } else if (param_selection_loc > 0) { param_selection_loc--; } } redraw_params = true; return false; } if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { if (param_selection_loc == 4) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 4; } else if (param_selection_loc < 3) { param_selection_loc += 5; } else if (param_selection_loc > 4) { param_selection_loc -= 5; } redraw_params = true; return false; } // Adjust parameter. if (key_tap(KEY_R) || key_tap(KEY_L)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } switch (param_selection_loc) { case 0: { params->duty_cycle = CLAMP(params->duty_cycle + inc, 0, 3); } break; case 1: { params->env_volume = CLAMP(params->env_volume + inc, 0, 15); } break; case 2: { if (params->env_time == 7 && inc > 0) { params->env_time = 0; } else if (params->env_time == 0 && inc < 0) { params->env_time = 7; } else if (params->env_time != 0) { params->env_time = CLAMP(params->env_time + inc, 1, 7); } } break; case 3: { params->env_direction ^= 1; } break; case 4: { params->prob = CLAMP(params->prob + inc * -1, 0, PROB_NUM - 1); } break; case 5: { params->sweep_number = CLAMP(params->sweep_number + inc, 0, 7); } break; case 6: { params->sweep_time = CLAMP(params->sweep_time + inc, 0, 7); } break; case 7: { params->sweep_direction ^= 1; } break; case 9: { params->pan = CLAMP(params->pan + inc, -1, 1); } break; } redraw_params = true; return true; } return false; } bool set_param_selection_sq2(ChannelSquareParams *params, InputHandler return_handler) { // Go back to trigger selection. if (key_released(KEY_A)) { input_handler = return_handler; redraw_params = true; return false; } // Cursor movement. if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { if (key_tap(KEY_RIGHT)) { if (param_selection_loc == 4) { param_selection_loc = 0; } else if (param_selection_loc == 7) { param_selection_loc = 5; } else if (param_selection_loc < 7) { param_selection_loc++; } } else { if (param_selection_loc == 0) { param_selection_loc = 4; } else if (param_selection_loc == 5) { param_selection_loc = 7; } else if (param_selection_loc > 0 && param_selection_loc != 9) { param_selection_loc--; } } redraw_params = true; return false; } if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { if (param_selection_loc == 4) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 4; } redraw_params = true; return false; } // Adjust parameter. if (key_tap(KEY_R) || key_tap(KEY_L)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } switch (param_selection_loc) { case 0: { params->duty_cycle = CLAMP(params->duty_cycle + inc, 0, 3); } break; case 1: { params->env_volume = CLAMP(params->env_volume + inc, 0, 15); } break; case 2: { if (params->env_time == 7 && inc > 0) { params->env_time = 0; } else if (params->env_time == 0 && inc < 0) { params->env_time = 7; } else if (params->env_time != 0) { params->env_time = CLAMP(params->env_time + inc, 1, 7); } } break; case 3: { params->env_direction ^= 1; } break; case 4: { params->prob = CLAMP(params->prob + inc * -1, 0, PROB_NUM - 1); } break; case 9: { params->pan = CLAMP(params->pan + inc, -1, 1); } break; } redraw_params = true; return true; } return false; } bool set_param_selection_wave(ChannelWaveParams *params, InputHandler return_handler) { // Go back to trigger selection. if (key_released(KEY_A)) { input_handler = return_handler; redraw_params = true; return false; } // Cursor movement. if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { if (key_tap(KEY_RIGHT)) { if (param_selection_loc == 4) { param_selection_loc = 0; } else if (param_selection_loc == 6) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 5; } else if (param_selection_loc < 6) { param_selection_loc++; } } else { if (param_selection_loc == 0) { param_selection_loc = 4; } else if (param_selection_loc == 5) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 6; } else if (param_selection_loc > 0) { param_selection_loc--; } } redraw_params = true; return false; } if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { if (param_selection_loc == 4) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 4; } else if (param_selection_loc < 2) { param_selection_loc += 5; } else if (param_selection_loc > 4) { param_selection_loc -= 5; } redraw_params = true; return false; } // Adjust parameter. if (key_tap(KEY_R) || key_tap(KEY_L)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } switch (param_selection_loc) { case 0: { params->shape_a = CLAMP(params->shape_a + inc, 0, WAVE_MAX - 1); } break; case 1: { params->type_a = CLAMP(params->type_a + inc, 0, WAVE_VARS - 1); } break; case 2: { params->shape_b = CLAMP(params->shape_b + inc, 0, WAVE_MAX - 1); } break; case 3: { params->type_b = CLAMP(params->type_b + inc, 0, WAVE_VARS - 1); } break; case 4: { params->prob = CLAMP(params->prob + inc * -1, 0, PROB_NUM - 1); } break; case 5: { params->wave_mode = CLAMP(params->wave_mode + inc, 0, 2); } break; case 6: { params->wave_volume = CLAMP(params->wave_volume + inc, 0, 4); } break; case 9: { params->pan = CLAMP(params->pan + inc, -1, 1); } break; } redraw_params = true; return true; } return false; } bool set_param_selection_noise(ChannelNoiseParams *params, InputHandler return_handler) { // Go back to trigger selection. if (key_released(KEY_A)) { input_handler = return_handler; redraw_params = true; return false; } // Cursor movement. if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { if (key_tap(KEY_RIGHT)) { if (param_selection_loc == 4) { param_selection_loc = 0; } else if (param_selection_loc == 7) { param_selection_loc = 5; } else if (param_selection_loc < 7) { param_selection_loc++; } } else { if (param_selection_loc == 0) { param_selection_loc = 4; } else if (param_selection_loc == 5) { param_selection_loc = 7; } else if (param_selection_loc > 0 && param_selection_loc != 9) { param_selection_loc--; } } redraw_params = true; return false; } if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { if (param_selection_loc == 4) { param_selection_loc = 9; } else if (param_selection_loc == 9) { param_selection_loc = 4; } redraw_params = true; return false; } // Adjust parameter. if (key_tap(KEY_R) || key_tap(KEY_L)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } switch (param_selection_loc) { case 0: { params->bit_mode = CLAMP(params->bit_mode + inc, 0, 1); } break; case 1: { params->env_volume = CLAMP(params->env_volume + inc, 0, 15); } break; case 2: { if (params->env_time == 7 && inc > 0) { params->env_time = 0; } else if (params->env_time == 0 && inc < 0) { params->env_time = 7; } else if (params->env_time != 0) { params->env_time = CLAMP(params->env_time + inc, 1, 7); } } break; case 3: { params->env_direction ^= 1; } break; case 4: { params->prob = CLAMP(params->prob + inc * -1, 0, PROB_NUM - 1); } break; case 9: { params->pan = CLAMP(params->pan + inc, -1, 1); } break; } redraw_params = true; return true; } return false; } void handle_param_selection_ch1() { Pattern *pat = &patterns[pattern_selection_loc]; if (set_param_selection_sq1(&ch1_params, handle_channel_selection)) { for (size_t i = 0; i < 16; i++) { pat->ch1.params[i] = ch1_params; } } } void handle_param_selection_ch2() { Pattern *pat = &patterns[pattern_selection_loc]; if (set_param_selection_sq2(&ch2_params, handle_channel_selection)) { for (size_t i = 0; i < 16; i++) { pat->ch2.params[i] = ch2_params; } } } void handle_param_selection_ch3() { Pattern *pat = &patterns[pattern_selection_loc]; if (set_param_selection_wave(&ch3_params, handle_channel_selection)) { for (size_t i = 0; i < 16; i++) { pat->ch3.params[i] = ch3_params; } } } void handle_param_selection_ch4() { Pattern *pat = &patterns[pattern_selection_loc]; if (set_param_selection_noise(&ch4_params, handle_channel_selection)) { for (size_t i = 0; i < 16; i++) { pat->ch4.params[i] = ch4_params; } } } void handle_param_selection_sq1() { ChannelSquareParams *params = &patterns[pattern_selection_loc].ch1.params[trig_selection_loc]; set_param_selection_sq1(params, handle_trigger_selection); } void handle_param_selection_sq2() { ChannelSquareParams *params = &patterns[pattern_selection_loc].ch2.params[trig_selection_loc]; set_param_selection_sq2(params, handle_trigger_selection); } void handle_param_selection_wave() { ChannelWaveParams *params = &patterns[pattern_selection_loc].ch3.params[trig_selection_loc]; set_param_selection_wave(params, handle_trigger_selection); } void handle_param_selection_noise() { ChannelNoiseParams *params = &patterns[pattern_selection_loc].ch4.params[trig_selection_loc]; set_param_selection_noise(params, handle_trigger_selection); } void handle_trigger_selection(void) { TriggerNote *trig = get_current_trig(); bool empty = patterns[pattern_selection_loc].empty; if (key_tap(KEY_B)) { if (key_hold(KEY_SELECT)) { clipboard_copy(); } else { if (empty) { clear_pattern(pattern_selection_loc); } trig->active ^= 1; redraw_trigs = true; } } else if (key_tap(KEY_L)) { s32 inc = -1; if (key_hold(KEY_SELECT)) { inc = -12; } // Decrease note. if (trig->active && !empty) { trig->note = scale_note(trig->note, inc); } redraw_trigs = true; } else if (key_tap(KEY_R)) { s32 inc = 1; if (key_hold(KEY_SELECT)) { inc = 12; } // Increase note. if (trig->active && !empty) { trig->note = scale_note(trig->note, inc); } redraw_trigs = true; } // Move trigger cursor. if (key_tap(KEY_LEFT)) { if (trig_selection_loc == 0 || trig_selection_loc == 8) { // We are at the boundary, switch to channel selection. input_handler = handle_channel_selection; } else { trig_selection_loc = MAX(trig_selection_loc - 1, 0); } redraw_params = true; } else if (key_tap(KEY_RIGHT)) { if (trig_selection_loc != 7 && trig_selection_loc != 15) { trig_selection_loc = MIN(trig_selection_loc + 1, 15); } else if (trig_selection_loc == 7) { input_handler = handle_right_col_selection; right_col_selection_loc = R_COL_BPM; } else if (trig_selection_loc == 15) { right_col_selection_loc = R_COL_PLAY; input_handler = handle_right_col_selection; } redraw_params = true; } else if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { trig_selection_loc = (trig_selection_loc + 8) % 16; redraw_params = true; } else if (key_tap(KEY_A)) { if (key_hold(KEY_SELECT)) { if (patterns[pattern_selection_loc].empty) { clear_pattern(pattern_selection_loc); } clipboard_paste(); redraw_bpm = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; } else { // Switch to parameter selection. switch (channel_selection_loc) { case 0: { input_handler = handle_param_selection_sq1; } break; case 1: { input_handler = handle_param_selection_sq2; } break; case 2: { input_handler = handle_param_selection_wave; } break; case 3: { input_handler = handle_param_selection_noise; } break; } redraw_params = true; } } } void send_notif(char *msg) { if (msg == NULL) { return; } char buf[32] = {0}; size_t n_chars = 0; for (size_t i = 0; i < 32; i++) { if (*msg == '\0') { break; } buf[i] = *msg++; n_chars++; } if (n_chars == 0) { return; } notif.time = NOTIF_TIME; for (size_t i = 0; i < 32; i++) { notif.msg[i] = buf[i]; } } void handle_sequencer_input(void) { if (key_tap(KEY_START)) { if (key_hold(KEY_SELECT)) { save_bank(current_bank); save_metadata(); send_notif("SAVED BANK"); return; } // Stop the sequencer or start playing from the beginning. toggle_playing(); } else if (key_hold(KEY_SELECT)) { if (input_handler == handle_param_selection_sq1 || input_handler == handle_param_selection_sq2 || input_handler == handle_param_selection_wave || input_handler == handle_param_selection_noise) { clipboard_copy(); } input_handler(); } else { input_handler(); } } void serial_irq() { if (play_status) { play_step(); } } void sequencer_init(void) { stop_playing(); // Load the previous bank from SRAM or initialize it if needed. sram_read(&metadata, 0, sizeof(Metadata)); if (metadata.initialized != SAVE_INIT_MAGIC) { for (size_t i = 0; i < KB(64) / 8; ++i) { SRAM[i] = 0x00; } dma_fill(&metadata, 0, sizeof(Metadata), 3); metadata.initialized = SAVE_INIT_MAGIC; save_metadata(); save_bank(0); save_bank(1); save_bank(2); save_bank(3); save_bank(4); save_bank(5); } else { current_bank = metadata.current_bank; next_bank = metadata.current_bank; current_pattern = metadata.current_pattern; next_pattern = metadata.current_pattern; pattern_selection_loc = current_pattern; settings = metadata.settings; load_bank(metadata.current_bank); } swap_palette(settings.theme); // Initialize input handler. input_handler = handle_trigger_selection; // Initialize sound system. SOUND_STATUS = SOUND_ENABLE; init_dsound(); set_audio_settings(); }