#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_GREY #define COL_CURRENT_CHANNEL COL_GREY #define COL_CURRENT_TRIG COL_GREY #define COL_WAVE_A COL_RED #define COL_WAVE_B COL_CYAN // // Assets. // #define N_TILES_NOTE_NAMES 73 * 2 #define N_TILES_CHAN_BTSN 4 * 3 #define N_TILES_WAVE_BTNS 4 * 2 #define ASSETS_NOTE_NAMES ((u32*)(MEM_VRAM + KB(32))) #define ASSETS_CHANNEL_BUTTONS (ASSETS_NOTE_NAMES + (N_TILES_NOTE_NAMES * 8)) #define ASSETS_DEFAULT_WAVES (ASSETS_CHANNEL_BUTTONS + (N_TILES_CHAN_BTSN * 8)) 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 97 #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_NOTE_NAMES; 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 97 #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 = ASSETS_CHANNEL_BUTTONS; 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 72 #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 64 #define PARAMS_START_X 35 #define PARAMS_START_Y 8 void draw_params_cursor_wave(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 selection. 20, // Volume selection. 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 - 23 + y_positions[i]; draw_line(x, y, x + cursor_length, y, clr); } void draw_params_cursor_square(size_t i, u8 clr, bool sweep) { size_t x_offset = sweep ? 0 : 30; u8 x_positions[] = { 0, // Duty. 31, // Env. Vol. 59, // Env. Direction. 87, // Env. Time. 118, // Sweep Number. 146, // Sweep Time. 132, // Sweep Direction. }; u8 y_positions[] = { 20, // Duty. 20, // Env. Vol. 20, // Env. Direction. 20, // Env. Time. 20, // Sweep Number. 20, // Sweep Time. 0, // Sweep Direction. }; size_t cursor_length = 24; size_t x = PARAMS_START_X + x_positions[i] + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 23 + 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: { draw_params_cursor_square(i, clr, true); } break; case 1: { draw_params_cursor_square(i, clr, false); } break; case 2: { draw_params_cursor_wave(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 - 1; draw_filled_rect(x0, y0, x1, y1, COL_BG); } IWRAM_CODE void draw_parameters_wave(void) { // 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 + 13; // 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. { Tile *wave_tiles = ASSETS_DEFAULT_WAVES; size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y + PARAMS_H - 12; 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 - 22; 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 + 7, 6, COL_FG); } break; case 1: { txt_drawf("B", x + 12, y + 7, 6, COL_FG); } break; case 2: { txt_drawf("A+B", x + 6, y + 7, 6, COL_FG); } break; } } // Wave volume. { size_t x = PARAMS_START_X + 140; size_t y = PARAMS_START_Y + PARAMS_H - 45; 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_square(ChannelSquareParams *params, bool sweep) { size_t x_offset = sweep ? 0 : 30; // Duty cycle. { // Shape drawing. { size_t x = PARAMS_START_X + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 43; size_t x0 = x + 2; size_t x1 = x0; size_t x2 = x0; size_t x3 = x0; size_t x4 = x0; size_t x5 = x0; size_t y0 = y + 14; size_t y1 = y + 2; switch (params->duty_cycle) { case 0: { x1 += 4; x2 += 6; x3 += 13; x4 += 15; x5 += 20; } break; case 1: { x1 += 4; x2 += 7; x3 += 13; x4 += 16; x5 += 20; } break; case 2: { x1 += 3; x2 += 8; x3 += 12; x4 += 17; x5 += 20; } break; case 3: { x1 += 2; x2 += 9; x3 += 11; x4 += 18; x5 += 20; } break; } draw_line(x0, y0, x1, y0, COL_RED); draw_line(x1, y1, x1, y0, COL_RED); draw_line(x1, y1, x2, y1, COL_RED); draw_line(x2, y1, x2, y0, COL_RED); draw_line(x2, y0, x3, y0, COL_RED); draw_line(x3, y1, x3, y0, COL_RED); draw_line(x3, y1, x4, y1, COL_RED); draw_line(x4, y1, x4, y0, COL_RED); draw_line(x4, y0, x5, y0, COL_RED); // Bounding box. draw_rect(x, y - 3, x + 24, y + 18, COL_RED); } // Param box. { size_t x = PARAMS_START_X + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 25; draw_line(x, y + 7, x + 2, y + 7, COL_FG); draw_line(x + 22, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("duty", x + 3, y + 3, 4, COL_FG); switch (params->duty_cycle) { case 0: { txt_drawf("12", x + 6, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("25", x + 6, y + 10, 6, COL_FG); } break; case 2: { txt_drawf("50", x + 6, y + 10, 6, COL_FG); } break; case 3: { txt_drawf("75", x + 6, y + 10, 6, COL_FG); } break; } } } // Envelope. { // Env. drawing. { // Bounding box. { size_t x0 = PARAMS_START_X + 31 + x_offset; size_t y0 = PARAMS_START_Y + PARAMS_H - 46; size_t x1 = x0 + 79; size_t y1 = y0 + 21; draw_rect(x0, y0, x1, y1, COL_CYAN); } size_t x = PARAMS_START_X + 42 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 43; size_t x0 = x; size_t y0 = y + 15 - params->env_volume; size_t x1 = x + 8 * params->env_time; size_t y1 = params->env_direction == 0 ? y + 15 : y; size_t x2 = x + 8 * 7 + 1; size_t y2 = y1; // Env. if (params->env_time == 0) { draw_line(x1, y0, x2, y0, COL_CYAN); } else { draw_line(x0, y0, x1, y1, COL_CYAN); draw_line(x1, y1, x2, y2, COL_CYAN); } } // Env. volume. { size_t x = PARAMS_START_X + 31 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 25; draw_line(x, y + 7, x + 4, y + 7, COL_FG); draw_line(x + 20, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("vol", x + 5, y + 3, 4, COL_FG); switch (params->env_volume) { case 0: { txt_drawf("0", x + 9, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("6", x + 9, y + 10, 6, COL_FG); } break; case 2: { txt_drawf("13", x + 6, y + 10, 6, COL_FG); } break; case 3: { txt_drawf("20", x + 6, y + 10, 6, COL_FG); } break; case 4: { txt_drawf("26", x + 6, y + 10, 6, COL_FG); } break; case 5: { txt_drawf("33", x + 6, y + 10, 6, COL_FG); } break; case 6: { txt_drawf("40", x + 6, y + 10, 6, COL_FG); } break; case 7: { txt_drawf("46", x + 6, y + 10, 6, COL_FG); } break; case 8: { txt_drawf("53", x + 6, y + 10, 6, COL_FG); } break; case 9: { txt_drawf("60", x + 6, y + 10, 6, COL_FG); } break; case 10: { txt_drawf("66", x + 6, y + 10, 6, COL_FG); } break; case 11: { txt_drawf("73", x + 6, y + 10, 6, COL_FG); } break; case 12: { txt_drawf("80", x + 6, y + 10, 6, COL_FG); } break; case 13: { txt_drawf("86", x + 6, y + 10, 6, COL_FG); } break; case 14: { txt_drawf("93", x + 6, y + 10, 6, COL_FG); } break; case 15: { txt_drawf("100", x + 3, y + 10, 6, COL_FG); } break; } } // Env. direction { size_t x = PARAMS_START_X + 59 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 25; draw_line(x, y + 7, x + 4, y + 7, COL_FG); draw_line(x + 20, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("dir", x + 5, y + 3, 4, COL_FG); char arr_up[2] = { 0x19, 0 }; char arr_down[2] = { 0x18, 0 }; switch (params->env_direction) { case 0: { txt_drawf(arr_up, x + 9, y + 11, 6, COL_FG); } break; case 1: { txt_drawf(arr_down, x + 9, y + 11, 6, COL_FG); } break; } } // Env. time. { size_t x = PARAMS_START_X + 87 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 25; draw_line(x, y + 7, x + 2, y + 7, COL_FG); draw_line(x + 22, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("time", x + 3, y + 3, 4, COL_FG); switch (params->env_time) { case 0: { txt_drawf("0", x + 9, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("14", x + 6, y + 10, 6, COL_FG); } break; case 2: { txt_drawf("28", x + 6, y + 10, 6, COL_FG); } break; case 3: { txt_drawf("42", x + 6, y + 10, 6, COL_FG); } break; case 4: { txt_drawf("57", x + 6, y + 10, 6, COL_FG); } break; case 5: { txt_drawf("71", x + 6, y + 10, 6, COL_FG); } break; case 6: { txt_drawf("85", x + 6, y + 10, 6, COL_FG); } break; case 7: { txt_drawf("100", x + 3, y + 10, 6, COL_FG); } break; } } } // Sweep number. if (sweep) { size_t x = PARAMS_START_X + 118; size_t y = PARAMS_START_Y + PARAMS_H - 25; draw_line(x, y + 7, x + 4, y + 7, COL_FG); draw_line(x + 20, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("num", x + 5, y + 3, 4, COL_FG); switch (params->sweep_number) { case 0: { txt_drawf("0", x + 9, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("1", x + 9, y + 10, 6, COL_FG); } break; case 2: { txt_drawf("2", x + 9, y + 10, 6, COL_FG); } break; case 3: { txt_drawf("3", x + 9, y + 10, 6, COL_FG); } break; case 4: { txt_drawf("4", x + 9, y + 10, 6, COL_FG); } break; case 5: { txt_drawf("5", x + 9, y + 10, 6, COL_FG); } break; case 6: { txt_drawf("6", x + 9, y + 10, 6, COL_FG); } break; case 7: { txt_drawf("7", x + 9, y + 10, 6, COL_FG); } break; } } // Sweep time. if (sweep) { size_t x = PARAMS_START_X + 146; size_t y = PARAMS_START_Y + PARAMS_H - 25; draw_line(x, y + 7, x + 2, y + 7, COL_FG); draw_line(x + 22, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("time", x + 3, y + 3, 4, COL_FG); switch (params->sweep_time) { case 0: { txt_drawf("0", x + 9, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("1", x + 9, y + 10, 6, COL_FG); } break; case 2: { txt_drawf("2", x + 9, y + 10, 6, COL_FG); } break; case 3: { txt_drawf("3", x + 9, y + 10, 6, COL_FG); } break; case 4: { txt_drawf("4", x + 9, y + 10, 6, COL_FG); } break; case 5: { txt_drawf("5", x + 9, y + 10, 6, COL_FG); } break; case 6: { txt_drawf("6", x + 9, y + 10, 6, COL_FG); } break; case 7: { txt_drawf("7", x + 9, y + 10, 6, COL_FG); } break; } } // Sweep direction. if (sweep) { size_t x = PARAMS_START_X + 132; size_t y = PARAMS_START_Y + PARAMS_H - 45; draw_line(x, y + 7, x + 4, y + 7, COL_FG); draw_line(x + 20, y + 7, x + 24, y + 7, COL_FG); draw_line(x, y + 8, x, y + 19, COL_FG); draw_line(x + 24, y + 8, x + 24, y + 19, COL_FG); draw_line(x, y + 20, x + 24, y + 20, COL_FG); txt_drawf_small("dir", x + 5, y + 3, 4, COL_FG); char arr_up[2] = { 0x19, 0 }; char arr_down[2] = { 0x18, 0 }; switch (params->sweep_direction) { case 0: { txt_drawf(arr_up, x + 9, y + 11, 6, COL_FG); } break; case 1: { txt_drawf(arr_down, x + 9, y + 11, 6, COL_FG); } break; } } // Labels. { size_t x = PARAMS_START_X + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 43; txt_drawf_small("shape", x + 1, y - 12, 4, COL_FG); txt_drawf_small("envelope", x + 54, y - 12, 4, COL_FG); if (sweep) { txt_drawf_small("sweep", x + 133, y - 12, 4, COL_FG); } } } void draw_parameters(void) { clear_parameters(); switch (channel_selection_loc) { case 0: { draw_parameters_square(&ch1.params[trig_selection_loc], true); } break; case 1: { draw_parameters_square(&ch2.params[trig_selection_loc], false); } break; case 2: { draw_parameters_wave(); } 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_sq1(void); void handle_param_selection_sq2(void); void handle_param_selection_wave(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_sq1(void) { // Go back to trigger selection. if (key_tap(KEY_A)) { draw_params_cursor(param_selection_loc, COL_BG); input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURSOR); } // Cursor movement. if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { int inc = 0; int loc = param_selection_loc; if (key_tap(KEY_RIGHT)) { if (loc < 5) { inc = 1; } else if (loc == 6) { inc = -1; } } else { if (loc <= 5) { inc = -1; } else if (loc == 6) { inc = -2; } } draw_params_cursor(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 6); draw_params_cursor(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 == 4) { inc = 2; } else if (loc == 5) { inc = 1; } else if (loc == 6) { inc = -1; } } else { if (loc == 4) { inc = 2; } else if (loc == 5) { inc = 1; } else if (loc == 6) { inc = -1; } } draw_params_cursor(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 6); draw_params_cursor(param_selection_loc, COL_CURSOR); } // Adjust parameter. if (key_tap(KEY_R) || key_tap(KEY_L)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } ChannelSquareParams *params = &ch1.params[trig_selection_loc]; 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: { params->env_direction ^= 1; } break; case 3: { params->env_time = CLAMP(params->env_time + inc, 0, 7); } break; case 4: { params->sweep_number = CLAMP(params->sweep_number + inc, 0, 7); } break; case 5: { params->sweep_time = CLAMP(params->sweep_time + inc, 0, 7); } break; case 6: { params->sweep_direction ^= 1; } break; } draw_parameters(); draw_params_cursor(param_selection_loc, COL_CURSOR); } } void handle_param_selection_sq2(void) { // Go back to trigger selection. if (key_tap(KEY_A)) { draw_params_cursor(param_selection_loc, COL_BG); input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURSOR); } // Cursor movement. if (key_tap(KEY_LEFT) || key_tap(KEY_RIGHT)) { int inc = 0; int loc = param_selection_loc; if (key_tap(KEY_RIGHT)) { inc = 1; } else { inc = -1; } draw_params_cursor(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 3); draw_params_cursor(param_selection_loc, COL_CURSOR); } // Adjust parameter. if (key_tap(KEY_R) || key_tap(KEY_L)) { int inc; if (key_tap(KEY_L)) { inc = -1; } else { inc = 1; } ChannelSquareParams *params = &ch2.params[trig_selection_loc]; 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: { params->env_direction ^= 1; } break; case 3: { params->env_time = CLAMP(params->env_time + inc, 0, 7); } break; } draw_parameters(); draw_params_cursor(param_selection_loc, COL_CURSOR); } } void handle_param_selection_wave(void) { // Go back to trigger selection. if (key_tap(KEY_A)) { draw_params_cursor(param_selection_loc, COL_BG); input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURSOR); } // Cursor movement. 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(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 73); draw_params_cursor(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(param_selection_loc, COL_BG); param_selection_loc = CLAMP(loc + inc, 0, 73); draw_params_cursor(param_selection_loc, COL_CURSOR); } // Adjust parameter. 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_sq1; } break; case 1: { input_handler = handle_param_selection_sq2; } break; case 2: { input_handler = handle_param_selection_wave; } 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_assets(void) { unpack_tiles(note_names, ASSETS_NOTE_NAMES, N_TILES_NOTE_NAMES); unpack_tiles(channel_buttons, ASSETS_CHANNEL_BUTTONS, N_TILES_CHAN_BTSN); unpack_tiles(default_wave_buttons, ASSETS_DEFAULT_WAVES, N_TILES_WAVE_BTNS); } void sequencer_init(void) { // Unpack non-sprite tiles directly on the VRAM. load_assets(); // 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. 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; }