#include "rng.c" // // Globals. // typedef enum { SEQ_SELECT_TRIGGER, SEQ_SELECT_CHANNEL, SEQ_SELECT_PARAMETER, } SeqSelect; static int bpm = 115; static int step_counter = 0; int trig_selection_loc = 0; int param_selection_loc = 64; int channel_selection_loc = 2; SeqSelect current_selection = SEQ_SELECT_TRIGGER; // // Wave data. // // TODO: Make them u32. static const u8 sine_wave[16] = { 0x89, 0xBC, 0xDE, 0xEF, 0xFE, 0xED, 0xCB, 0x98, 0x76, 0x43, 0x21, 0x10, 0x01, 0x12, 0x34, 0x67, }; static const u8 saw_wave[16] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, }; static const u8 square_wave[16] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; // TODO: Split into individual trigger types. typedef struct SeqTrigger { bool trigger; Note note; u8 env_volume; u8 env_time; u8 env_direction; u8 duty_cycle; u8 sweep_number; u8 sweep_time; u8 sweep_direction; u8 wave_volume; u8 wave_mode; u8 wave_a[16]; u8 wave_b[16]; } SeqTrigger; static SeqTrigger sequences[3][16] = { // Synth 1. { {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_D_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_E_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_F_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_A_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_B_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, }, // Synth 2. { {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_5, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_B_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_A_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_G_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_F_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_E_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_D_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0, 0, 0, {0}, {0}}, }, // Synth 3. { {true, NOTE_C_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_D_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_E_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_F_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_A_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_B_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_C_6, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_C_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_D_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_E_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_F_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_A_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_B_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, {true, NOTE_C_6, 8, 4, 0, 2, 0, 0, 0, 3, 0, {0xffffffff, 0x00000000, 0xffffffff, 0x00000000}, {0}}, }, }; #define TRIG_W 15 #define TRIG_H 24 #define TRIG_START_X 66 #define TRIG_START_Y 90 #define TRIG_OFFSET_X (TRIG_W + 4) #define TRIG_OFFSET_Y (TRIG_H + 7) void draw_triggers(void) { for (size_t i = 0; i < 8; i++) { size_t x0 = TRIG_START_X + TRIG_OFFSET_X * i; size_t x1 = TRIG_START_X + TRIG_W + TRIG_OFFSET_X * i; size_t y0 = TRIG_START_Y; size_t y1 = TRIG_START_Y + TRIG_H; draw_rect(x0, y0, x1, y1, 1); } for (size_t i = 0; i < 8; i++) { size_t x0 = TRIG_START_X + TRIG_OFFSET_X * i; size_t x1 = TRIG_START_X + TRIG_W + TRIG_OFFSET_X * i; size_t y0 = TRIG_START_Y + TRIG_OFFSET_Y; size_t y1 = TRIG_START_Y + TRIG_H + TRIG_OFFSET_Y; draw_rect(x0, y0, x1, y1, 1); } } #define CHAN_START_X 35 #define CHAN_START_Y 90 #define CHAN_OFFSET_Y 12 void draw_channels(void) { // Contains 5 channel buttons: Ch. 1-4 + FM. We are only drawing the DMG // channels for now, since FM may take some time to develop. u32 channel_buttons[] = { 0xff017111, 0x117101ff, 0xff008585, 0x879500ff, 0x0f080808, 0x0808080f, 0xff01b989, 0x89b901ff, 0xff004242, 0x434a00ff, 0x0f080909, 0x0909080f, 0xff015d45, 0xc55d01ff, 0xff00a1a1, 0xa1a500ff, 0x0f080a0a, 0x0a0a080f, 0xff015d45, 0xc55d01ff, 0xff00a1a1, 0xa12500ff, 0x0f080a0a, 0x0a0b080f, 0xff01c141, 0xc14101ff, 0xff00151c, 0x141400ff, 0x0f080808, 0x0808080f, }; Tile channel_tiles[3 * 4] = {0}; unpack_tiles(channel_buttons, channel_tiles, 3 * 4); size_t k = 0; for (size_t i = 0; i < 4; i++) { size_t y = CHAN_START_Y + i * CHAN_OFFSET_Y; draw_tile(CHAN_START_X, y, channel_tiles + k++, false); draw_tile(CHAN_START_X + 8, y, channel_tiles + k++, false); draw_tile(CHAN_START_X + 16, y, channel_tiles + k++, false); } } void irq_timer_0(void) { Note active_note; { SeqTrigger *trig = &sequences[0][step_counter]; active_note = trig->note; if (trig->trigger) { SOUND_SQUARE1_SWEEP = SOUND_SWEEP_NUMBER(trig->sweep_number) | SOUND_SWEEP_DIR(trig->sweep_direction) | SOUND_SWEEP_TIME(trig->sweep_time); SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(trig->env_volume) | SOUND_SQUARE_ENV_TIME(trig->env_time) | SOUND_SQUARE_ENV_DIR(trig->env_direction) | SOUND_SQUARE_DUTY(trig->duty_cycle); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET | sound_rates[active_note]; } } { SeqTrigger *trig = &sequences[1][step_counter]; active_note = trig->note; if (trig->trigger) { SOUND_SQUARE2_CTRL = SOUND_SQUARE_ENV_VOL(trig->env_volume) | SOUND_SQUARE_ENV_TIME(trig->env_time) | SOUND_SQUARE_ENV_DIR(trig->env_direction) | SOUND_SQUARE_DUTY(trig->duty_cycle); SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET | sound_rates[active_note]; } } { SeqTrigger *trig = &sequences[2][step_counter]; active_note = trig->note; if (trig->trigger) { // Update both banks. // TODO: Actually depends on which bank is selected, no need to // update both if only one is playing. // TODO: Should we compare if previous and current wave are the // same before updating? SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy(SOUND_WAVE_RAM, trig->wave_a, 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy(SOUND_WAVE_RAM, trig->wave_b, 16); switch (trig->wave_mode) { case 0: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(0); } break; case 1: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(1); } break; case 2: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(1) | SOUND_WAVE_BANK_SELECT(0); } break; case 3: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(1) | SOUND_WAVE_BANK_SELECT(1); } break; } SOUND_WAVE_MODE |= SOUND_WAVE_ENABLE; switch (trig->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; } SOUND_WAVE_FREQ = SOUND_FREQ_RESET | sound_rates[active_note]; } } step_counter = (step_counter + 1) % 16; } #define SEQ_N_CHANNELS 3 void handle_sequencer_input(void) { poll_keys(); SeqTrigger *trig = &sequences[channel_selection_loc][trig_selection_loc]; if (current_selection == SEQ_SELECT_TRIGGER) { if (key_tap(KEY_LEFT)) { if (trig_selection_loc == 0 || trig_selection_loc == 8) { current_selection = SEQ_SELECT_CHANNEL; } else { trig_selection_loc = MAX(trig_selection_loc - 1, 0); } } else if (key_tap(KEY_RIGHT)) { if (trig_selection_loc != 7) { trig_selection_loc = MIN(trig_selection_loc + 1, 15); } } else if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { trig_selection_loc = (trig_selection_loc + 8) % 16; } else if (key_tap(KEY_B)) { trig->trigger ^= 1; } else if (key_tap(KEY_L)) { if (trig->trigger) { trig->note = MAX(trig->note - 1, NOTE_C_2); } } else if (key_tap(KEY_R)) { if (trig->trigger) { trig->note = MIN( trig->note + 1, NOTE_C_8); } } else if (key_tap(KEY_A)) { // Switch to parameter selection. current_selection = SEQ_SELECT_PARAMETER; } } else if (current_selection == SEQ_SELECT_PARAMETER) { if (channel_selection_loc < 2) { // Move through the selected synth parameters. if (key_tap(KEY_LEFT)) { int max_param = 6; if (channel_selection_loc == 1) { max_param = 3; } if (param_selection_loc == 0) { param_selection_loc = max_param; } else { param_selection_loc = MAX(param_selection_loc - 1, 0); } } if (key_tap(KEY_RIGHT)) { int max_param = 6; if (channel_selection_loc == 1) { max_param = 3; } if (param_selection_loc == max_param) { param_selection_loc = 0; } else { param_selection_loc = MIN(param_selection_loc + 1, max_param); } } // Adjust the parameters up or down. if (key_tap(KEY_L) || key_tap(KEY_R)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } switch (param_selection_loc) { case 0: { trig->env_volume = CLAMP(trig->env_volume + inc, 0, 15); } break; case 1: { trig->env_time = CLAMP(trig->env_time + inc, 0, 7); } break; case 2: { trig->env_direction ^= 1; } break; case 3: { trig->duty_cycle = CLAMP(trig->duty_cycle + inc, 0, 3); } break; case 4: { trig->sweep_number = CLAMP(trig->sweep_number + inc, 0, 7); } break; case 5: { trig->sweep_time = CLAMP(trig->sweep_time + inc, 0, 7); } break; case 6: { if (trig->sweep_direction == 0) { trig->sweep_direction = 1; } else { trig->sweep_direction = 0; } } break; } } } else if (channel_selection_loc == 2) { if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { int inc = 0; int loc = param_selection_loc; if (key_tap(KEY_RIGHT)) { if (loc == 15 || loc == 31) { inc = 17; } else if (loc != 47 && loc != 63){ inc = 1; } } else { if (loc == 32 || loc == 48) { inc = -17; } else if (loc != 16 && loc != 64){ inc = -1; } } param_selection_loc = CLAMP(loc + inc, 0, 71); } if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { int inc = 0; int loc = param_selection_loc; if (key_tap(KEY_UP)) { if ((loc >= 16 && loc < 32) || (loc >= 48 && loc < 64)) { inc = -16; } else if (loc == 64) { inc = -48; } else if (loc == 65) { inc = -45; } else if (loc == 66) { inc = -42; } else if (loc == 67) { inc = -39; } else if (loc == 68) { inc = -20; } else if (loc == 69) { inc = -17; } else if (loc == 70) { inc = -14; } else if (loc == 71) { inc = -11; } } else { if (loc < 16 || (loc >= 32 && loc < 48)) { inc = 16; } else if (loc >= 16 && loc <= 19){ inc = 48 - (loc - 16); } else if (loc >= 20 && loc <= 23){ inc = 45 - (loc - 20); } else if (loc >= 24 && loc <= 27){ inc = 42 - (loc - 24); } else if (loc >= 28 && loc <= 31){ inc = 39 - (loc - 28); } else if (loc >= 48 && loc <= 51){ inc = 20 - (loc - 48); } else if (loc >= 52 && loc <= 55){ inc = 17 - (loc - 52); } else if (loc >= 56 && loc <= 59){ inc = 14 - (loc - 56); } else if (loc >= 60 && loc <= 63){ inc = 11 - (loc - 60); } } param_selection_loc = CLAMP(loc + inc, 0, 71); } if (key_tap(KEY_R) || key_tap(KEY_L)) { int odd = param_selection_loc & 0x1; int inc; if (key_tap(KEY_R)) { inc = 1; } else { inc = -1; } // Wave: AA BB CC DD ... // ^^ // |`- odd // `-- even if (param_selection_loc < 32) { u8 byte_number = param_selection_loc >> 1; u8 byte = sequences[2][trig_selection_loc].wave_a[byte_number]; if (odd) { byte = (~0xF & byte) | ((byte + inc) & 0xF); } else { byte = (0xF & byte) | (((byte >> 4) + inc) & 0xF) << 4; } sequences[2][trig_selection_loc].wave_a[byte_number] = byte; } else if (param_selection_loc < 64){ u8 byte_number = (param_selection_loc - 32) >> 1; u8 byte = sequences[2][trig_selection_loc].wave_b[byte_number]; if (odd) { byte = (~0xF & byte) | (byte + inc); } else { byte = (0xF & byte) | ((byte >> 4) + inc) << 4; } sequences[2][trig_selection_loc].wave_b[byte_number] = byte; } else if (param_selection_loc == 64){ memcpy(&trig->wave_a, &sine_wave, 16); } else if (param_selection_loc == 65){ memcpy(&trig->wave_a, &saw_wave, 16); } else if (param_selection_loc == 66){ memcpy(&trig->wave_a, &square_wave, 16); } else if (param_selection_loc == 67){ u32 rand_wave[4] = { rng32(), rng32(), rng32(), rng32(), }; memcpy(&trig->wave_a, &rand_wave, 16); } else if (param_selection_loc == 68){ memcpy(&trig->wave_b, &sine_wave, 16); } else if (param_selection_loc == 69){ memcpy(&trig->wave_b, &saw_wave, 16); } else if (param_selection_loc == 70){ memcpy(&trig->wave_b, &square_wave, 16); } else if (param_selection_loc == 71){ u32 rand_wave[4] = { rng32(), rng32(), rng32(), rng32(), }; memcpy(&trig->wave_b, &rand_wave, 16); } } } // Go back to trigger selection. if (key_tap(KEY_A)) { current_selection = SEQ_SELECT_TRIGGER; } // Enable disable trigger. if (key_tap(KEY_B)) { trig->trigger ^= 1; } } else if (current_selection == SEQ_SELECT_CHANNEL) { if (key_tap(KEY_RIGHT)) { current_selection = SEQ_SELECT_TRIGGER; trig_selection_loc = 0; param_selection_loc = 0; } 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); } } 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); } } } if (key_tap(KEY_START)) { step_counter = 0; if ((TIMER_CTRL_0 & TIMER_CTRL_ENABLE) == 0) { set_time(bpm); } else { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; } } if (key_tap(KEY_SELECT)) { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; } if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT) || key_tap(KEY_UP) || key_tap(KEY_DOWN) || key_tap(KEY_L) || key_tap(KEY_R) ) { } } void set_time(int bpm) { // 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_0, irq_timer_0); TIMER_DATA_0 = n_ticks; TIMER_CTRL_0 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3; } void sequencer_init(void) { // Initialize background objects and sprites. draw_triggers(); draw_channels(); // Initialize sound system. SOUND_STATUS = SOUND_ENABLE; SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1 | SOUND_SQUARE2 | SOUND_WAVE, 3); SOUND_DSOUND_MASTER = SOUND_DMG25; set_time(bpm); }