#include "rng.c" #include "text.h" #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) { gate_off(); 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) { next_bank = i; clipboard.type = CLIP_EMPTY; if (settings.auto_save) { save_bank(current_bank); } if (current_bank != i) { load_bank(i); } chain.current = 15; chain.current = find_next_pattern(); if (chain.len > 0 && chain.enabled) { chain.playing = true; next_pattern = chain.chain[chain.current]; current_pattern = next_pattern; } else { current_pattern = 0; next_pattern = 0; } metadata.current_scale = current_scale; metadata.current_scale_root = current_scale_root; metadata.current_pattern = current_pattern; metadata.current_bank = i; save_metadata(); 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]; bool switch_bank = false; 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) { switch_bank = 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]; ChannelSquare1Params *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); } 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]; ChannelSquare2Params *params = &pat->ch2.params[step_counter]; if (trig->active && should_play(params->prob)) { 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: { wave_target = &waves[params->shape_a][params->type_a]; wave_env_attack = params->wave_attack; wave_env_decay = params->wave_decay; wave_env = WAV_ENV_START; } break; case 1: { wave_target = &waves[params->shape_b][params->type_b]; wave_env_attack = params->wave_attack; wave_env_decay = params->wave_decay; wave_env = WAV_ENV_START; } 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); wave_env = WAV_ENV_OFF; SOUND_WAVE_MODE |= SOUND_WAVE_ENABLE; SOUND_WAVE_FREQ = SOUND_FREQ_RESET | sound_rates[trig->note]; wave_env_ticks = 0; } break; } wave_freq = 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 { 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; } // Adjust per-channel panning. switch (settings.sync) { case SYNC_OUT_AUDIO_12BPQ: case SYNC_OUT_AUDIO_6BPQ: case SYNC_OUT_AUDIO_4BPQ: case SYNC_OUT_AUDIO_2BPQ: case SYNC_OUT_LINK_AUDIO_12BPQ: case SYNC_OUT_LINK_AUDIO_6BPQ: case SYNC_OUT_LINK_AUDIO_4BPQ: case SYNC_OUT_LINK_AUDIO_2BPQ: 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; if (switch_bank) { select_bank(next_bank); update_bpm = true; } } static int nseq_ticks = 0; IWRAM_CODE UNROLL_LOOPS void wave_ad_tick(void) { Wave wave_zero = { 0x77777777, 0x77777777, 0x77777777, 0x77777777, }; const u8 mult_table[16][8] = { {7, 5, 4, 3, 2, 1, 1, 0}, {7, 6, 5, 4, 3, 2, 2, 1}, {7, 6, 5, 4, 4, 3, 2, 2}, {7, 6, 5, 5, 4, 4, 3, 3}, {7, 6, 6, 5, 5, 5, 4, 4}, {7, 7, 6, 6, 6, 5, 5, 5}, {7, 7, 7, 6, 6, 6, 6, 6}, {7, 7, 7, 7, 7, 7, 7, 7}, {7, 7, 7, 8, 8, 8, 8, 8}, {7, 7, 8, 8, 8, 9, 9, 9}, {7, 8, 8, 9, 9, 9, 10, 10}, {7, 8, 9, 9, 10, 10, 11, 11}, {7, 8, 9, 10, 10, 11, 12, 12}, {7, 8, 9, 10, 11, 12, 12, 13}, {7, 9, 10, 11, 12, 13, 13, 14}, {7, 9, 10, 11, 13, 13, 14, 15}, }; env_start: switch (wave_env) { case WAV_ENV_START: { // Calculate all 8 transitions for the current target wave. for (size_t k = 0; k < 8; k++) { for (size_t j = 0; j < 4; j++) { u32 next = 0; u32 prev = (*wave_target)[j]; for (size_t i = 0; i < 8; i++) { u8 val = (prev >> 4 * i) & 0xF; u8 tmp = mult_table[val][k]; next |= (tmp << 4 * i); } current_wave[k][j] = next; } } wave_env_ticks = 0; wave_env_prog = 0; SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy32(SOUND_WAVE_RAM, current_wave[0], 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, current_wave[0], 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(0); SOUND_WAVE_MODE |= SOUND_WAVE_ENABLE; SOUND_WAVE_FREQ = SOUND_FREQ_RESET | wave_freq; if (wave_env_attack == 0) { memcpy32(SOUND_WAVE_RAM, wave_target, 16); SOUND_WAVE_MODE ^= SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, wave_target, 16); SOUND_WAVE_MODE ^= SOUND_WAVE_BANK_SELECT(1); } else { wave_env = WAV_ENV_ATTACK; goto env_start; } if (wave_env_decay == 0) { wave_env = WAV_ENV_OFF; goto env_start; } else { wave_env = WAV_ENV_DECAY; } return; } break; case WAV_ENV_ATTACK: { if (wave_env_ticks++ < wave_env_attack) { return; } wave_env_ticks = 0; // DEBUG: // int x = 50; // int y = 50; // draw_filled_rect(0 + x, 0 + y, 100 + x, 32 + y, COL_BG); // draw_wave_pattern(¤t_wave[wave_env_prog], 0 + x, 0 + y, 1); memcpy32(SOUND_WAVE_RAM, current_wave[wave_env_prog], 16); SOUND_WAVE_MODE ^= SOUND_WAVE_BANK_SELECT(1); if (++wave_env_prog >= 8) { if (wave_env_decay == 0) { wave_env = WAV_ENV_OFF; } else { wave_env = WAV_ENV_DECAY; } wave_env_prog = 0; } } break; case WAV_ENV_DECAY: { if (wave_env_ticks++ < wave_env_decay) { return; } wave_env_ticks = 0; // DEBUG: // int x = 50; // int y = 50; // draw_filled_rect(0 + x, 0 + y, 100 + x, 32 + y, COL_BG); // draw_wave_pattern(¤t_wave[7-wave_env_prog], 0 + x, 0 + y, 1); memcpy32(SOUND_WAVE_RAM, current_wave[7 - wave_env_prog], 16); SOUND_WAVE_MODE ^= SOUND_WAVE_BANK_SELECT(1); if (++wave_env_prog >= 8) { wave_env = WAV_ENV_END; wave_env_prog = 0; } } break; case WAV_ENV_OFF: { if (wave_env_ticks++ == 24) { SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_CTRL = SOUND_WAVE_MUTE; wave_env = WAV_ENV_END; SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy32(SOUND_WAVE_RAM, wave_zero, 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, wave_zero, 16); } return; } break; case WAV_ENV_END: { SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_CTRL = SOUND_WAVE_MUTE; SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy32(SOUND_WAVE_RAM, wave_zero, 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, wave_zero, 16); return; } break; } } void sequencer_tick(void) { switch (settings.sync) { case SYNC_OUT_LINK_96BPQ: { gate_on(); } break; case SYNC_OUT_LINK_48BPQ: { if (sync_ticks++ % 2 == 0) { gate_on(); } } break; case SYNC_OUT_LINK_24BPQ: { if (sync_ticks++ % 4 == 0) { gate_on(); } } break; case SYNC_OUT_LINK_12BPQ: { if (sync_ticks++ % 8 == 0) { gate_on(); } } break; case SYNC_OUT_LINK_6BPQ: { if (sync_ticks++ % 16 == 0) { gate_on(); } } break; case SYNC_OUT_LINK_4BPQ: { if (sync_ticks++ % 24 == 0) { gate_on(); } } break; case SYNC_OUT_LINK_2BPQ: { if (sync_ticks++ % 48 == 0) { gate_on(); } } break; case SYNC_OUT_AUDIO_12BPQ: { if (sync_ticks++ % 8 == 0) { audio_sync_click = true; } } break; case SYNC_OUT_AUDIO_6BPQ: { if (sync_ticks++ % 16 == 0) { audio_sync_click = true; } } break; case SYNC_OUT_AUDIO_4BPQ: { if (sync_ticks++ % 24 == 0) { audio_sync_click = true; } } break; case SYNC_OUT_AUDIO_2BPQ: { if (sync_ticks++ % 48 == 0) { audio_sync_click = true; } } break; case SYNC_OUT_LINK_AUDIO_12BPQ: { if (sync_ticks++ % 8 == 0) { gate_on(); audio_sync_click = true; } } break; case SYNC_OUT_LINK_AUDIO_6BPQ: { if (sync_ticks++ % 16 == 0) { gate_on(); audio_sync_click = true; } } break; case SYNC_OUT_LINK_AUDIO_4BPQ: { if (sync_ticks++ % 24 == 0) { gate_on(); audio_sync_click = true; } } break; case SYNC_OUT_LINK_AUDIO_2BPQ: { if (sync_ticks++ % 48 == 0) { gate_on(); audio_sync_click = true; } } break; default: break; } if (nseq_ticks++ == 0) { play_step(); } if (nseq_ticks == 24) { nseq_ticks = 0; } wave_ad_tick(); } void set_time(int bpm) { if (settings.sync == SYNC_IN_LINK_96BPQ || settings.sync == SYNC_IN_LINK_48BPQ || settings.sync == SYNC_IN_LINK_24BPQ || settings.sync == SYNC_IN_LINK_12BPQ || settings.sync == SYNC_IN_LINK_6BPQ || settings.sync == SYNC_IN_LINK_4BPQ) { return; } // We use a high resolution 96 PPQ clock for sequencing. With the following // formula we can calculate the time (in seconds) based on the PPQ and BPM: // // t = 60 / (PPQ * BPM) // // To translate this timer to a GBA timer running at 64 CPU cycles per tick // we need to divide this by the constant Q = 3.97329943e-06. In theory, the // period of this timer running a a frequency of 262.21kHz should be // Q = 3.815us, but using this number resulted in a less accurate sequencer // compared to the metronome of a DAW (Bitwig). Tuning Q using a japanese // GBA-SP and an Analogue Pocket resulted in the previously mentioned value. // // If we factor out the BPM we can obtain a fixed constant: // // C = 60 / (96 * 3.97329943e-06) = 157300 // // Allowing us to calculate the clock timer as -C/BPM. Note that this is // integer division and we will be losing the fractional part, resulting in // some minor precision differences, but it seems to work well in practice. int n_ticks = -157300 / bpm; irs_set(IRQ_TIMER_2, sequencer_tick); TIMER_DATA_2 = n_ticks; TIMER_CTRL_2 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_1; } 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_retrig(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; } if (channel_selection_loc == 3) { int tmp = current_scale; current_scale = 0; trig->note = scale_note(trig->note, inc); current_scale = tmp; } else { trig->note = scale_note(trig->note, inc); } } redraw_trigs = true; } else if (key_retrig(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; } if (channel_selection_loc == 3) { int tmp = current_scale; current_scale = 0; trig->note = scale_note(trig->note, inc); current_scale = tmp; } else { trig->note = scale_note(trig->note, inc); } } redraw_trigs = true; } if (key_retrig(KEY_RIGHT)) { trig_selection_loc = 0; param_selection_loc = 0; input_handler = handle_trigger_selection; redraw_params = true; } else if (key_retrig(KEY_LEFT)) { input_handler = handle_pattern_selection; param_selection_loc = 0; redraw_params = true; } else if (key_retrig(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_retrig(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) { 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_SQUARE1_CTRL = 0; SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_FREQ = SOUND_FREQ_RESET; SOUND_WAVE_CTRL = 0; SOUND_NOISE_FREQ = SOUND_FREQ_RESET; SOUND_NOISE_CTRL = 0; redraw_play_pause = true; redraw_pattern_buttons = true; } void reset_sequencer(void) { step_counter = 0; nseq_ticks = 0; sync_ticks = 0; chain.current = 15; chain.current = find_next_pattern(); chain.playing = false; bar_counter = 0; } void stop_playing(void) { play_status = 0; reset_sequencer(); stop_sound(); } void toggle_playing(void) { // Toggle play status. play_status ^= 1; redraw_play_pause = true; redraw_pattern_buttons = true; // Make sure fresh playing from a sequence works as expected. if (current_pattern == next_pattern && chain.len > 0 && chain.enabled) { chain.playing = true; next_pattern = chain.chain[chain.current]; current_pattern = next_pattern; } if (settings.sync == SYNC_IN_LINK_96BPQ || settings.sync == SYNC_IN_LINK_48BPQ || settings.sync == SYNC_IN_LINK_24BPQ || settings.sync == SYNC_IN_LINK_12BPQ || settings.sync == SYNC_IN_LINK_4BPQ) { stop_sound(); return; } // Toggle sequencer. if (play_status == 1) { 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); } } else { stop_sound(); } } void handle_right_col_selection(void) { if (key_retrig(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_retrig(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_retrig(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_retrig(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_retrig(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_retrig(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: { toggle_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) { static int previous_loc = 0; if (key_tap(KEY_A)) { input_handler = handle_pattern_selection; } else if (key_retrig(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_retrig(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 >= 0 && param_selection_loc <= 2) { previous_loc = param_selection_loc; param_selection_loc = 16; } else if (param_selection_loc >= 3 && param_selection_loc <= 4) { previous_loc = param_selection_loc; param_selection_loc = 17; } else if (param_selection_loc >= 5 && param_selection_loc <= 7) { previous_loc = param_selection_loc; 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 = previous_loc; } else if (param_selection_loc == 17){ param_selection_loc = previous_loc; } else if (param_selection_loc == 18){ param_selection_loc = previous_loc; } } 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_retrig(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_retrig(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_retrig(KEY_RIGHT)) { param_selection_loc = 0; input_handler = handle_channel_selection; redraw_params = true; } else if (key_retrig(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_retrig(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_retrig(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(ChannelSquare1Params *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_retrig(KEY_LEFT) || key_retrig(KEY_RIGHT)) { if (key_retrig(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_retrig(KEY_R) || key_retrig(KEY_L)) { int inc; if (key_retrig(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(ChannelSquare2Params *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_retrig(KEY_LEFT) || key_retrig(KEY_RIGHT)) { if (key_retrig(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_retrig(KEY_R) || key_retrig(KEY_L)) { int inc; if (key_retrig(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_retrig(KEY_LEFT) || key_retrig(KEY_RIGHT)) { if (key_retrig(KEY_RIGHT)) { if (param_selection_loc == 4) { param_selection_loc = 0; } else if (param_selection_loc == 9) { param_selection_loc = 5; } else { param_selection_loc++; } } else { if (param_selection_loc == 0) { param_selection_loc = 4; } else if (param_selection_loc == 5) { param_selection_loc = 9; } else { param_selection_loc--; } } redraw_params = true; return false; } if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { if (param_selection_loc < 5) { param_selection_loc += 5; } else if (param_selection_loc >= 5) { param_selection_loc -= 5; } redraw_params = true; return false; } // Adjust parameter. if (key_retrig(KEY_R) || key_retrig(KEY_L)) { int inc; if (key_retrig(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 7: { params->wave_attack = CLAMP(params->wave_attack + inc, 0, 16); } break; case 8: { if (params->wave_decay == 16) { if (inc > 0) { params->wave_decay = 0; } else { params->wave_decay--; } } else if (params->wave_decay == 0) { if (inc < 0) { params->wave_decay = 16; } } else { params->wave_decay = CLAMP(params->wave_decay + inc, 1, 16); } } 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_retrig(KEY_LEFT) || key_retrig(KEY_RIGHT)) { if (key_retrig(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_retrig(KEY_R) || key_retrig(KEY_L)) { int inc; if (key_retrig(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++) { switch (param_selection_loc) { case 0: { pat->ch1.params[i].duty_cycle = ch1_params.duty_cycle; } break; case 1: { pat->ch1.params[i].env_volume = ch1_params.env_volume; } break; case 2: { pat->ch1.params[i].env_time = ch1_params.env_time; } break; case 3: { pat->ch1.params[i].env_direction = ch1_params.env_direction; } break; case 4: { pat->ch1.params[i].prob = ch1_params.prob; } break; case 5: { pat->ch1.params[i].sweep_number = ch1_params.sweep_number; } break; case 6: { pat->ch1.params[i].sweep_time = ch1_params.sweep_time; } break; case 7: { pat->ch1.params[i].sweep_direction = ch1_params.sweep_direction; } break; case 9: { pat->ch1.params[i].pan = ch1_params.pan; } break; default: break; } } } } 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++) { switch (param_selection_loc) { case 0: { pat->ch2.params[i].duty_cycle = ch2_params.duty_cycle; } break; case 1: { pat->ch2.params[i].env_volume = ch2_params.env_volume; } break; case 2: { pat->ch2.params[i].env_time = ch2_params.env_time; } break; case 3: { pat->ch2.params[i].env_direction = ch2_params.env_direction; } break; case 4: { pat->ch2.params[i].prob = ch2_params.prob; } break; case 9: { pat->ch2.params[i].pan = ch2_params.pan; } break; default: break; } } } } 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++) { switch (param_selection_loc) { case 0: { pat->ch3.params[i].shape_a = ch3_params.shape_a; } break; case 1: { pat->ch3.params[i].type_a = ch3_params.type_a; } break; case 2: { pat->ch3.params[i].shape_b = ch3_params.shape_b; } break; case 3: { pat->ch3.params[i].type_b = ch3_params.type_b; } break; case 4: { pat->ch3.params[i].prob = ch3_params.prob; } break; case 5: { pat->ch3.params[i].wave_mode = ch3_params.wave_mode; } break; case 6: { pat->ch3.params[i].wave_volume = ch3_params.wave_volume; } break; case 7: { pat->ch3.params[i].wave_attack = ch3_params.wave_attack; } break; case 8: { pat->ch3.params[i].wave_decay = ch3_params.wave_decay; } break; case 9: { pat->ch3.params[i].pan = ch3_params.pan; } break; default: break; } } } } 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++) { switch (param_selection_loc) { case 0: { pat->ch4.params[i].bit_mode = ch4_params.bit_mode; } break; case 1: { pat->ch4.params[i].env_volume = ch4_params.env_volume; } break; case 2: { pat->ch4.params[i].env_time = ch4_params.env_time; } break; case 3: { pat->ch4.params[i].env_direction = ch4_params.env_direction; } break; case 4: { pat->ch4.params[i].prob = ch4_params.prob; } break; case 9: { pat->ch4.params[i].pan = ch4_params.pan; } break; default: break; } } } } void handle_param_selection_sq1() { ChannelSquare1Params *params = &patterns[pattern_selection_loc].ch1.params[trig_selection_loc]; set_param_selection_sq1(params, handle_trigger_selection); } void handle_param_selection_sq2() { ChannelSquare2Params *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 nudge_trigs(int cur_loc, int next_loc) { Pattern *pat = &patterns[pattern_selection_loc]; switch (channel_selection_loc) { case 0: { ChannelSquare1Params cur_params = pat->ch1.params[cur_loc]; TriggerNote cur_trig = pat->ch1.notes[cur_loc]; pat->ch1.params[cur_loc] = pat->ch1.params[next_loc]; pat->ch1.notes[cur_loc] = pat->ch1.notes[next_loc]; pat->ch1.params[next_loc] = cur_params; pat->ch1.notes[next_loc] = cur_trig; } break; case 1: { ChannelSquare2Params cur_params = pat->ch2.params[cur_loc]; TriggerNote cur_trig = pat->ch2.notes[cur_loc]; pat->ch2.params[cur_loc] = pat->ch2.params[next_loc]; pat->ch2.notes[cur_loc] = pat->ch2.notes[next_loc]; pat->ch2.params[next_loc] = cur_params; pat->ch2.notes[next_loc] = cur_trig; } break; case 2: { ChannelWaveParams cur_params = pat->ch3.params[cur_loc]; TriggerNote cur_trig = pat->ch3.notes[cur_loc]; pat->ch3.params[cur_loc] = pat->ch3.params[next_loc]; pat->ch3.notes[cur_loc] = pat->ch3.notes[next_loc]; pat->ch3.params[next_loc] = cur_params; pat->ch3.notes[next_loc] = cur_trig; } break; case 3: { ChannelNoiseParams cur_params = pat->ch4.params[cur_loc]; TriggerNote cur_trig = pat->ch4.notes[cur_loc]; pat->ch4.params[cur_loc] = pat->ch4.params[next_loc]; pat->ch4.notes[cur_loc] = pat->ch4.notes[next_loc]; pat->ch4.params[next_loc] = cur_params; pat->ch4.notes[next_loc] = cur_trig; } break; default: break; } } void handle_trigger_selection(void) { TriggerNote *trig = get_current_trig(); static bool nudge = false; bool empty = patterns[pattern_selection_loc].empty; if (key_pressed(KEY_B) && ctrl.key_b > 10) { nudge = true; } else if (key_released(KEY_B)) { if (nudge) { nudge = false; } else { if (key_hold(KEY_SELECT)) { clipboard_copy(); } else { if (empty) { clear_pattern(pattern_selection_loc); } trig->active ^= 1; } } redraw_trigs = true; } else if (key_retrig(KEY_L)) { s32 inc = -1; if (key_hold(KEY_SELECT)) { inc = -12; } // Decrease note. if (trig->active && !empty) { if (channel_selection_loc == 3) { int tmp = current_scale; current_scale = 0; trig->note = scale_note(trig->note, inc); current_scale = tmp; } else { trig->note = scale_note(trig->note, inc); } } redraw_trigs = true; } else if (key_retrig(KEY_R)) { s32 inc = 1; if (key_hold(KEY_SELECT)) { inc = 12; } // Increase note. if (trig->active && !empty) { if (channel_selection_loc == 3) { int tmp = current_scale; current_scale = 0; trig->note = scale_note(trig->note, inc); current_scale = tmp; } else { trig->note = scale_note(trig->note, inc); } } redraw_trigs = true; } // Move trigger cursor. if (key_retrig(KEY_LEFT)) { if (key_hold(KEY_B)) { if (trig_selection_loc != 0 && trig_selection_loc != 8) { int next_selection_loc = MAX(trig_selection_loc - 1, 0); nudge_trigs(trig_selection_loc, next_selection_loc); nudge = true; trig_selection_loc = next_selection_loc; } redraw_trigs = true; } else { 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_retrig(KEY_RIGHT)) { if (key_hold(KEY_B)) { if (trig_selection_loc != 7 && trig_selection_loc != 15) { int next_selection_loc = MIN(trig_selection_loc + 1, 15); nudge_trigs(trig_selection_loc, next_selection_loc); nudge = true; trig_selection_loc = next_selection_loc; } redraw_trigs = true; } else { 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)) { if (key_hold(KEY_B)) { int next_selection_loc = (trig_selection_loc + 8) % 16; nudge_trigs(trig_selection_loc, next_selection_loc); nudge = true; trig_selection_loc = next_selection_loc; redraw_trigs = true; } else { 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 { if (trig->active && !patterns[pattern_selection_loc].empty) { // 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)) { metadata.current_bank = current_bank; metadata.current_pattern = current_pattern; metadata.current_scale = current_scale; metadata.current_scale_root = current_scale_root; save_bank(current_bank); save_metadata(); send_notif("SAVED BANK"); return; } // Stop the sequencer or start playing from the beginning. reset_sequencer(); 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 sync_in_96(void) { if (play_status) { sequencer_tick(); } } void sync_in_48(void) { if (play_status) { // 48bpq -> 96bpq. sequencer_tick(); sequencer_tick(); } } void sync_in_24(void) { if (play_status) { // 24bpq -> 96bpq. sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); } } void sync_in_12(void) { if (play_status) { // 12bpq -> 96bpq. sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); } } void sync_in_6(void) { if (play_status) { // 6bpq -> 96bpq. sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); sequencer_tick(); } } void sync_in_4(void) { if (play_status) { // 4bpq -> 96bpq. for (size_t i = 0; i < 24; i++) { sequencer_tick(); } } } 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; current_scale = metadata.current_scale; current_scale_root = metadata.current_scale_root; 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(); }