#include "rng.c" #include "text.h" void sram_read(u8 *dst, u16 pos, u16 n_bytes) { for (size_t i = 0; i < n_bytes; ++i) { dst[i] = SRAM[pos + i]; } } void sram_write(u8 *src, u16 pos, u16 n_bytes) { for (size_t i = 0; i < n_bytes; ++i) { SRAM[pos + i] = src[i]; } } // TODO // - Preview sound keys? // - Copy paste trigs/notes/params // - Finish noise channel (parameter editing) void set_time(int bpm); // // 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_WAVE_A COL_RED #define COL_WAVE_B COL_CYAN #define CHAN_W 19 #define CHAN_H 8 #define CHAN_START_X 29 #define CHAN_START_Y 92 #define CHAN_OFFSET_Y 12 #define TRIG_W 15 #define TRIG_H 24 #define TRIG_START_X 58 #define TRIG_START_Y 92 #define TRIG_OFFSET_X (TRIG_W + 3) #define TRIG_OFFSET_Y (TRIG_H + 7) #define PIANO_W 170 #define PIANO_H 20 #define PIANO_START_X 29 #define PIANO_START_Y 65 #define PIANO_NOTE_W 2 #define PARAMS_W 170 #define PARAMS_H 64 #define PARAMS_START_X 29 #define PARAMS_START_Y 1 #define R_SIDEBAR_X ((TRIG_START_X) + (TRIG_OFFSET_X) * 8 + 4) #define L_SIDEBAR_X ((CHAN_START_X) - 26) #define PAT_START_X (L_SIDEBAR_X + 5) #define PAT_START_Y 18 #define PAT_W 14 #define PAT_H 12 #define PAT_OFFSET_Y 17 #define R_COL_W 24 #define BPM_START_X (R_SIDEBAR_X) #define BPM_START_Y (TRIG_START_Y + TRIG_H + 9) #define BPM_H 22 #define PLAY_START_X (R_SIDEBAR_X) #define PLAY_START_Y (TRIG_START_Y) #define STOP_START_X (R_SIDEBAR_X) #define STOP_START_Y (TRIG_START_Y + 14) #define PLAY_STOP_H (10) #define BANK_START_X (R_SIDEBAR_X + 5) #define BANK_START_Y (PAT_START_Y) #define SEQ_N_CHANNELS 4 // // 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. // const int default_bpm = 75; static int step_counter = 0; int trig_selection_loc = 0; int param_selection_loc = 0; int channel_selection_loc = 0; int pattern_selection_loc = 0; int right_col_selection_loc = 0; int play_status = 0; static int current_pattern = 0; static int next_pattern = 0; static int current_bank = 0; enum RIGHT_COL_LOC { R_COL_BPM = 0, R_COL_STOP = 1, R_COL_PLAY = 2, R_COL_BANK_D = 3, R_COL_BANK_C = 4, R_COL_BANK_B = 5, R_COL_BANK_A = 6, }; 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 ChannelNoiseParams { u8 env_volume; u8 env_time; u8 env_direction; u8 bit_mode; } ChannelNoiseParams; typedef struct ChannelSquare { bool active; TriggerNote notes[16]; ChannelSquareParams params[16]; } ChannelSquare; typedef struct ChannelWave { bool active; TriggerNote notes[16]; ChannelWaveParams params[16]; } ChannelWave; typedef struct ChannelNoise { bool active; TriggerNote notes[16]; ChannelNoiseParams params[16]; } ChannelNoise; const ChannelSquare default_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, }; const ChannelSquare default_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, }; const ChannelWave default_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, }; const ChannelNoise default_ch4 = { .notes = { {true, NOTE_C_4}, {false, NOTE_D_4}, {false, NOTE_E_4}, {false, NOTE_F_4}, {false, NOTE_G_4}, {false, NOTE_A_4}, {false, NOTE_B_4}, {false, NOTE_C_5}, {false, NOTE_C_4}, {false, NOTE_C_4}, {false, NOTE_C_4}, {false, NOTE_C_4}, {false, NOTE_C_4}, {false, NOTE_C_4}, {false, NOTE_C_4}, {false, NOTE_C_4}, }, .params = { {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, {0xF, 0x2, 0, 0}, }, .active = true, }; typedef struct Metadata { u8 blank; u32 magic; int current_bank; int current_pattern; } Metadata; static Metadata metadata = {0}; typedef struct Pattern { ChannelSquare ch1; ChannelSquare ch2; ChannelWave ch3; ChannelNoise ch4; int bpm; u8 bank; } Pattern; static Pattern patterns[8] = { {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, {default_ch1, default_ch2, default_ch3, default_ch4, default_bpm, 0}, }; void save_bank(int i) { sram_write(&patterns, sizeof(Metadata) + i * sizeof(patterns), sizeof(patterns)); } void load_bank(int i) { sram_read(&patterns, sizeof(Metadata) + i * sizeof(patterns), sizeof(patterns)); } // // Channel render functions. // 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 = patterns[pattern_selection_loc].ch1.active; } break; case 1: { active = patterns[pattern_selection_loc].ch2.active; } break; case 2: { active = patterns[pattern_selection_loc].ch3.active; } break; case 3: { active = patterns[pattern_selection_loc].ch4.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); } // // Trigger render functions. // 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 - 4; 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 = patterns[pattern_selection_loc].ch1.notes[i]; } break; case 1: { trig = patterns[pattern_selection_loc].ch2.notes[i]; } break; case 2: { trig = patterns[pattern_selection_loc].ch3.notes[i]; } break; case 3: { trig = patterns[pattern_selection_loc].ch4.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_right_col_cursor(u8 clr) { size_t x0 = 0; size_t x1 = 0; size_t y = 0; switch (right_col_selection_loc) { case R_COL_BPM: { x0 = BPM_START_X; x1 = x0 + R_COL_W; y = BPM_START_Y + BPM_H + 2; } break; case R_COL_STOP: { x0 = STOP_START_X; x1 = x0 + R_COL_W; y = STOP_START_Y + PLAY_STOP_H + 2; } break; case R_COL_PLAY: { x0 = PLAY_START_X; x1 = x0 + R_COL_W; y = PLAY_START_Y + PLAY_STOP_H + 2; } break; case R_COL_BANK_A: { x0 = BANK_START_X; x1 = x0 + PAT_W; y = BANK_START_Y + PAT_H + 2; } break; case R_COL_BANK_B: { x0 = BANK_START_X; x1 = x0 + PAT_W; y = BANK_START_Y + PAT_H + 2 + 1 * PAT_OFFSET_Y; } break; case R_COL_BANK_C: { x0 = BANK_START_X; x1 = x0 + PAT_W; y = BANK_START_Y + PAT_H + 2 + 2 * PAT_OFFSET_Y; } break; case R_COL_BANK_D: { x0 = BANK_START_X; x1 = x0 + PAT_W; y = BANK_START_Y + PAT_H + 2 + 3 * PAT_OFFSET_Y; } break; } draw_line(x0, y, x1, y, clr); } void draw_current_step(u8 col) { size_t offset_x = TRIG_OFFSET_X * (step_counter % 8); size_t offset_y = step_counter < 8 ? 2 : 2 + TRIG_OFFSET_Y; size_t x0 = TRIG_START_X + 3 + offset_x; size_t x1 = TRIG_START_X - 3 + TRIG_W + offset_x; size_t y = TRIG_START_Y - 4 + TRIG_H + offset_y; draw_line(x0, y, x1, y, col); } void draw_bank_buttons() { size_t x = BANK_START_X; size_t y = BANK_START_Y; txt_drawf_small("BANK", x - 2, y - 10, 4, COL_FG); char bank_names[] = { 'A', 'B', 'C', 'D', }; for (int i = 0; i < 4; i++) { int color = COL_GREY; if (i == current_bank) { color = COL_FG; } draw_filled_rect(x, y, x + PAT_W, y + PAT_H, COL_BG); draw_rect(x, y, x + PAT_W, y + PAT_H, color); txt_drawc(bank_names[i], x + 4, y + 2, 6, color); y += PAT_OFFSET_Y; } } void draw_pattern_buttons() { size_t x = PAT_START_X; size_t y = PAT_START_Y; txt_drawf_small("PAT", x, y - 10, 4, COL_FG); char pat_names[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', }; for (int i = 0; i < 8; i++) { int color = COL_GREY; if (i == current_pattern) { color = COL_FG; } if (i == next_pattern && current_pattern != next_pattern) { color = COL_BLUE; } draw_filled_rect(x, y, x + PAT_W, y + PAT_H, COL_BG); draw_rect(x, y, x + PAT_W, y + PAT_H, color); txt_drawc(pat_names[i], x + 4, y + 2, 6, color); y += PAT_OFFSET_Y; } } void draw_pattern_cursor(size_t i, u8 clr) { size_t offset_x = 0; size_t offset_y = PAT_H + i * PAT_OFFSET_Y + 2; size_t x0 = PAT_START_X + offset_x; size_t x1 = PAT_START_X + offset_x + PAT_W; size_t y = PAT_START_Y + offset_y; draw_line(x0, y, x1, y, clr); } void draw_play() { size_t x = PLAY_START_X; size_t y = PLAY_START_Y; draw_filled_rect(x, y, x + R_COL_W, y + PLAY_STOP_H, COL_BG); draw_rect(x, y, x + R_COL_W, y + PLAY_STOP_H, COL_CYAN); if (play_status == 1) { // Pause button draw_filled_rect(x + 10, y + 3, x + 11, y + 7, COL_CYAN); draw_filled_rect(x + 13, y + 3, x + 14, y + 7, COL_CYAN); } else { // Play button x += 1; draw_line(x + 10, y + 2, x + 10, y + 8, COL_CYAN); draw_line(x + 11, y + 3, x + 11, y + 7, COL_CYAN); draw_line(x + 12, y + 4, x + 12, y + 6, COL_CYAN); draw_line(x + 13, y + 5, x + 13, y + 5, COL_CYAN); } } void draw_stop() { size_t x = STOP_START_X; size_t y = STOP_START_Y; draw_rect(x, y, x + R_COL_W, y + PLAY_STOP_H, COL_RED); draw_filled_rect(x + 10, y + 3, x + 14, y + 7, COL_RED); } void draw_bpm() { size_t x = BPM_START_X; size_t y = BPM_START_Y; // Draw bounding box. draw_filled_rect(x, y, x + R_COL_W, y + BPM_H, COL_BG); draw_rect(x, y, x + R_COL_W, y + BPM_H, COL_FG); draw_line(x + 5, y, x + 19, y, COL_BG); txt_drawf_small("BPM", x + 5, y - 4, 4, COL_FG); // Make sure its horizontally centered if only 2 digits int bpm = patterns[pattern_selection_loc].bpm; if (bpm >= 100) { txt_drawf("%d", x + 3, y + 7, 6, COL_FG, bpm); } else { txt_drawf("%d", x + 6, y + 7, 6, COL_FG, bpm); } } 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); } } 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); } } 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_noise(size_t i, u8 clr) { u8 x_positions[] = { 0, // Bit mode. 31, // Env. Vol. 59, // Env. Direction. 87, // Env. Time. }; u8 y_positions[] = { 20, // Bit mode. 20, // Env. Vol. 20, // Env. Direction. 20, // Env. Time. }; size_t cursor_length = 24; size_t x = PARAMS_START_X + x_positions[i] + 30; 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: { draw_params_cursor_noise(i, clr); } 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. Pattern *pat = &patterns[pattern_selection_loc]; { u8 *wave_a = pat->ch3.params[trig_selection_loc].wave_a; u8 *wave_b = pat->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 (pat->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 (pat->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 - 44; 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 - 47; 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 - 44; 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 - 45; 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_noise(void) { size_t x_offset = 30; Pattern *pat = &patterns[pattern_selection_loc]; ChannelNoiseParams *params = &pat->ch4.params[trig_selection_loc]; // Bit mode. { // 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("mode", x + 3, y + 3, 4, COL_FG); switch (params->bit_mode) { case 0: { txt_drawf("A", x + 9, y + 10, 6, COL_FG); } break; case 1: { txt_drawf("B", x + 9, 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 - 47; 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 - 44; 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; } } } // Labels. { size_t x = PARAMS_START_X + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 45; txt_drawf_small("envelope", x + 54, y - 12, 4, COL_FG); } } void draw_parameters(void) { clear_parameters(); Pattern *pat = &patterns[pattern_selection_loc]; switch (channel_selection_loc) { case 0: { draw_parameters_square(&pat->ch1.params[trig_selection_loc], true); } break; case 1: { draw_parameters_square(&pat->ch2.params[trig_selection_loc], false); } break; case 2: { draw_parameters_wave(); } break; case 3: { draw_parameters_noise(); } break; } } void irq_timer(void) { if (current_pattern != next_pattern && step_counter == 0) { current_pattern = next_pattern; draw_pattern_buttons(); } Pattern *pat = &patterns[current_pattern]; set_time(pat->bpm); if (pat->ch1.active) { TriggerNote *trig = &pat->ch1.notes[step_counter]; ChannelSquareParams *params = &pat->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 (pat->ch2.active) { TriggerNote *trig = &pat->ch2.notes[step_counter]; ChannelSquareParams *params = &pat->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 (pat->ch3.active) { TriggerNote *trig = &pat->ch3.notes[step_counter]; ChannelWaveParams *params = &pat->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; } if (pat->ch4.active) { TriggerNote *trig = &pat->ch4.notes[step_counter]; ChannelNoiseParams *params = &pat->ch4.params[step_counter]; SOUND_NOISE_CTRL = SOUND_NOISE_ENV_VOL(params->env_volume) | SOUND_NOISE_ENV_TIME(params->env_time) | SOUND_NOISE_ENV_DIR(params->env_direction); if (trig->active) { static const u8 div_freq[] = { 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const u8 pre_freq[] = { 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; SOUND_NOISE_FREQ = SOUND_FREQ_RESET | SOUND_NOISE_PRESTEP_FREQ(pre_freq[trig->note]) | SOUND_NOISE_DIV_FREQ(div_freq[trig->note]) | SOUND_NOISE_COUNTER_STAGE(params->bit_mode) | SOUND_NOISE_TIMED_MODE(0); } else { SOUND_NOISE_FREQ = 0; } } else { SOUND_NOISE_CTRL = 0; SOUND_NOISE_FREQ = 0; } draw_current_step(COL_BG); step_counter = (step_counter + 1) % 16; draw_current_step(COL_RED); } 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) { Pattern *pat = &patterns[pattern_selection_loc]; switch (channel_selection_loc) { case 0: { return &pat->ch1.notes[trig_selection_loc]; } break; case 1: { return &pat->ch2.notes[trig_selection_loc]; } break; case 2: { return &pat->ch3.notes[trig_selection_loc]; } break; case 3: { return &pat->ch4.notes[trig_selection_loc]; } break; } return NULL; } // 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); void handle_trigger_selection(void); void handle_channel_selection(void); void handle_pattern_selection(void); void handle_param_selection_sq1(void); void handle_param_selection_sq2(void); void handle_param_selection_wave(void); void handle_param_selection_noise(void); void handle_right_col_selection(void); void handle_channel_selection(void) { if (key_tap(KEY_B)) { switch (channel_selection_loc) { case 0: { patterns[pattern_selection_loc].ch1.active ^= 1; } break; case 1: { patterns[pattern_selection_loc].ch2.active ^= 1; } break; case 2: { patterns[pattern_selection_loc].ch3.active ^= 1; } break; case 3: { patterns[pattern_selection_loc].ch4.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_CURSOR); TriggerNote *trig = get_current_trig(); draw_note(trig->note, COL_NOTE_PRESSED); draw_parameters(); } else if (key_tap(KEY_LEFT)) { input_handler = handle_pattern_selection; draw_channel_cursor(channel_selection_loc, COL_GREY); draw_pattern_cursor(pattern_selection_loc, COL_CURSOR); } 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 stop_playing(void) { play_status = 0; if (step_counter != 0) { draw_current_step(COL_BG); } step_counter = 0; TIMER_CTRL_0 = 0; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; SOUND_NOISE_CTRL = 0; draw_current_step(COL_RED); draw_play(); } void toggle_playing(void) { play_status ^= 1; if (step_counter != 0) { draw_current_step(COL_BG); } step_counter = 0; if ((TIMER_CTRL_0 & TIMER_CTRL_ENABLE) == 0) { set_time(patterns[current_pattern].bpm); } else { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; SOUND_NOISE_CTRL = 0; } draw_current_step(COL_RED); draw_play(); } void pause_playing(void) { play_status ^= 1; if ((TIMER_CTRL_0 & TIMER_CTRL_ENABLE) == 0) { set_time(patterns[current_pattern].bpm); } else { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; SOUND_NOISE_CTRL = 0; } draw_play(); } void select_bank(int i) { stop_playing(); save_bank(current_bank); metadata.current_pattern = current_pattern; metadata.current_bank = i; sram_write(&metadata, 0, sizeof(Metadata)); if (current_bank != i) { load_bank(i); } current_bank = i; draw_pattern_buttons(); draw_triggers(); draw_channels(); draw_bank_buttons(); } void handle_right_col_selection(void) { if (key_tap(KEY_LEFT)) { input_handler = handle_trigger_selection; switch (right_col_selection_loc) { case R_COL_BPM: { trig_selection_loc = 15; } break; case R_COL_PLAY : case R_COL_STOP: { trig_selection_loc = 7; } break; } draw_right_col_cursor(COL_BG); 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)) { if (right_col_selection_loc < R_COL_BANK_A) { draw_right_col_cursor(COL_BG); right_col_selection_loc++; draw_right_col_cursor(COL_CURSOR); } } else if (key_tap(KEY_DOWN)) { if (right_col_selection_loc > R_COL_BPM) { draw_right_col_cursor(COL_BG); right_col_selection_loc--; draw_right_col_cursor(COL_CURSOR); } } else if (key_tap(KEY_L)) { switch (right_col_selection_loc) { case R_COL_BPM: { if (patterns[pattern_selection_loc].bpm > 10) { patterns[pattern_selection_loc].bpm--; if ((TIMER_CTRL_0 & TIMER_CTRL_ENABLE) != 0 && current_pattern == pattern_selection_loc) { set_time(patterns[current_pattern].bpm); } draw_bpm(); } } break; } } else if (key_tap(KEY_R)) { switch (right_col_selection_loc) { case R_COL_BPM: { if (patterns[pattern_selection_loc].bpm < 300) { patterns[pattern_selection_loc].bpm++; if ((TIMER_CTRL_0 & TIMER_CTRL_ENABLE) != 0 && current_pattern == pattern_selection_loc) { set_time(patterns[current_pattern].bpm); } draw_bpm(); } } break; } } else if (key_tap(KEY_B)) { switch (right_col_selection_loc) { case R_COL_STOP: { toggle_playing(); } break; case R_COL_PLAY: { pause_playing(); } break; case R_COL_BANK_A: { select_bank(0); } break; case R_COL_BANK_B: { select_bank(1); } break; case R_COL_BANK_C: { select_bank(2); } break; case R_COL_BANK_D: { select_bank(3); } break; } } } void handle_pattern_selection(void) { if (key_tap(KEY_B)) { next_pattern = pattern_selection_loc; draw_pattern_buttons(); } if (key_tap(KEY_RIGHT)) { input_handler = handle_channel_selection; draw_channel_cursor(channel_selection_loc, COL_CURSOR); draw_pattern_cursor(pattern_selection_loc, COL_GREY); } else if (key_tap(KEY_UP)) { if (pattern_selection_loc > 0) { draw_pattern_cursor(pattern_selection_loc, COL_BG); pattern_selection_loc = pattern_selection_loc - 1; draw_pattern_cursor(pattern_selection_loc, COL_CURSOR); draw_triggers(); draw_bpm(); } } else if (key_tap(KEY_DOWN)) { if (pattern_selection_loc < 7) { draw_pattern_cursor(pattern_selection_loc, COL_BG); pattern_selection_loc = pattern_selection_loc + 1; draw_pattern_cursor(pattern_selection_loc, COL_CURSOR); draw_triggers(); draw_bpm(); } } } 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; } Pattern *pat = &patterns[pattern_selection_loc]; ChannelSquareParams *params = &pat->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; } Pattern *pat = &patterns[pattern_selection_loc]; ChannelSquareParams *params = &pat->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) { Pattern *pat = &patterns[pattern_selection_loc]; // 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 = &pat->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 = &pat->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 = &pat->ch3.params[trig_selection_loc].wave_a; u32 *wave_b = &pat->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 = &pat->ch3.params[trig_selection_loc].wave_mode; *wave_mode = CLAMP(*wave_mode + inc, 0, 2); } else if (param_selection_loc == 73) { u8 *wave_volume = &pat->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_param_selection_noise(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; } Pattern *pat = &patterns[pattern_selection_loc]; ChannelNoiseParams *params = &pat->ch4.params[trig_selection_loc]; switch (param_selection_loc) { case 0: { params->bit_mode = CLAMP(params->bit_mode + inc, 0, 1); } break; case 1: { params->env_volume = CLAMP(params->env_volume + inc, 0, 15); } break; case 2: { 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_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 && trig_selection_loc != 15) { 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 (trig_selection_loc == 7) { input_handler = handle_right_col_selection; right_col_selection_loc = R_COL_STOP; draw_trig_cursor(trig_selection_loc, COL_BG); draw_note(trig->note, COL_FG); draw_right_col_cursor(COL_CURSOR); } else if (trig_selection_loc == 15) { right_col_selection_loc = R_COL_BPM; input_handler = handle_right_col_selection; draw_trig_cursor(trig_selection_loc, COL_BG); draw_note(trig->note, COL_FG); draw_right_col_cursor(COL_CURSOR); } } 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; case 3: { input_handler = handle_param_selection_noise; } break; } draw_params_cursor(param_selection_loc, COL_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. toggle_playing(); } else if (key_tap(KEY_SELECT)) { // Play/pause. pause_playing(); } } 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(); // Load the previous bank from SRAM or initialize it if needed. sram_read(&metadata, 0, sizeof(Metadata)); if (metadata.magic != 0xbadd10de) { metadata.magic = 0xbadd10de; sram_write(&metadata, 0, sizeof(Metadata)); save_bank(0); save_bank(1); save_bank(2); save_bank(3); } else { current_bank = metadata.current_bank; current_pattern = metadata.current_pattern; next_pattern = metadata.current_pattern; pattern_selection_loc = current_pattern; load_bank(metadata.current_bank); } // Initialize background objects and sprites. draw_triggers(); draw_channels(); draw_piano(); TriggerNote *trig = get_current_trig(); draw_note(trig->note, COL_NOTE_PRESSED); // Draw screen border frame. draw_rect(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, 1); // Initialize input handler. input_handler = handle_trigger_selection; draw_trig_cursor(trig_selection_loc, COL_CURSOR); draw_channel_cursor(channel_selection_loc, COL_GREY); draw_pattern_cursor(pattern_selection_loc, COL_GREY); draw_current_step(COL_RED); draw_parameters(); draw_bpm(); draw_play(); draw_stop(); draw_pattern_buttons(); draw_bank_buttons(); // Initialize sound system. SOUND_STATUS = SOUND_ENABLE; SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1 | SOUND_SQUARE2 | SOUND_WAVE | SOUND_NOISE, 3); SOUND_DSOUND_MASTER = SOUND_DMG25; }