#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 // // 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 = 64; 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 ? 1 : 1 + 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; 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 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 until the default parameters, since those don't change. { 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 - 8; draw_filled_rect(x0, y0, x1, y1, COL_BG); } // 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_RED); draw_wave_pattern(wave_b, x + 70, y, COL_CYAN); // 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 - 20; 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 + 19, COL_FG); draw_line(x + 30, y + 5, x + 30, y + 19, COL_FG); draw_line(x, y + 20, x + 30, y + 20, 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 + 9, 6, COL_FG); } break; case 1: { txt_drawf("B", x + 12, y + 9, 6, COL_FG); } break; case 2: { txt_drawf("A+B", x + 6, y + 9, 6, COL_FG); } break; case 3: { txt_drawf("B+A", x + 6, y + 9, 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) { // 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); dma_copy(SOUND_WAVE_RAM, params->wave_a, 16, 3); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); dma_copy(SOUND_WAVE_RAM, params->wave_b, 16, 3); switch (params->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 (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_GREY); draw_trig_cursor(trig_selection_loc, COL_BLUE); TriggerNote *trig = get_current_trig(); draw_note(trig->note, COL_BLUE); 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_BLUE); 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_BLUE); 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_BLUE); } // // 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_BLUE); } // // 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)) { // TODO: draw param cursor. input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_BLUE); } // 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){ // dma_copy(&trig->wave_a, &sine_wave, 16); // } else if (param_selection_loc == 65){ // dma_copy(&trig->wave_a, &saw_wave, 16); // } else if (param_selection_loc == 66){ // dma_copy(&trig->wave_a, &square_wave, 16); // } else if (param_selection_loc == 67){ // u32 rand_wave[4] = { // rng32(), rng32(), rng32(), rng32(), // }; // dma_copy(&trig->wave_a, &rand_wave, 16); // } else if (param_selection_loc == 68){ // dma_copy(&trig->wave_b, &sine_wave, 16); // } else if (param_selection_loc == 69){ // dma_copy(&trig->wave_b, &saw_wave, 16); // } else if (param_selection_loc == 70){ // dma_copy(&trig->wave_b, &square_wave, 16); // } else if (param_selection_loc == 71){ // u32 rand_wave[4] = { // rng32(), rng32(), rng32(), rng32(), // }; // dma_copy(&trig->wave_b, &rand_wave, 16); // } // } } 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_BLUE); 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_BLUE); 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_BLUE); 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_BLUE); draw_note(trig->note, COL_BLUE); 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_BLUE); draw_note(trig->note, COL_BLUE); 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_BLUE); draw_note(trig->note, COL_BLUE); 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; } // TODO: draw param cursor. draw_trig_cursor(trig_selection_loc, COL_GREY); } } 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_BLUE); // Initialize input handler. channel_selection_loc = 2; // DEBUG: Starting on CH3 input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, 3); draw_channel_cursor(channel_selection_loc, 5); 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; }