#define SPRITE_NOTE_NAMES_W 16 #define SPRITE_NOTE_NAMES_H 8 u32 sprite_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, 0x00000008, 0x0c0a0e08, 0x00000098, 0xa8a8a898, 0x00000020, 0x302b3a21, 0x00000060, 0xa0a0a060, 0x00000008, 0x0c0a0e08, 0x000000b8, 0x889888b8, 0x00000020, 0x302b3a21, 0x000000e0, 0x206020e0, 0x00000008, 0x0c0a0e08, 0x000000e0, 0x20602020, 0x00000008, 0x0c0a0e08, 0x000000b8, 0x8888a8b8, 0x00000020, 0x302b3a21, 0x000000e0, 0x2020a0e0, 0x00000008, 0x0c0a0e08, 0x000000b8, 0xa8a8b8a8, 0x00000020, 0x302b3a21, 0x000000e0, 0xa0a0e0a0, 0x00000008, 0x0c0a0e08, 0x000000b8, 0xa898a8b8, 0x00000020, 0x302b3a21, 0x000000e0, 0xa060a0e0, 0x00000008, 0x0c0a0e08, 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, }; u32 sprite_trigger_button[] = { 0xfe020202, 0x02020202, 0xff808080, 0x80808080, 0x02020202, 0x02020202, 0x80808080, 0x80808080, 0x02020202, 0x020202fe, 0x80808080, 0x808080ff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }; u32 sprite_trigger_active_indicator[] = { 0x00000000, 0x00f80000, }; u32 sprite_trigger_selection[] = { 0x0f010101, 0x00000000, 0x80000000, 0x00000000, 0x07040404, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0101010f, 0x00000000, 0x00000080, 0x00000000, 0x04040407, 0x00000000, 0x00000000, 0x00000000, }; typedef struct SeqTrigger { bool trigger; Note note; // TODO: ... } SeqTrigger; static SeqTrigger sequence_synth[] = { {true, NOTE_D_4}, {true, NOTE_F_4}, {true, NOTE_A_4}, {true, NOTE_C_5}, {true, NOTE_D_4}, {false, NOTE_C_SHARP_4}, {false, NOTE_D_4}, {false, NOTE_D_4}, {true, NOTE_D_4}, {true, NOTE_F_4}, {true, NOTE_A_4}, {true, NOTE_C_5}, {true, NOTE_D_4}, {false, NOTE_D_4}, {true, NOTE_A_4}, {false, NOTE_A_5}, }; static int bpm = 120; static int step_counter = 0; static Note active_note; void irq_timer_0(void) { active_note = sequence_synth[step_counter].note; if (sequence_synth[step_counter].trigger) { SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(13) | SOUND_SQUARE_ENV_TIME(4) | SOUND_SQUARE_DUTY(2); SOUND_SQUARE1_FREQ = SOUND_SQUARE_RESET | sound_rates[active_note]; } step_counter = (step_counter + 1) % 16; } void set_time(int bpm) { // The number of ticks of a 1024 cycle clock in a step based on the BPM can // be calculated as: // X bpm -> 60000 / 4 / bpm = Y ms = Ye-3 s // Y ms -> Ye-3 / 59.99e-9 /1024 = Z ticks // We have to operate on integer values, so the numbers have been // precalculated to `n_ticks = 244181 / bmp` int n_ticks = -244181 / bpm; irs_set(IRQ_TIMER_0, irq_timer_0); TIMER_DATA_0 = n_ticks; TIMER_CTRL_0 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3; } // // Sequencer sprite memory. // // Currently we load all sequencer sprite memory in the VRAM: // // - Note names: 73x2 tiles // - Sprite trigger button: 8 tiles // - Duration indicator (TODO) // - Current step marker. // // The order of OBJs correspond to: // // - 000-015 step note names. // - 015-029 step trigger steps. // - 030-045 step duration indicators. // - 046-046 current step marker. // - 047-047 trigger selection indicator. // size_t obj_counter = 0; typedef struct SeqSprite { u8 id; int x; int y; u16 obj_attr_0; u16 obj_attr_1; u16 obj_attr_2; } SeqSprite; #define SEQ_POS_X 45 #define SEQ_POS_Y 50 #define SEQ_TRIG_DIST 20 int trig_selection_loc = 1; SeqSprite seq_sprites[34] = {0}; void init_sequencer_sprites(void) { // Sprite note names. size_t sprite_id = load_packed_sprite_data(&sprite_note_names, 2, 73); for (size_t i = 0; i < 16; ++i) { int x = SEQ_POS_X + i * SEQ_TRIG_DIST; int y = SEQ_POS_Y; if (i >= 8) { y += 32; x -= 8 * SEQ_TRIG_DIST; } seq_sprites[i].x = x; seq_sprites[i].y = y; seq_sprites[i].id = obj_counter++; seq_sprites[i].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); // TODO: They should start hidden until the update function changes that. // seq_sprites[i].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_HIDDEN; seq_sprites[i].obj_attr_1 = OBJ_SIZE_SMALL | OBJ_X_COORD(x); seq_sprites[i].obj_attr_2 = sprites[sprite_id].tile_start; } // Trigger boxes. sprite_id = load_packed_sprite_data(&sprite_trigger_button, 8, 1); for (size_t i = 0; i < 16; ++i) { int x = SEQ_POS_X + i * SEQ_TRIG_DIST; int y = SEQ_POS_Y; if (i >= 8) { y += 32; x -= 8 * SEQ_TRIG_DIST; } seq_sprites[i + 16].id = obj_counter++; seq_sprites[i + 16].obj_attr_0 = OBJ_SHAPE_TALL | OBJ_Y_COORD(y); seq_sprites[i + 16].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); seq_sprites[i + 16].obj_attr_2 = sprites[sprite_id].tile_start; } sprite_id = load_packed_sprite_data(&sprite_trigger_active_indicator, 1, 1); // TODO: No need for a for loop. { int x = SEQ_POS_X; int y = SEQ_POS_Y + 15; seq_sprites[32].id = obj_counter++; seq_sprites[32].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[32].obj_attr_1 = OBJ_SIZE_SMALL | OBJ_X_COORD(x); seq_sprites[32].obj_attr_2 = sprites[sprite_id].tile_start | OBJ_PAL_BANK(1); } sprite_id = load_packed_sprite_data(&sprite_trigger_selection, 16, 1); // TODO: No need for a for loop. { int x = SEQ_POS_X - 1 + trig_selection_loc * SEQ_TRIG_DIST; int y = SEQ_POS_Y - 2; if (trig_selection_loc >= 8) { y += 32; x -= 8 * SEQ_TRIG_DIST; } seq_sprites[33].id = obj_counter++; seq_sprites[33].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[33].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); seq_sprites[33].obj_attr_2 = sprites[sprite_id].tile_start | OBJ_PAL_BANK(2); } } void update_sequencer_sprites(void) { // 000-015: Step note names. for (size_t i = 0; i < 16; ++i) { // Each note name is made of 2 8x8 tiles (16x8). size_t base_tile = sequence_synth[i].note * 2; // TODO: Show the note name if is within the duration, hide when trigger // is off or duration of previous note was cut short. If not triggered // but note name appears, needs to be on a different palette bank. if (!sequence_synth[i].trigger) { seq_sprites[i].obj_attr_0 |= OBJ_HIDDEN; } else { seq_sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; } seq_sprites[i].obj_attr_2 = base_tile; } // 33: Sequence indicator. { int x = SEQ_POS_X + step_counter * SEQ_TRIG_DIST + 3; int y = SEQ_POS_Y + 15; if (step_counter >= 8) { y += 32; x -= 8 * SEQ_TRIG_DIST; } seq_sprites[32].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); // TODO: Mask and update instead. seq_sprites[32].obj_attr_1 = OBJ_SIZE_SMALL | OBJ_X_COORD(x); // TODO: Mask and update instead. } // 34: Trigger selection. { int x = SEQ_POS_X - 1 + trig_selection_loc * SEQ_TRIG_DIST; int y = SEQ_POS_Y - 2; if (trig_selection_loc >= 8) { y += 32; x -= 8 * SEQ_TRIG_DIST; } // TODO: Mask and update instead. seq_sprites[33].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[33].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); } } void handle_sequencer_input(void) { if (key_pressed(KEY_LEFT)) { if (trig_selection_loc == 0) { trig_selection_loc = 15; } else { trig_selection_loc = CLAMP(trig_selection_loc - 1, 0, 15); } } if (key_pressed(KEY_RIGHT)) { if (trig_selection_loc == 15) { trig_selection_loc = 0; } else { trig_selection_loc = CLAMP(trig_selection_loc + 1, 0, 15); } } if (key_pressed(KEY_UP) || key_pressed(KEY_DOWN)) { trig_selection_loc = (trig_selection_loc + 8) % 16; } if (key_pressed(KEY_B)) { sequence_synth[trig_selection_loc].trigger ^= 1; } if (key_pressed(KEY_L)) { sequence_synth[trig_selection_loc].note = CLAMP(sequence_synth[trig_selection_loc].note - 1, NOTE_C_2, NOTE_C_8); } if (key_pressed(KEY_R)) { sequence_synth[trig_selection_loc].note = CLAMP(sequence_synth[trig_selection_loc].note + 1, NOTE_C_2, NOTE_C_8); } if (key_pressed(KEY_START)) { step_counter = 0; set_time(bpm); } if (key_pressed(KEY_SELECT)) { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; } } void render_sequencer_sprites(void) { size_t len = sizeof(seq_sprites) / sizeof(SeqSprite); for (size_t i = 0; i < len; ++i) { size_t id = seq_sprites[i].id; OBJ_ATTR_0(id) = seq_sprites[i].obj_attr_0; OBJ_ATTR_1(id) = seq_sprites[i].obj_attr_1; OBJ_ATTR_2(id) = seq_sprites[i].obj_attr_2; } }