#include "rng.c" #include "text.h" // // Color indexes. // #define COL_BG 0 #define COL_FG 1 #define COL_RED 2 #define COL_BLUE 3 #define COL_CYAN 4 #define COL_GREY 5 // Theme colors. #define COL_CURSOR COL_BLUE #define COL_NOTE_PRESSED COL_BLUE #define COL_CURRENT_CHANNEL COL_FG #define COL_CURRENT_TRIG COL_FG #define COL_WAVE_A COL_RED #define COL_WAVE_B COL_CYAN // // Assets. // #define ASSETS_DATA ((u32*)(MEM_VRAM + KB(32))) static const u32 note_names[] = { 0x000000e0, 0x202020e0, 0x0000000e, 0x080e020e, 0x00000098, 0xa8a8a898, 0x00000038, 0x203b0a39, 0x00000060, 0xa0a0a060, 0x0000000e, 0x080e020e, 0x000000b8, 0x889888b8, 0x00000038, 0x203b0a39, 0x000000e0, 0x206020e0, 0x0000000e, 0x080e020e, 0x000000e0, 0x20602020, 0x0000000e, 0x080e020e, 0x000000b8, 0x8888a8b8, 0x00000038, 0x203b0a39, 0x000000e0, 0x2020a0e0, 0x0000000e, 0x080e020e, 0x000000b8, 0xa8a8b8a8, 0x00000038, 0x203b0a39, 0x000000e0, 0xa0a0e0a0, 0x0000000e, 0x080e020e, 0x000000b8, 0xa898a8b8, 0x00000038, 0x203b0a39, 0x000000e0, 0xa060a0e0, 0x0000000e, 0x080e020e, 0x000000e0, 0x202020e0, 0x0000000e, 0x080c080e, 0x00000098, 0xa8a8a898, 0x00000038, 0x20332239, 0x00000060, 0xa0a0a060, 0x0000000e, 0x080c080e, 0x000000b8, 0x889888b8, 0x00000038, 0x20332239, 0x000000e0, 0x206020e0, 0x0000000e, 0x080c080e, 0x000000e0, 0x20602020, 0x0000000e, 0x080c080e, 0x000000b8, 0x8888a8b8, 0x00000038, 0x20332239, 0x000000e0, 0x2020a0e0, 0x0000000e, 0x080c080e, 0x000000b8, 0xa8a8b8a8, 0x00000038, 0x20332239, 0x000000e0, 0xa0a0e0a0, 0x0000000e, 0x080c080e, 0x000000b8, 0xa898a8b8, 0x00000038, 0x20332239, 0x000000e0, 0xa060a0e0, 0x0000000e, 0x080c080e, 0x000000e0, 0x202020e0, 0x0000000a, 0x0a0e0808, 0x00000098, 0xa8a8a898, 0x00000028, 0x283b2221, 0x00000060, 0xa0a0a060, 0x0000000a, 0x0a0e0808, 0x000000b8, 0x889888b8, 0x00000028, 0x283b2221, 0x000000e0, 0x206020e0, 0x0000000a, 0x0a0e0808, 0x000000e0, 0x20602020, 0x0000000a, 0x0a0e0808, 0x000000b8, 0x8888a8b8, 0x00000028, 0x283b2221, 0x000000e0, 0x2020a0e0, 0x0000000a, 0x0a0e0808, 0x000000b8, 0xa8a8b8a8, 0x00000028, 0x283b2221, 0x000000e0, 0xa0a0e0a0, 0x0000000a, 0x0a0e0808, 0x000000b8, 0xa898a8b8, 0x00000028, 0x283b2221, 0x000000e0, 0xa060a0e0, 0x0000000a, 0x0a0e0808, 0x000000e0, 0x202020e0, 0x0000000e, 0x020e080e, 0x00000098, 0xa8a8a898, 0x00000038, 0x083b2239, 0x00000060, 0xa0a0a060, 0x0000000e, 0x020e080e, 0x000000b8, 0x889888b8, 0x00000038, 0x083b2239, 0x000000e0, 0x206020e0, 0x0000000e, 0x020e080e, 0x000000e0, 0x20602020, 0x0000000e, 0x020e080e, 0x000000b8, 0x8888a8b8, 0x00000038, 0x083b2239, 0x000000e0, 0x2020a0e0, 0x0000000e, 0x020e080e, 0x000000b8, 0xa8a8b8a8, 0x00000038, 0x083b2239, 0x000000e0, 0xa0a0e0a0, 0x0000000e, 0x020e080e, 0x000000b8, 0xa898a8b8, 0x00000038, 0x083b2239, 0x000000e0, 0xa060a0e0, 0x0000000e, 0x020e080e, 0x000000e0, 0x202020e0, 0x0000000e, 0x020e0a0e, 0x00000098, 0xa8a8a898, 0x00000038, 0x083b2a39, 0x00000060, 0xa0a0a060, 0x0000000e, 0x020e0a0e, 0x000000b8, 0x889888b8, 0x00000038, 0x083b2a39, 0x000000e0, 0x206020e0, 0x0000000e, 0x020e0a0e, 0x000000e0, 0x20602020, 0x0000000e, 0x020e0a0e, 0x000000b8, 0x8888a8b8, 0x00000038, 0x083b2a39, 0x000000e0, 0x2020a0e0, 0x0000000e, 0x020e0a0e, 0x000000b8, 0xa8a8b8a8, 0x00000038, 0x083b2a39, 0x000000e0, 0xa0a0e0a0, 0x0000000e, 0x020e0a0e, 0x000000b8, 0xa898a8b8, 0x00000038, 0x083b2a39, 0x000000e0, 0xa060a0e0, 0x0000000e, 0x020e0a0e, 0x000000e0, 0x202020e0, 0x0000000e, 0x08040202, 0x00000098, 0xa8a8a898, 0x00000038, 0x20130a09, 0x00000060, 0xa0a0a060, 0x0000000e, 0x08040202, 0x000000b8, 0x889888b8, 0x00000038, 0x20130a09, 0x000000e0, 0x206020e0, 0x0000000e, 0x08040202, 0x000000e0, 0x20602020, 0x0000000e, 0x08040202, 0x000000b8, 0x8888a8b8, 0x00000038, 0x20130a09, 0x000000e0, 0x2020a0e0, 0x0000000e, 0x08040202, 0x000000b8, 0xa8a8b8a8, 0x00000038, 0x20130a09, 0x000000e0, 0xa0a0e0a0, 0x0000000e, 0x08040202, 0x000000b8, 0xa898a8b8, 0x00000038, 0x20130a09, 0x000000e0, 0xa060a0e0, 0x0000000e, 0x08040202, 0x000000e0, 0x202020e0, 0x0000000e, 0x0a0e0a0e, }; static const 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, }; static const u32 default_wave_buttons[] = { 0xff013149, 0x850101ff, 0x3f202028, 0x2423203f, 0xff016151, 0x49c501ff, 0x3f202c2a, 0x2928203f, 0xff017d45, 0x45c501ff, 0x3f202828, 0x282f203f, 0xff014911, 0x812501ff, 0x3f202128, 0x2420203f, }; // // Wave data. // static const u32 sine_wave[16] = { 0xefdebc89, 0x98cbedfe, 0x10214376, 0x67341201, }; static const u32 saw_wave[16] = { 0x67452301, 0xefcdab89, 0x67452301, 0xefcdab89, }; static const u32 square_wave[16] = { 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, }; // // Globals. // static int bpm = 115; static int step_counter = 0; int trig_selection_loc = 0; int param_selection_loc = 0; int channel_selection_loc = 0; typedef struct TriggerNote { bool active; Note note; } TriggerNote; typedef struct ChannelSquareParams { u8 env_volume; u8 env_time; u8 env_direction; u8 duty_cycle; u8 sweep_number; u8 sweep_time; u8 sweep_direction; } ChannelSquareParams; typedef struct ChannelWaveParams { u8 wave_volume; u8 wave_mode; u32 wave_a[4]; u32 wave_b[4]; } ChannelWaveParams; typedef struct ChannelSquare { bool active; TriggerNote notes[16]; ChannelSquareParams params[16]; } ChannelSquare; typedef struct ChannelWave { bool active; TriggerNote notes[16]; ChannelWaveParams params[16]; } ChannelWave; static ChannelSquare ch1 = { .notes = { {true, NOTE_C_4}, {true, NOTE_D_4}, {true, NOTE_E_4}, {true, NOTE_F_4}, {true, NOTE_G_4}, {true, NOTE_A_4}, {true, NOTE_B_4}, {true, NOTE_C_5}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, }, .params = { {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, }, .active = true, }; static ChannelSquare ch2 = { .notes = { {true, NOTE_C_4}, {true, NOTE_D_4}, {true, NOTE_E_4}, {true, NOTE_F_4}, {true, NOTE_G_4}, {true, NOTE_A_4}, {true, NOTE_B_4}, {true, NOTE_C_5}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, }, .params = { {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, {8, 4, 0, 2, 0, 0, 0}, }, .active = true, }; static ChannelWave ch3 = { .notes = { {true, NOTE_C_4}, {true, NOTE_D_4}, {true, NOTE_E_4}, {true, NOTE_F_4}, {true, NOTE_G_4}, {true, NOTE_A_4}, {true, NOTE_B_4}, {true, NOTE_C_5}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, {true, NOTE_C_4}, }, .params = { {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0x00000000, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, {3, 0, {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000},{0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}}, }, .active = true, }; // // Trigger render functions. // #define TRIG_W 15 #define TRIG_H 24 #define TRIG_START_X 64 #define TRIG_START_Y 90 #define TRIG_OFFSET_X (TRIG_W + 3) #define TRIG_OFFSET_Y (TRIG_H + 7) void clear_trigger(size_t i) { size_t offset_x = TRIG_OFFSET_X * (i % 8); size_t offset_y = i < 8 ? 0 : TRIG_OFFSET_Y; size_t x0 = TRIG_START_X + offset_x + 1; size_t x1 = TRIG_START_X + offset_x + TRIG_W - 1; size_t y0 = TRIG_START_Y + offset_y + 1; size_t y1 = TRIG_START_Y + offset_y + TRIG_H - 1; draw_filled_rect(x0, y0, x1, y1, COL_BG); } void draw_trigger(size_t chan, size_t i) { TriggerNote trig = {0}; switch (chan) { case 0: { trig = ch1.notes[i]; } break; case 1: { trig = ch2.notes[i]; } break; case 2: { trig = ch3.notes[i]; } break; } if (trig.active) { size_t offset_x = TRIG_OFFSET_X * (i % 8); size_t offset_y = i < 8 ? 0 : TRIG_OFFSET_Y; size_t x = TRIG_START_X + offset_x; size_t y = TRIG_START_Y + offset_y; Tile *tiles = ASSETS_DATA; tiles += 2 * trig.note; draw_tile(x, y, tiles, COL_FG, true); draw_tile(x + 8, y, tiles + 1, COL_FG, true); } else { clear_trigger(i); } } void draw_trig_cursor(size_t i, u8 clr) { size_t offset_x = TRIG_OFFSET_X * (i % 8); size_t offset_y = i < 8 ? 2 : 2 + TRIG_OFFSET_Y; size_t x0 = TRIG_START_X + offset_x; size_t x1 = TRIG_START_X + TRIG_W + offset_x; size_t y = TRIG_START_Y + TRIG_H + offset_y; draw_line(x0, y, x1, y, clr); } void draw_triggers(void) { for (size_t i = 0; i < 16; i++) { size_t offset_x = TRIG_OFFSET_X * (i % 8); size_t offset_y = i < 8 ? 0 : 0 + TRIG_OFFSET_Y; size_t x0 = TRIG_START_X + offset_x; size_t x1 = TRIG_START_X + offset_x + TRIG_W; size_t y0 = TRIG_START_Y + offset_y; size_t y1 = TRIG_START_Y + offset_y + TRIG_H; draw_rect(x0, y0, x1, y1, COL_FG); clear_trigger(i); draw_trigger(channel_selection_loc, i); } } // // Channel render functions. // #define CHAN_W 19 #define CHAN_H 8 #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. 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++) { bool active = false; switch (i) { case 0: { active = ch1.active; } break; case 1: { active = ch2.active; } break; case 2: { active = ch3.active; } break; } u8 clr = active ? COL_FG : COL_GREY; size_t y = CHAN_START_Y + i * CHAN_OFFSET_Y; draw_tile(CHAN_START_X, y, channel_tiles + k++, clr, false); draw_tile(CHAN_START_X + 8, y, channel_tiles + k++, clr, false); draw_tile(CHAN_START_X + 16, y, channel_tiles + k++, clr, false); } } void draw_channel_cursor(size_t i, u8 clr) { size_t offset_x = 0; size_t offset_y = CHAN_H + i * CHAN_OFFSET_Y + 1; size_t x0 = CHAN_START_X + offset_x; size_t x1 = CHAN_START_X + offset_x + CHAN_W; size_t y = CHAN_START_Y + offset_y; draw_line(x0, y, x1, y, clr); } #define PIANO_W 170 #define PIANO_H 20 #define PIANO_START_X 35 #define PIANO_START_Y 65 #define PIANO_NOTE_W 2 void draw_note(u8 note, u8 clr) { size_t octave = note / 12; size_t value = note % 12; size_t x0 = 0; size_t y0 = 0; size_t x1 = 0; size_t y1 = 0; switch (value) { // White notes. case 0:{ x0 = PIANO_START_X + 2 + octave * 28; x1 = x0 + 1; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 2; x1 = x0; y0 = y0 + 9; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 2:{ x0 = PIANO_START_X + 2 + octave * 28 + 5; x1 = x0; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + 12; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 4; x1 = x0 + 2; y0 = PIANO_START_Y - 2 + 13; y1 = y0 + 7; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 4:{ x0 = PIANO_START_X + 2 + octave * 28 + 9; x1 = x0 + 1; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + 12; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 8; x1 = x0 + 2; y0 = PIANO_START_Y - 2 + 13; y1 = y0 + 7; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 5:{ x0 = PIANO_START_X + 2 + octave * 28 + 12; x1 = x0 + 1; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 14; x1 = x0; y0 = y0 + 9; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 7:{ x0 = PIANO_START_X + 2 + octave * 28 + 17; x1 = x0; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + 12; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 16; x1 = x0 + 2; y0 = PIANO_START_Y - 2 + 13; y1 = y0 + 7; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 9:{ x0 = PIANO_START_X + 2 + octave * 28 + 21; x1 = x0; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + 12; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 20; x1 = x0 + 2; y0 = PIANO_START_Y - 2 + 13; y1 = y0 + 7; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 11: { x0 = PIANO_START_X + 2 + octave * 28 + 25; x1 = x0 + 1; y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + 12; draw_filled_rect(x0, y0, x1, y1, clr); x0 = PIANO_START_X + 2 + octave * 28 + 24; x1 = x0 + 2; y0 = PIANO_START_Y - 2 + 13; y1 = y0 + 7; draw_filled_rect(x0, y0, x1, y1, clr); } break; default: { if (clr == COL_FG) { clr = COL_BG; } y0 = PIANO_START_Y + 2; y1 = PIANO_START_Y - 2 + 11; switch (value) { case 1: { x0 = PIANO_START_X + 2 + octave * 28 + 3; } break; case 3: { x0 = PIANO_START_X + 2 + octave * 28 + 7; } break; case 6: { x0 = PIANO_START_X + 2 + octave * 28 + 15; } break; case 8: { x0 = PIANO_START_X + 2 + octave * 28 + 19; } break; case 10: { x0 = PIANO_START_X + 2 + octave * 28 + 23; } break; } x1 = x0; draw_line(x0, y0, x1, y1, clr); } break; } } void draw_piano(void) { size_t x0 = PIANO_START_X; size_t x1 = PIANO_START_X + PIANO_W; size_t y0 = PIANO_START_Y; size_t y1 = PIANO_START_Y + PIANO_H; draw_rect(x0, y0, x1, y1, COL_FG); for (size_t i = 0; i < 12 * 6; i++) { draw_note(i, COL_FG); } } #define PARAMS_W 170 #define PARAMS_H 48 #define PARAMS_START_X 35 #define PARAMS_START_Y 12 void draw_params_cursor_ch3(size_t i, u8 clr) { u8 x_positions[] = { // 32 half bytes (Wave A). 0, 4, 8, 12, 16, 20, 24, 28, 34, 38, 42, 46, 50, 54, 58, 62, 0, 4, 8, 12, 16, 20, 24, 28, 34, 38, 42, 46, 50, 54, 58, 62, // 32 half bytes (Wave B). 70, 74, 78, 82, 86, 90, 94, 98, 104, 108, 112, 116, 120, 124, 128, 132, 70, 74, 78, 82, 86, 90, 94, 98, 104, 108, 112, 116, 120, 124, 128, 132, // Default wave A. 1, 18, 35, 52, // Default wave B. 71, 88, 105, 122, // Mode selection. 141, // Volume selection. 141, }; u8 y_positions[] = { // 32 half bytes (Wave A) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 32 half bytes (Wave B) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // Default wave A. 20, 20, 20, 20, // Default wave B. 20, 20, 20, 20, // Mode seleciton. 20, // Volume seleciton. 0, }; size_t cursor_length = 0; if (i < 64) { cursor_length = 4; } else if (i < 72) { cursor_length = 13; } else { cursor_length = 30; } size_t x = PARAMS_START_X + x_positions[i] - 1; size_t y = PARAMS_START_Y + PARAMS_H - 18 + y_positions[i]; draw_line(x, y, x + cursor_length, y, clr); } void draw_params_cursor(size_t i, u8 clr) { switch (channel_selection_loc) { case 0: { } break; case 1: { } break; case 2: { draw_params_cursor_ch3(i, clr); } break; case 3: { } break; } } IWRAM_CODE void draw_wave_pattern(u8 *pattern, int x, int y, u8 clr) { for (size_t i = 0; i < 16; ++i) { u8 byte = pattern[i]; u8 first = (byte >> 4) & 0xF; u8 second = byte & 0xF; u8 a = x + i * 4; u8 b = y + 16; draw_pixel(a, b - first, clr); draw_pixel(a + 1, b - first, clr); draw_pixel(a + 2, b - second, clr); draw_pixel(a + 3, b - second, clr); } } IWRAM_CODE void clear_parameters(void) { size_t x0 = PARAMS_START_X; size_t y0 = PARAMS_START_Y; size_t x1 = PARAMS_START_X + PARAMS_W; size_t y1 = PARAMS_START_Y + PARAMS_H; draw_filled_rect(x0, y0, x1, y1, COL_BG); } IWRAM_CODE void draw_parameters_ch3(void) { clear_parameters(); // TODO: Move to VRAM and decompress at the beginning. Tile wave_tiles[4 * 2] = {0}; unpack_tiles(default_wave_buttons, wave_tiles, 4 * 2); // Draw current wave data. { u8 *wave_a = ch3.params[trig_selection_loc].wave_a; u8 *wave_b = ch3.params[trig_selection_loc].wave_b; size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y + 2; // Wave Patterns. draw_wave_pattern(wave_a, x, y, COL_WAVE_A); draw_wave_pattern(wave_b, x + 70, y, COL_WAVE_B); // Wave text. x -= 2; txt_drawf_small("%02x%02x%02x%02x", x, y + 20, 4, COL_FG, wave_a[0], wave_a[1], wave_a[2], wave_a[3]); txt_drawf_small("%02x%02x%02x%02x", x + 34, y + 20, 4, COL_FG, wave_a[4], wave_a[5], wave_a[6], wave_a[7]); txt_drawf_small("%02x%02x%02x%02x", x, y + 28, 4, COL_FG, wave_a[8], wave_a[9], wave_a[10], wave_a[11]); txt_drawf_small("%02x%02x%02x%02x", x + 34, y + 28, 4, COL_FG, wave_a[12], wave_a[13], wave_a[14], wave_a[15]); x += 70; txt_drawf_small("%02x%02x%02x%02x", x, y + 20, 4, COL_FG, wave_b[0], wave_b[1], wave_b[2], wave_b[3]); txt_drawf_small("%02x%02x%02x%02x", x + 34, y + 20, 4, COL_FG, wave_b[4], wave_b[5], wave_b[6], wave_b[7]); txt_drawf_small("%02x%02x%02x%02x", x, y + 28, 4, COL_FG, wave_b[8], wave_b[9], wave_b[10], wave_b[11]); txt_drawf_small("%02x%02x%02x%02x", x + 34, y + 28, 4, COL_FG, wave_b[12], wave_b[13], wave_b[14], wave_b[15]); } // Draw default wave buttons. { size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y + PARAMS_H - 7; for (size_t i = 0, k = 0; i < 4 * 2; i += 2, k++) { draw_tile(x + 17 * k, y, wave_tiles + i, COL_FG, true); draw_tile(x + 17 * k + 8, y, wave_tiles + i + 1, COL_FG, true); } for (size_t i = 0, k = 0; i < 4 * 2; i += 2, k++) { draw_tile(x + 17 * k + 70, y, wave_tiles + i, COL_FG, true); draw_tile(x + 17 * k + 8 + 70, y, wave_tiles + i + 1, COL_FG, true); } } // Mode selection. { size_t x = PARAMS_START_X + 140; size_t y = PARAMS_START_Y + PARAMS_H - 17; draw_line(x, y + 4, x + 5, y + 4, COL_FG); draw_line(x + 25, y + 4, x + 30, y + 4, COL_FG); draw_line(x, y + 5, x, y + 16, COL_FG); draw_line(x + 30, y + 5, x + 30, y + 17, COL_FG); draw_line(x, y + 17, x + 30, y + 17, COL_FG); txt_drawf_small("mode", x + 6, y, 4, COL_FG); switch (ch3.params[trig_selection_loc].wave_mode) { case 0: { txt_drawf("A", x + 12, y + 8, 6, COL_FG); } break; case 1: { txt_drawf("B", x + 12, y + 8, 6, COL_FG); } break; case 2: { txt_drawf("A+B", x + 6, y + 8, 6, COL_FG); } break; } } // Wave volume. { size_t x = PARAMS_START_X + 140; size_t y = PARAMS_START_Y + PARAMS_H - 40; draw_line(x, y + 7, x + 7, y + 7, COL_FG); draw_line(x + 23, y + 7, x + 30, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 30, y + 8, x + 30, y + 19, COL_FG); draw_line(x, y + 20, x + 30, y + 20, COL_FG); txt_drawf_small("vol", x + 8, y + 3, 4, COL_FG); switch (ch3.params[trig_selection_loc].wave_volume) { case 0: { txt_drawf("0", x + 12, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("25", x + 9, y + 10, 6, COL_FG); } break; case 2: { txt_drawf("50", x + 9, y + 10, 6, COL_FG); } break; case 3: { txt_drawf("75", x + 9, y + 10, 6, COL_FG); } break; case 4: { txt_drawf("100", x + 6, y + 10, 6, COL_FG); } break; } } } void draw_parameters(void) { switch (channel_selection_loc) { case 0: { } break; case 1: { } break; case 2: { draw_parameters_ch3(); } break; case 3: { } break; } } void irq_timer(void) { if (ch1.active) { TriggerNote *trig = &ch1.notes[step_counter]; ChannelSquareParams *params = &ch1.params[step_counter]; if (trig->active) { SOUND_SQUARE1_SWEEP = SOUND_SWEEP_NUMBER(params->sweep_number) | SOUND_SWEEP_DIR(params->sweep_direction) | SOUND_SWEEP_TIME(params->sweep_time); 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]; } } else { SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE1_FREQ = 0; } if (ch2.active) { TriggerNote *trig = &ch2.notes[step_counter]; ChannelSquareParams *params = &ch2.params[step_counter]; if (trig->active) { 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]; } } else { SOUND_SQUARE2_CTRL = 0; SOUND_SQUARE2_FREQ = 0; } if (ch3.active) { TriggerNote *trig = &ch3.notes[step_counter]; ChannelWaveParams *params = &ch3.params[step_counter]; if (trig->active) { switch (params->wave_mode) { case 0: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, params->wave_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, params->wave_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, params->wave_b, 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy32(SOUND_WAVE_RAM, params->wave_a, 16); SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(1) | SOUND_WAVE_BANK_SELECT(0); } break; } SOUND_WAVE_MODE |= SOUND_WAVE_ENABLE; 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; } SOUND_WAVE_FREQ = SOUND_FREQ_RESET | sound_rates[trig->note]; } } else { SOUND_WAVE_CTRL = 0; SOUND_WAVE_FREQ = 0; } step_counter = (step_counter + 1) % 16; } 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); TIMER_DATA_0 = n_ticks; TIMER_CTRL_0 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3; } TriggerNote * get_current_trig(void) { switch (channel_selection_loc) { case 0: { return &ch1.notes[trig_selection_loc]; } break; case 1: { return &ch2.notes[trig_selection_loc]; } break; case 2: { return &ch3.notes[trig_selection_loc]; } break; } return NULL; } // Input handling works using a FSM. The input handler is switched to whichever // function controls each section. For example, channel selection or trigger // selection. void (*input_handler)(void); #define SEQ_N_CHANNELS 3 void handle_trigger_selection(void); void handle_channel_selection(void); void handle_param_selection_ch1(void); void handle_param_selection_ch2(void); void handle_param_selection_ch3(void); void handle_channel_selection(void) { if (key_tap(KEY_B)) { switch (channel_selection_loc) { case 0: { ch1.active ^= 1; } break; case 1: { ch2.active ^= 1; } break; case 2: { ch3.active ^= 1; } break; } draw_channels(); } if (key_tap(KEY_RIGHT)) { trig_selection_loc = 0; param_selection_loc = 0; input_handler = handle_trigger_selection; draw_channel_cursor(channel_selection_loc, COL_CURRENT_CHANNEL); draw_trig_cursor(trig_selection_loc, COL_CURSOR); TriggerNote *trig = get_current_trig(); draw_note(trig->note, COL_NOTE_PRESSED); draw_parameters(); } else if (key_tap(KEY_UP)) { draw_channel_cursor(channel_selection_loc, COL_BG); if (channel_selection_loc == 0) { channel_selection_loc = SEQ_N_CHANNELS - 1; } else { channel_selection_loc = MAX(channel_selection_loc - 1, 0); } draw_channel_cursor(channel_selection_loc, COL_CURSOR); draw_triggers(); } else if (key_tap(KEY_DOWN)) { draw_channel_cursor(channel_selection_loc, COL_BG); if (channel_selection_loc == SEQ_N_CHANNELS - 1) { channel_selection_loc = 0; } else { channel_selection_loc = MIN(channel_selection_loc + 1, SEQ_N_CHANNELS); } draw_channel_cursor(channel_selection_loc, COL_CURSOR); draw_triggers(); } } void handle_param_selection_ch1(void) { if (key_tap(KEY_A)) { // TODO: draw param cursor. input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURRENT_TRIG); } // // 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; // } // } } void handle_param_selection_ch2(void) { if (key_tap(KEY_A)) { // TODO: draw param cursor. input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURRENT_TRIG); } // // 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; // } // } } void handle_param_selection_ch3(void) { if (key_tap(KEY_A)) { // Go back to trigger selection. draw_params_cursor(param_selection_loc, COL_BG); input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURSOR); } 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) { inc = 26; } else if (loc == 63) { inc = 9; } else if (loc != 47 && loc != 63 && loc < 72) { inc = 1; } } else { if (loc == 32 || loc == 48) { inc = -17; } else if (loc == 73) { inc = -26; } else if (loc != 16 && loc != 64) { inc = -1; } } draw_params_cursor_ch3(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 73); draw_params_cursor_ch3(param_selection_loc, COL_CURSOR); } 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 == 72) { inc = 1; } else if (loc == 73) { inc = -1; } } 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); } else if (loc == 72) { inc = 1; } else if (loc == 73) { inc = -1; } } draw_params_cursor_ch3(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 73); draw_params_cursor_ch3(param_selection_loc, COL_CURSOR); } 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) { // Draw on wave a. u8 byte_number = param_selection_loc / 2; u8 *byte = &ch3.params[trig_selection_loc].wave_a; byte += byte_number; if (odd) { *byte = (~0xF & *byte) | ((*byte + inc) & 0xF); } else { *byte = (0xF & *byte) | (((*byte >> 4) + inc) & 0xF) << 4; } } else if (param_selection_loc < 64){ // Draw on wave b. u8 byte_number = (param_selection_loc - 32) / 2; u8 *byte = &ch3.params[trig_selection_loc].wave_b; byte += byte_number; if (odd) { *byte = (~0xF & *byte) | ((*byte + inc) & 0xF); } else { *byte = (0xF & *byte) | (((*byte >> 4) + inc) & 0xF) << 4; } } else if (param_selection_loc < 72) { // Copy default waves. u32 *wave_a = &ch3.params[trig_selection_loc].wave_a; u32 *wave_b = &ch3.params[trig_selection_loc].wave_b; switch (param_selection_loc) { case 64: { memcpy32(wave_a, sine_wave, 16); } break; case 65: { memcpy32(wave_a, saw_wave, 16); } break; case 66: { memcpy32(wave_a, square_wave, 16); } break; case 67: { u32 rand_wave[4] = { rng32(), rng32(), rng32(), rng32(), }; memcpy32(wave_a, rand_wave, 16); } break; case 68: { memcpy32(wave_b, sine_wave, 16); } break; case 69: { memcpy32(wave_b, saw_wave, 16); } break; case 70: { memcpy32(wave_b, square_wave, 16); } break; case 71: { u32 rand_wave[4] = { rng32(), rng32(), rng32(), rng32(), }; memcpy32(wave_b, rand_wave, 16); } break; } } else if (param_selection_loc == 72) { u8 *wave_mode = &ch3.params[trig_selection_loc].wave_mode; *wave_mode = CLAMP(*wave_mode + inc, 0, 2); } else if (param_selection_loc == 73) { u8 *wave_volume = &ch3.params[trig_selection_loc].wave_volume; *wave_volume = CLAMP(*wave_volume + inc, 0, 4); } draw_parameters(); draw_params_cursor(param_selection_loc, COL_CURSOR); } } void handle_trigger_selection(void) { TriggerNote *trig = get_current_trig(); if (key_tap(KEY_B)) { // Toggle trigger. trig->active ^= 1; draw_trigger(channel_selection_loc, trig_selection_loc); } else if (key_tap(KEY_L)) { // Decrease note. if (trig->active) { draw_note(trig->note, COL_FG); trig->note = MAX(trig->note - 1, NOTE_C_2); draw_note(trig->note, COL_NOTE_PRESSED); clear_trigger(trig_selection_loc); draw_trigger(channel_selection_loc, trig_selection_loc); } } else if (key_tap(KEY_R)) { // Increase note. if (trig->active) { draw_note(trig->note, COL_FG); trig->note = MIN( trig->note + 1, NOTE_C_8 - 1); draw_note(trig->note, COL_NOTE_PRESSED); clear_trigger(trig_selection_loc); draw_trigger(channel_selection_loc, trig_selection_loc); } } // 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. draw_trig_cursor(trig_selection_loc, COL_BG); draw_note(trig->note, COL_FG); input_handler = handle_channel_selection; draw_channel_cursor(channel_selection_loc, COL_CURSOR); clear_parameters(); } else { draw_trig_cursor(trig_selection_loc, COL_BG); draw_note(trig->note, COL_FG); trig_selection_loc = MAX(trig_selection_loc - 1, 0); trig = get_current_trig(); draw_trig_cursor(trig_selection_loc, COL_CURSOR); draw_note(trig->note, COL_NOTE_PRESSED); draw_parameters(); } } else if (key_tap(KEY_RIGHT)) { if (trig_selection_loc != 7) { draw_trig_cursor(trig_selection_loc, COL_BG); draw_note(trig->note, COL_FG); trig_selection_loc = MIN(trig_selection_loc + 1, 15); trig = get_current_trig(); draw_trig_cursor(trig_selection_loc, COL_CURSOR); draw_note(trig->note, COL_NOTE_PRESSED); draw_parameters(); } } else if (key_tap(KEY_UP) || key_tap(KEY_DOWN)) { draw_trig_cursor(trig_selection_loc, COL_BG); draw_note(trig->note, COL_FG); trig_selection_loc = (trig_selection_loc + 8) % 16; trig = get_current_trig(); draw_trig_cursor(trig_selection_loc, COL_CURSOR); draw_note(trig->note, COL_NOTE_PRESSED); draw_parameters(); } else if (key_tap(KEY_A)) { // Switch to parameter selection. 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; } draw_params_cursor(param_selection_loc, COL_CURSOR); draw_trig_cursor(trig_selection_loc, COL_CURRENT_TRIG); } } void handle_sequencer_input(void) { poll_keys(); input_handler(); if (key_tap(KEY_START)) { // Stop the sequencer or start playing from the beginning. 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; } } else if (key_tap(KEY_SELECT)) { // Play/pause. TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; } } void load_note_names(void) { unpack_tiles(note_names, ASSETS_DATA, 2 * 73); } void sequencer_init(void) { // Unpack non-sprite tiles directly on the VRAM. load_note_names(); // Initialize background objects and sprites. draw_triggers(); draw_channels(); draw_piano(); TriggerNote *trig = get_current_trig(); draw_note(trig->note, COL_NOTE_PRESSED); // Initialize input handler. channel_selection_loc = 2; // DEBUG: Starting on CH3 input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURSOR); draw_channel_cursor(channel_selection_loc, COL_CURRENT_CHANNEL); draw_parameters(); // Initialize sound system. SOUND_STATUS = SOUND_ENABLE; SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1 | SOUND_SQUARE2 | SOUND_WAVE, 3); SOUND_DSOUND_MASTER = SOUND_DMG25; }