// // Channel render functions. // void draw_param_stub(size_t idx, u8 color) { if (idx >= 10) { return; } u8 x0 = PARAMS_START_X + (idx % 5) * PARAMS_BOX_OFFSET_X; u8 x1 = x0 + PARAMS_BOX_W; u8 y0 = PARAMS_START_Y; if (idx >= 5) { y0 += PARAMS_BOX_OFFSET_Y; } u8 y1 = y0 + PARAMS_BOX_H; for (size_t i = 0; i < PARAMS_BOX_W; i += 2) { draw_pixel(x0 + i + 1, y0, color); draw_pixel(x0 + i + 1, y1, color); } for (size_t i = 0; i < PARAMS_BOX_H; i += 2) { draw_pixel(x0, y0 + i + 1, color); draw_pixel(x1, y0 + i + 1, color); } } void draw_channel_sprite(size_t x, size_t y, u8 clr, u8 idx) { draw_icn(x, y, &ch_btn_sprite[0 + 6 * idx], clr, 0, 0); draw_icn(x + 8, y, &ch_btn_sprite[2 + 6 * idx], clr, 0, 0); draw_icn(x + 16, y, &ch_btn_sprite[4 + 6 * idx], clr, 0, 0); } void draw_channels(void) { // NOTE: Different channel colors can be interesting. u8 colors[] = { 1, 1, 1, 1, }; 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 ? colors[i] : COL_OFF; size_t y = CHAN_START_Y + i * CHAN_OFFSET_Y; draw_channel_sprite(CHAN_START_X, y, clr, i); } } 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; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x0, y, x1, y, clr); draw_line(x0, y + 1, x1, y + 1, clr); } break; default: { draw_line(x0, y, x1, y, clr); } break; } } // // 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; const u32 *tile = ¬e_name_sprites[4 * trig.note]; draw_icn(x, y, &tile[0], COL_FG, 1, 0); draw_icn(x + 8, y, &tile[2], COL_FG, 1, 0); } 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; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x0, y, x1, y, clr); draw_line(x0, y + 1, x1, y + 1, clr); } break; default: { draw_line(x0, y, x1, y, clr); } break; } } void draw_right_col_cursor(int i, u8 clr) { size_t x0 = 0; size_t x1 = 0; size_t y = 0; switch (i) { case R_COL_BPM: { x0 = BPM_START_X; x1 = x0 + R_COL_W; y = BPM_START_Y + BPM_H + 0; } break; case R_COL_STOP: { x0 = STOP_START_X; x1 = x0 + PLAY_STOP_W; y = STOP_START_Y + PLAY_STOP_H + 2; } break; case R_COL_PLAY: { x0 = PLAY_START_X; x1 = x0 + PLAY_STOP_W; y = PLAY_START_Y + PLAY_STOP_H + 2; } break; case R_COL_SETTINGS: { x0 = SETTINGS_START_X + 2; x1 = x0 + R_COL_W - 4; y = SETTINGS_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; } switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x0, y, x1, y, clr); draw_line(x0, y + 1, x1, y + 1, clr); } break; default: { draw_line(x0, y, x1, y, clr); } break; } } void draw_current_step(u8 step, u8 clr) { size_t offset_x = TRIG_OFFSET_X * (step % 8); size_t offset_y = step < 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 - 5 + TRIG_H + offset_y; draw_line(x0, y, x1, y, clr); } void draw_bank_buttons() { size_t x = BANK_START_X; size_t y = BANK_START_Y; txt_drawf_small("BANK", x - 2, y - 10, COL_FG); char bank_names[] = { 'A', 'B', 'C', 'D', }; for (int i = 0; i < 4; i++) { int color = COL_OFF; 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 + 1, color); y += PAT_OFFSET_Y; } } void draw_pattern_buttons() { // Clear patterns. size_t x = PAT_START_X; size_t y = PAT_START_Y; draw_filled_rect(x, y, x + PAT_W, y + PAT_H * 8, COL_BG); txt_drawf_small("PAT", x, y - 10, COL_FG); char pat_names[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', }; for (int i = 0; i < 8; i++) { int color = COL_OFF; if (i == current_pattern) { color = COL_FG; } if (i == next_pattern && current_pattern != next_pattern) { color = COL_ACC_0; } draw_rect(x, y, x + PAT_W, y + PAT_H, color); txt_drawc(pat_names[i], x + 4, y + 1, 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; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x0, y, x1, y, clr); draw_line(x0, y + 1, x1, y + 1, clr); } break; default: { draw_line(x0, y, x1, y, clr); } break; } } void draw_play() { size_t x = PLAY_START_X; size_t y = PLAY_START_Y; draw_filled_rect(x, y, x + PLAY_STOP_W, y + PLAY_STOP_H, COL_BG); draw_rect(x, y, x + PLAY_STOP_W, y + PLAY_STOP_H, COL_ACC_2); size_t x_btn = x + PLAY_STOP_W / 2 - 1; if (play_status == 1) { // Pause button draw_filled_rect(x_btn - 1, y + 3, x_btn, y + 7, COL_ACC_2); draw_filled_rect(x_btn + 2, y + 3, x_btn + 3, y + 7, COL_ACC_2); } else { // Play button x += 1; draw_line(x_btn + 0, y + 2, x_btn + 0, y + 8, COL_ACC_2); draw_line(x_btn + 1, y + 3, x_btn + 1, y + 7, COL_ACC_2); draw_line(x_btn + 2, y + 4, x_btn + 2, y + 6, COL_ACC_2); draw_line(x_btn + 3, y + 5, x_btn + 3, y + 5, COL_ACC_2); } } void draw_settings() { size_t x = SETTINGS_START_X; size_t y = SETTINGS_START_Y; draw_rect(x + 2, y, x + R_COL_W - 2, y + PLAY_STOP_H, COL_OFF); draw_line(x + 6, y + 4, x + 9, y + 4, COL_OFF); draw_line(x + 6, y + 6, x + 9, y + 6, COL_OFF); draw_line(x + 15, y + 4, x + 18, y + 4, COL_OFF); draw_line(x + 15, y + 6, x + 18, y + 6, COL_OFF); draw_line(x + 9, y + 5, x + 15, y + 5, COL_OFF); } void draw_stop() { size_t x = STOP_START_X; size_t y = STOP_START_Y; size_t x_btn = x + PLAY_STOP_W / 2 - 2; draw_rect(x, y, x + PLAY_STOP_W, y + PLAY_STOP_H, COL_ACC_1); draw_filled_rect(x_btn, y + 3, x_btn + 4, y + 7, COL_ACC_1); draw_settings(); } void draw_bpm() { size_t x = BPM_START_X; size_t y = BPM_START_Y + 2; // Draw bounding box. draw_filled_rect(x, y, x + R_COL_W, y + BPM_H - 4, COL_BG); draw_rect(x, y, x + R_COL_W, y + BPM_H - 4, COL_FG); draw_line(x + 5, y, x + 19, y, COL_BG); txt_drawf_small("BPM", x + 5, y - 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 + 5, COL_FG, bpm); } else { txt_drawf("%d", x + 6, y + 5, 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 base_x = PIANO_START_X + 2; size_t base_y0 = PIANO_START_Y + 2; size_t base_y1 = PIANO_START_Y - 2; size_t x0 = 0; size_t y0 = 0; size_t x1 = 0; size_t y1 = 0; switch (value) { // White notes. case 0:{ x0 = base_x + octave * 28; x1 = x0 + 1; y0 = base_y0; y1 = base_y1 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 2; x1 = x0; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 2:{ x0 = base_x + octave * 28 + 5; x1 = x0; y0 = base_y0; y1 = base_y1 + PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 4; x1 = x0 + 2; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 4:{ x0 = base_x + octave * 28 + 9; x1 = x0 + 1; y0 = base_y0; y1 = base_y1 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 8; x1 = x0 + 2; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 5:{ x0 = base_x + octave * 28 + 12; x1 = x0 + 1; y0 = base_y0; y1 = base_y1 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 14; x1 = x0; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 7:{ x0 = base_x + octave * 28 + 17; x1 = x0; y0 = base_y0; y1 = base_y1 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 16; x1 = x0 + 2; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 9:{ x0 = base_x + octave * 28 + 21; x1 = x0; y0 = base_y0; y1 = base_y1 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 20; x1 = x0 + 2; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; case 11: { x0 = base_x + octave * 28 + 25; x1 = x0 + 1; y0 = base_y0; y1 = base_y1 + PIANO_H; draw_filled_rect(x0, y0, x1, y1, clr); x0 = base_x + octave * 28 + 24; x1 = x0 + 2; y0 = base_y1 + PIANO_BLACK_H; y1 = y0 + PIANO_H - PIANO_BLACK_H; draw_filled_rect(x0, y0, x1, y1, clr); } break; default: { if (clr == COL_FG) { clr = COL_BG; } y0 = base_y0; y1 = base_y1 + PIANO_BLACK_H - 2; switch (value) { case 1: { x0 = base_x + octave * 28 + 3; } break; case 3: { x0 = base_x + octave * 28 + 7; } break; case 6: { x0 = base_x + octave * 28 + 15; } break; case 8: { x0 = base_x + octave * 28 + 19; } break; case 10: { x0 = base_x + 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, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 32 half bytes (Wave B) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // Default wave A. 21, 21, 21, 21, // Default wave B. 21, 21, 21, 21, // Mode selection. 21, // Volume selection. 1, }; 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 - 25 + y_positions[i]; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x, y, x + cursor_length, y, clr); draw_line(x, y + 1, x + cursor_length, y + 1, clr); } break; default: { draw_line(x, y, x + cursor_length, y, clr); } break; } } 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 - 24 + y_positions[i]; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x, y, x + cursor_length, y, clr); draw_line(x, y + 1, x + cursor_length, y + 1, clr); } break; default: { draw_line(x, y, x + cursor_length, y, clr); } break; } } 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 - 24 + y_positions[i]; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x, y, x + cursor_length, y, clr); draw_line(x, y + 1, x + cursor_length, y + 1, clr); } break; default: { draw_line(x, y, x + cursor_length, y, clr); } break; } } 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; draw_filled_rect(x0, y0, x1, y1, COL_BG); } IWRAM_CODE void draw_parameters_wave(ChannelWaveParams *params, bool global) { u8 col_fg = COL_FG; if (global && input_handler == handle_channel_selection) { col_fg = COL_OFF; } // Draw current wave data. { u8 *wave_a = params->wave_a; u8 *wave_b = params->wave_b; size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y + 8; // Wave Patterns. draw_wave_pattern(wave_a, x, y + 1, COL_ACC_1); draw_wave_pattern(wave_b, x + 70, y + 1, COL_ACC_2); // Wave text. x -= 2; txt_drawf_small("%02x%02x%02x%02x", x, y + 19, col_fg, wave_a[0], wave_a[1], wave_a[2], wave_a[3]); txt_drawf_small("%02x%02x%02x%02x", x + 34, y + 19, col_fg, wave_a[4], wave_a[5], wave_a[6], wave_a[7]); txt_drawf_small("%02x%02x%02x%02x", x, y + 28, 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, 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 + 19, col_fg, wave_b[0], wave_b[1], wave_b[2], wave_b[3]); txt_drawf_small("%02x%02x%02x%02x", x + 34, y + 19, col_fg, wave_b[4], wave_b[5], wave_b[6], wave_b[7]); txt_drawf_small("%02x%02x%02x%02x", x, y + 28, 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, col_fg, wave_b[12], wave_b[13], wave_b[14], wave_b[15]); } // Draw default wave buttons. { const u32 *tile = default_wave_buttons; size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y + PARAMS_H - 13; for (size_t i = 0, k = 0; i < 4 * 2; i += 2, k++) { draw_icn(x + 17 * k , y, &tile[i * 2 + 0], col_fg, 1, 0); draw_icn(x + 17 * k + 8, y, &tile[i * 2 + 2], col_fg, 1, 0); } for (size_t i = 0, k = 0; i < 4 * 2; i += 2, k++) { draw_icn(x + 70 + 17 * k , y, &tile[i * 2 + 0], col_fg, 1, 0); draw_icn(x + 70 + 17 * k + 8, y, &tile[i * 2 + 2], col_fg, 1, 0); } } // Mode selection. { size_t x = PARAMS_START_X + 140; size_t y = PARAMS_START_Y + PARAMS_H - 23; 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, col_fg); switch (params->wave_mode) { case 0: { txt_drawf("A", x + 12, y + 7, col_fg); } break; case 1: { txt_drawf("B", x + 12, y + 7, col_fg); } break; case 2: { txt_drawf("A+B", x + 6, y + 7, col_fg); } break; } } // Wave volume. { size_t x = PARAMS_START_X + 140; size_t y = PARAMS_START_Y + PARAMS_H - 46; 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, col_fg); switch (params->wave_volume) { case 0: { txt_drawf("0", x + 12, y + 10, col_fg); } break; case 1: { txt_drawf("25", x + 9, y + 10, col_fg); } break; case 2: { txt_drawf("50", x + 9, y + 10, col_fg); } break; case 3: { txt_drawf("75", x + 9, y + 10, col_fg); } break; case 4: { txt_drawf("100", x + 6, y + 10, col_fg); } break; } } } void draw_parameters_square(ChannelSquareParams *params, bool sweep, bool global) { u8 col_fg = COL_FG; if (global && input_handler == handle_channel_selection) { col_fg = COL_OFF; } // Duty cycle / shape. { size_t x = PARAMS_START_X + 3; size_t y = PARAMS_START_Y + 1; 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_ACC_1); draw_line(x1, y1, x1, y0, COL_ACC_1); draw_line(x1, y1, x2, y1, COL_ACC_1); draw_line(x2, y1, x2, y0, COL_ACC_1); draw_line(x2, y0, x3, y0, COL_ACC_1); draw_line(x3, y1, x3, y0, COL_ACC_1); draw_line(x3, y1, x4, y1, COL_ACC_1); draw_line(x4, y1, x4, y0, COL_ACC_1); draw_line(x4, y0, x5, y0, COL_ACC_1); } // Envelope. { size_t x = PARAMS_START_X + PARAMS_BOX_OFFSET_X * 1 + 1; size_t y = PARAMS_START_Y + 1; size_t x0 = x; size_t y0 = y + 15 - params->env_volume; size_t x1 = x + (3 * PARAMS_BOX_OFFSET_X) * params->env_time / 8 + 7; size_t y1 = params->env_direction == 0 ? y + 15 : y; size_t x2 = x + PARAMS_BOX_OFFSET_X * 3 - 5; size_t y2 = y1; // Env. if (params->env_time == 0) { // TODO: Time 0 vol 0 (dir) doesn't make sense (mute) // TODO: Time 0 vol 100 doesn't make sense (constant max volume) // NOTE: Maybe it's just a matter of handling it better on the // control? Have to check every time we change direction to swap // the conditions. draw_line(x0, y0, x2, y0, COL_ACC_2); } else { draw_line(x0, y0, x1, y1, COL_ACC_2); draw_line(x1, y1, x2, y2, COL_ACC_2); } } // TODO: Trig probability { size_t x = PARAMS_START_X + PARAMS_BOX_OFFSET_X * 4 + 3; size_t y = PARAMS_START_Y + 5; txt_drawf("100", x, y, col_fg); txt_drawc('%', x + 18, y, col_fg); } // Sweep. if (sweep) { size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y + PARAMS_BOX_OFFSET_Y; // Bounding box. u8 x0 = x + 1; u8 x1 = x + PARAMS_BOX_OFFSET_X * 3 - 5; u8 y0 = y + 2; u8 y1 = y + PARAMS_BOX_H - 8; draw_line(x0, y0, x1, y0, col_fg); draw_line(x0, y1, x1, y1, col_fg); draw_line(x0 - 1, y0 + 1, x0 - 1, y1 - 1, col_fg); draw_line(x1 + 1, y0 + 1, x1 + 1, y1 - 1, col_fg); // Number. switch (params->sweep_number) { case 0: { txt_drawf("0", x + 12, y + 5, col_fg); } break; case 1: { txt_drawf("1", x + 12, y + 5, col_fg); } break; case 2: { txt_drawf("2", x + 12, y + 5, col_fg); } break; case 3: { txt_drawf("3", x + 12, y + 5, col_fg); } break; case 4: { txt_drawf("4", x + 12, y + 5, col_fg); } break; case 5: { txt_drawf("5", x + 12, y + 5, col_fg); } break; case 6: { txt_drawf("6", x + 12, y + 5, col_fg); } break; case 7: { txt_drawf("7", x + 12, y + 5, col_fg); } break; } // Time. x += PARAMS_BOX_OFFSET_X; switch (params->sweep_time) { case 0: { txt_drawf("0", x + 12, y + 5, col_fg); } break; case 1: { txt_drawf("1", x + 12, y + 5, col_fg); } break; case 2: { txt_drawf("2", x + 12, y + 5, col_fg); } break; case 3: { txt_drawf("3", x + 12, y + 5, col_fg); } break; case 4: { txt_drawf("4", x + 12, y + 5, col_fg); } break; case 5: { txt_drawf("5", x + 12, y + 5, col_fg); } break; case 6: { txt_drawf("6", x + 12, y + 5, col_fg); } break; case 7: { txt_drawf("7", x + 12, y + 5, col_fg); } break; } // Direction. x += PARAMS_BOX_OFFSET_X; char arr_down[2] = { 0x19, 0 }; char arr_up[2] = { 0x18, 0 }; switch (params->sweep_direction) { case 0: { txt_drawf(arr_up, x + 12, y + 5, col_fg); } break; case 1: { txt_drawf(arr_down, x + 12, y + 5, col_fg); } break; } } // Labels. { size_t x = PARAMS_START_X; size_t y = PARAMS_START_Y; txt_drawf_small("shape", x + 4 + PARAMS_BOX_OFFSET_X * 0, y + PARAMS_BOX_H - 7, col_fg); txt_drawf_small("vol", x + 8 + PARAMS_BOX_OFFSET_X * 1, y + PARAMS_BOX_H - 7, col_fg); txt_drawf_small("time", x + 6 + PARAMS_BOX_OFFSET_X * 2, y + PARAMS_BOX_H - 7, col_fg); txt_drawf_small("dir", x + 8 + PARAMS_BOX_OFFSET_X * 3, y + PARAMS_BOX_H - 7, col_fg); txt_drawf_small("prob", x + 6 + PARAMS_BOX_OFFSET_X * 4, y + PARAMS_BOX_H - 7, col_fg); y += PARAMS_BOX_OFFSET_Y; if (sweep) { txt_drawf_small("sweep", x + 4 + PARAMS_BOX_OFFSET_X * 0, y + PARAMS_BOX_H - 7, col_fg); txt_drawf_small("time", x + 6 + PARAMS_BOX_OFFSET_X * 1, y + PARAMS_BOX_H - 7, col_fg); txt_drawf_small("dir", x + 8 + PARAMS_BOX_OFFSET_X * 2, y + PARAMS_BOX_H - 7, col_fg); } } // Empty spacers. { if (!sweep) { draw_param_stub(5, COL_OFF); draw_param_stub(6, COL_OFF); draw_param_stub(7, COL_OFF); } draw_param_stub(8, COL_OFF); draw_param_stub(9, COL_OFF); } } void draw_parameters_noise(ChannelNoiseParams* params, bool global) { size_t x_offset = 30; u8 col_fg = COL_FG; if (global && input_handler == handle_channel_selection) { col_fg = COL_OFF; } // Bit mode. { // Param box. { size_t x = PARAMS_START_X + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 26; 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, col_fg); switch (params->bit_mode) { case 0: { txt_drawf("A", x + 9, y + 10, col_fg); } break; case 1: { txt_drawf("B", x + 9, y + 10, col_fg); } break; } } } // Envelope. { // Env. drawing. { // Bounding box. { size_t x0 = PARAMS_START_X + 31 + x_offset; size_t y0 = PARAMS_START_Y + PARAMS_H - 46; size_t x1 = x0 + 79; size_t y1 = y0 + 21; draw_rect(x0, y0, x1, y1, COL_ACC_2); } size_t x = PARAMS_START_X + 42 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 43; size_t x0 = x; size_t y0 = y + 15 - params->env_volume; size_t x1 = x + 8 * params->env_time; size_t y1 = params->env_direction == 0 ? y + 15 : y; size_t x2 = x + 8 * 7 + 1; size_t y2 = y1; // Env. if (params->env_time == 0) { draw_line(x1, y0, x2, y0, COL_ACC_2); } else { draw_line(x0, y0, x1, y1, COL_ACC_2); draw_line(x1, y1, x2, y2, COL_ACC_2); } } // Env. volume. { size_t x = PARAMS_START_X + 31 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 26; 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, col_fg); switch (params->env_volume) { case 0: { txt_drawf("0", x + 9, y + 10, col_fg); } break; case 1: { txt_drawf("6", x + 9, y + 10, col_fg); } break; case 2: { txt_drawf("13", x + 6, y + 10, col_fg); } break; case 3: { txt_drawf("20", x + 6, y + 10, col_fg); } break; case 4: { txt_drawf("26", x + 6, y + 10, col_fg); } break; case 5: { txt_drawf("33", x + 6, y + 10, col_fg); } break; case 6: { txt_drawf("40", x + 6, y + 10, col_fg); } break; case 7: { txt_drawf("46", x + 6, y + 10, col_fg); } break; case 8: { txt_drawf("53", x + 6, y + 10, col_fg); } break; case 9: { txt_drawf("60", x + 6, y + 10, col_fg); } break; case 10: { txt_drawf("66", x + 6, y + 10, col_fg); } break; case 11: { txt_drawf("73", x + 6, y + 10, col_fg); } break; case 12: { txt_drawf("80", x + 6, y + 10, col_fg); } break; case 13: { txt_drawf("86", x + 6, y + 10, col_fg); } break; case 14: { txt_drawf("93", x + 6, y + 10, col_fg); } break; case 15: { txt_drawf("100", x + 3, y + 10, col_fg); } break; } } // Env. direction { size_t x = PARAMS_START_X + 59 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 26; 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, 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, col_fg); } break; case 1: { txt_drawf(arr_down, x + 9, y + 11, col_fg); } break; } } // Env. time. { size_t x = PARAMS_START_X + 87 + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 26; 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, col_fg); switch (params->env_time) { case 0: { txt_drawf("0", x + 9, y + 10, col_fg); } break; case 1: { txt_drawf("14", x + 6, y + 10, col_fg); } break; case 2: { txt_drawf("28", x + 6, y + 10, col_fg); } break; case 3: { txt_drawf("42", x + 6, y + 10, col_fg); } break; case 4: { txt_drawf("57", x + 6, y + 10, col_fg); } break; case 5: { txt_drawf("71", x + 6, y + 10, col_fg); } break; case 6: { txt_drawf("85", x + 6, y + 10, col_fg); } break; case 7: { txt_drawf("100", x + 3, y + 10, col_fg); } break; } } } // Labels. { size_t x = PARAMS_START_X + x_offset; size_t y = PARAMS_START_Y + PARAMS_H - 43; txt_drawf_small("envelope", x + 54, y - 12, col_fg); } } void draw_parameters(void) { clear_parameters(); Pattern *pat = &patterns[pattern_selection_loc]; // DEBUG: Draw the parameter window // draw_rect(PARAMS_START_X, PARAMS_START_Y, PARAMS_START_X + PARAMS_W, PARAMS_START_Y + PARAMS_H, COL_OFF); // DEBUG: Drawing the reference grid overlay // for (size_t i = 0; i < 10; i++) { // draw_param_stub(i, COL_OFF); // } if (input_handler == handle_trigger_selection || input_handler == handle_param_selection_sq1 || input_handler == handle_param_selection_sq2 || input_handler == handle_param_selection_wave || input_handler == handle_param_selection_noise) { switch (channel_selection_loc) { case 0: { draw_parameters_square(&pat->ch1.params[trig_selection_loc], true, false); } break; case 1: { draw_parameters_square(&pat->ch2.params[trig_selection_loc], false, false); } break; case 2: { draw_parameters_wave(&pat->ch3.params[trig_selection_loc], false); } break; case 3: { draw_parameters_noise(&pat->ch4.params[trig_selection_loc], false); } break; } return; } if (input_handler == handle_channel_selection || input_handler == handle_param_selection_ch1 || input_handler == handle_param_selection_ch2 || input_handler == handle_param_selection_ch3 || input_handler == handle_param_selection_ch4) { switch (channel_selection_loc) { case 0: { draw_parameters_square(&ch1_params, true, true); } break; case 1: { draw_parameters_square(&ch2_params, false, true); } break; case 2: { draw_parameters_wave(&ch3_params, true); } break; case 3: { draw_parameters_noise(&ch4_params, true); } break; } return; } } void clear_cursors(void) { draw_trig_cursor(last_trig_loc, COL_BG); draw_channel_cursor(last_channel_loc, COL_BG); draw_pattern_cursor(last_pattern_loc, COL_BG); draw_right_col_cursor(last_right_col_loc, COL_BG); for (size_t i = 0; i < 16; i++) { draw_current_step(i, COL_OFF); } } void draw_pattern_chain_cursor(void) { size_t offset_x = (PAT_TRIG_H + 7) * (param_selection_loc % 8); size_t offset_y = param_selection_loc < 8 ? 0 : 0 + PAT_TRIG_OFFSET_Y; size_t x0 = PAT_TRIG_START_X + offset_x; size_t x1 = PAT_TRIG_START_X + offset_x + PAT_TRIG_W; size_t y = PAT_TRIG_START_Y + offset_y + PAT_TRIG_H + 2; switch (settings.cursor) { case CURSOR_THICK_LINE: { draw_line(x0, y, x1, y, COL_ACC_0); draw_line(x0, y + 1, x1, y + 1, COL_ACC_0); } break; default: { draw_line(x0, y, x1, y, COL_ACC_0); } break; } } void draw_cursors(void) { clear_cursors(); draw_current_step(step_counter, COL_ACC_1); if (input_handler == handle_trigger_selection) { draw_trig_cursor(trig_selection_loc, COL_ACC_0); } if (input_handler == handle_channel_selection) { draw_channel_cursor(channel_selection_loc, COL_ACC_0); } else { draw_channel_cursor(channel_selection_loc, COL_OFF); } if (input_handler == handle_pattern_selection) { draw_pattern_cursor(pattern_selection_loc, COL_ACC_0); } else { draw_pattern_cursor(pattern_selection_loc, COL_OFF); } if (input_handler == handle_right_col_selection) { draw_right_col_cursor(right_col_selection_loc, COL_ACC_0); } if (input_handler == handle_param_selection_sq1 || input_handler == handle_param_selection_sq2 || input_handler == handle_param_selection_wave || input_handler == handle_param_selection_noise) { draw_params_cursor(param_selection_loc, COL_ACC_0); draw_trig_cursor(trig_selection_loc, COL_OFF); } if (input_handler == handle_param_selection_ch1 || input_handler == handle_param_selection_ch2 || input_handler == handle_param_selection_ch3 || input_handler == handle_param_selection_ch4) { draw_params_cursor(param_selection_loc, COL_ACC_0); } if (input_handler == handle_pattern_chain) { draw_pattern_chain_cursor(); } } TriggerNote * get_current_trig(void); void draw_piano_notes(void) { draw_piano(); if (input_handler == handle_channel_selection) { // Show note on current channel only. Pattern *pat = &patterns[current_pattern]; u8 step = (step_counter - 1) % 16; switch (channel_selection_loc) { case 0: { if (pat->ch1.active && pat->ch1.notes[step].active) { draw_note(pat->ch1.notes[step].note, COL_OFF); } } break; case 1: { if (pat->ch2.active && pat->ch2.notes[step].active) { draw_note(pat->ch2.notes[step].note, COL_OFF); } } break; case 2: { if (pat->ch3.active && pat->ch3.notes[step].active) { draw_note(pat->ch3.notes[step].note, COL_OFF); } } break; } } else if (input_handler == handle_trigger_selection || input_handler == handle_param_selection_sq1 || input_handler == handle_param_selection_sq2 || input_handler == handle_param_selection_wave || input_handler == handle_param_selection_noise) { // Show currently selected trigger note. TriggerNote *trig = get_current_trig(); draw_note(trig->note, COL_OFF); } else { // Show last/current played notes in all channels. if (play_status == 1) { Pattern *pat = &patterns[current_pattern]; u8 step = (step_counter - 1) % 16; if (pat->ch3.active && pat->ch3.notes[step].active) { draw_note(pat->ch3.notes[step].note, COL_OFF); } if (pat->ch2.active && pat->ch2.notes[step].active) { draw_note(pat->ch2.notes[step].note, COL_OFF); } if (pat->ch1.active && pat->ch1.notes[step].active) { draw_note(pat->ch1.notes[step].note, COL_OFF); } } } } void draw_notif_bar() { u8 x0 = NOTIF_START_X; u8 y0 = NOTIF_START_Y - 7; u8 x1 = NOTIF_START_X + NOTIF_W; u8 y1 = y0 + NOTIF_H; u8 color = COL_FG; draw_filled_rect(x0, y0, x1, y1, COL_BG); draw_rect(x0, y0, x1, y1, color); if (notif.time > 0) { char msg[32] = {0}; if (notif.time <= 32) { for (s16 i = 0; i < notif.time; i++) { msg[i] = notif.msg[i + 32 - notif.time]; } } else { for (size_t i = 0; i < 32; i++) { msg[i] = notif.msg[i]; } } txt_drawf_small(msg, x0 + 2, y0 + 1, color); } else { if (settings.help == HELP_ON) { if (input_handler == handle_trigger_selection || input_handler == handle_channel_selection) { txt_drawf_small("L/R:NOTE A:PARAMS B:TOGGLE", x0 + 2, y0 + 1, color); } else if (input_handler == handle_param_selection_sq1 || input_handler == handle_param_selection_sq2 || input_handler == handle_param_selection_wave || input_handler == handle_param_selection_noise) { txt_drawf_small("L/R:ADJUST SELECT:COPY", x0 + 2, y0 + 1, color); } else if (input_handler == handle_pattern_selection) { txt_drawf_small("L/R:CHAIN A:PARAMS B:QUEUE", x0 + 2, y0 + 1, color); } else if (input_handler == handle_right_col_selection) { if (right_col_selection_loc == R_COL_STOP) { txt_drawf_small("B:STOP", x0 + 2, y0 + 1, color); } else if (right_col_selection_loc == R_COL_PLAY) { if (play_status == 0) { txt_drawf_small("B:PLAY", x0 + 2, y0 + 1, color); } else { txt_drawf_small("B:PAUSE", x0 + 2, y0 + 1, color); } } else if (right_col_selection_loc == R_COL_BPM) { txt_drawf_small("L/R:TEMPO", x0 + 2, y0 + 1, color); } else if (right_col_selection_loc == R_COL_SETTINGS) { txt_drawf_small("B:SETTINGS", x0 + 2, y0 + 1, color); } else if (right_col_selection_loc == R_COL_BANK_A || right_col_selection_loc == R_COL_BANK_B || right_col_selection_loc == R_COL_BANK_C || right_col_selection_loc == R_COL_BANK_D) { txt_drawf_small("B:SAVE CURRENT BANK AND SWITCH", x0 + 2, y0 + 1, color); } } return; } if (chain.len != 0) { u8 x = x0 + 2; u8 y = y0 + 1; txt_drawf_small("CHAIN: ", x, y, color); x += 4 * 7; bool first = true; for (size_t i = 0, k = 0; i < MAX_CHAIN; i++) { if (chain.active[i] != 1) { continue; } if (!first) { txt_drawc_small('-', x + 4 * k++, y, COL_FG); } u8 color = COL_FG; if (i == chain.current) { color = COL_ACC_2; } txt_drawc_small('A' + chain.chain[i], x + 4 * k++, y, color); first = false; } } else { if (play_status == 0) { txt_drawf_small("STOPPED", x0 + 2, y0 + 1, color); } else { txt_drawf_small("PLAYING", x0 + 2, y0 + 1, color); } } } } void draw_pattern_chain() { clear_parameters(); for (size_t i = 0; i < 16; i++) { u8 color = COL_FG; size_t offset_x = PAT_TRIG_OFFSET_X * (i % 8); size_t offset_y = i < 8 ? 0 : 0 + PAT_TRIG_OFFSET_Y; size_t x0 = PAT_TRIG_START_X + offset_x; size_t x1 = PAT_TRIG_START_X + offset_x + PAT_TRIG_W; size_t y0 = PAT_TRIG_START_Y + offset_y; size_t y1 = PAT_TRIG_START_Y + offset_y + PAT_TRIG_H; draw_rect(x0, y0, x1, y1, color); if (chain.active[i]) { txt_drawc('A' + chain.chain[i], x0 + 4, y0 + 3, color); } color = COL_OFF; if (chain.current == i && chain.len != 0) { color = COL_ACC_2; } draw_line(x0 + 5, y1 - 2, x1 - 5, y1 - 2, color); } }