#include "bitmap.h" // Positioning parameters. #define SEQ_TRIG_POS_X 45 #define SEQ_TRIG_POS_Y 50 #define SEQ_TRIG_DIST_X 18 #define SEQ_TRIG_DIST_Y 28 #define SEQ_CHANNEL_POS_X SEQ_TRIG_POS_X - 32 #define SEQ_CHANNEL_POS_Y SEQ_TRIG_POS_Y #define SEQ_CHANNEL_DIST_Y 11 #define SEQ_ENV_POS_X 10 #define SEQ_ENV_POS_Y 10 #define SEQ_ENV_DIST 32 #define SEQ_DUTYCYCLE_POS_X SEQ_ENV_POS_X + SEQ_ENV_DIST * 3 #define SEQ_DUTYCYCLE_POS_Y 10 - 8 #define SEQ_SWEEP_POS_X SEQ_DUTYCYCLE_POS_X + SEQ_ENV_DIST #define SEQ_SWEEP_POS_Y SEQ_ENV_POS_Y #define SEQ_N_CHANNELS 3 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, 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, }; 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, }; u32 sprite_env_label_volume[] = { 0xa0a0a0a0, 0xc0000000, 0x262a2a2a, 0xee000000, 0xaaeaaaaa, 0xae000000, 0x0e020602, 0x0e000000, }; u32 sprite_env_sweep_label_time[] = { 0x00000000, 0x00000000, 0xee444444, 0xe4000000, 0xea2e6a2a, 0xea000000, 0x00000000, 0x00000000, }; u32 sprite_env_label_direction[] = { 0x00000000, 0x00000000, 0xcc949494, 0xcc000000, 0x1d140cd4, 0xd5000000, 0x00000000, 0x00000000, }; u32 sprite_env_label_env[] = { 0x6ea2a6a2, 0xae000000, 0xea2a6a2a, 0xec000000, 0x62a2a2a2, 0xee000000, 0xee2a6e22, 0xe2000000, }; u32 sprite_sweep_number_label[] = { 0x60a0a0a0, 0xa0000000, 0xaaeaaaaa, 0xae000000, 0xee2a662a, 0xee000000, 0x0e0a060a, 0x0a000000, }; u32 sprite_sweep_label[] = { 0x8040c000, 0xc0000000, 0xa5a4a5b5, 0xa8000000, 0xbb889988, 0xbb000000, 0x03020300, 0x00000000, }; u32 sprite_duty_cycle_label[] = { 0x00000000, 0x00000080, 0xa6aaaaaa, 0xe60000ab, 0xaea4e444, 0x4400008b, 0x00000000, 0x00000003, 0x80808080, 0x00000000, 0xa8b89093, 0x00000000, 0x888888bb, 0x00000000, 0x00010003, 0x00000000, }; u32 sprite_duty_cycle[] = { 0xb03030b0, 0xb0b0b000, 0x0f0c0c0f, 0x01616f00, 0x1f83831f, 0x18989f00, 0x00090d06, 0x030d0c00, 0x00000000, 0x00000000, 0xdfd8d8df, 0x0303df00, 0x07606087, 0xc6662700, 0x00020301, 0x00030300, 0x00000000, 0x00000000, 0xdf43435f, 0x5858df00, 0x07666686, 0xc6662700, 0x00020301, 0x00030300, 0x00000000, 0x00000000, 0xdfd8d8cc, 0x0603c300, 0x07606087, 0xc6662700, 0x00020301, 0x00030300, }; u32 sprite_env_volume[] = { 0x80808080, 0x80808000, 0x0f0c0c0c, 0x0c6c6f00, 0x1f999919, 0x19999f00, 0x00090d06, 0x030d0c00, 0x80808080, 0x80808000, 0x0f01010f, 0x09696f00, 0x1f83831f, 0x13939f00, 0x00090d06, 0x030d0c00, 0x60606060, 0x60606000, 0x1f18181e, 0x18d8df00, 0x3e30303c, 0x30303e00, 0x00131b0c, 0x061b1900, 0xf8c0c0f8, 0x1818f800, 0x3e06063e, 0x26a6be00, 0x7c0c0c7c, 0x4c4d7d00, 0x00263618, 0x0c363200, 0xf8c0c0f8, 0x1818f800, 0x3e323232, 0x32b2be00, 0x7c646464, 0x64657d00, 0x00263618, 0x0c363200, 0xf8c0c0f0, 0xc0c0f800, 0x3e30303c, 0x30b0be00, 0x7c606078, 0x60617d00, 0x00263618, 0x0c363200, 0xc0e0d0c8, 0xf8c0c000, 0x3e323232, 0x32b2be00, 0x7c646464, 0x64657d00, 0x00263618, 0x0c363200, 0xc0e0d0c8, 0xf8c0c000, 0x3e06063e, 0x26a6be00, 0x7c0c0c7c, 0x4c4d7d00, 0x00263618, 0x0c363200, 0xf81818f8, 0xc0c0f800, 0x3e30303c, 0x30b0be00, 0x7c606078, 0x60617d00, 0x00263618, 0x0c363200, 0xf81818f8, 0x9898f800, 0x3e323232, 0x32b2be00, 0x7c646464, 0x64657d00, 0x00263618, 0x0c363200, 0xf81818f8, 0x9898f800, 0x3e06063e, 0x26a6be00, 0x7c0c0c7c, 0x4c4d7d00, 0x00263618, 0x0c363200, 0xf8c0c060, 0x30181800, 0x3e30303c, 0x30b0be00, 0x7c606078, 0x60617d00, 0x00263618, 0x0c363200, 0xf8c8c8f8, 0xc8c8f800, 0x3e323232, 0x32b2be00, 0x7c646464, 0x64657d00, 0x00263618, 0x0c363200, 0xf8c8c8f8, 0xc8c8f800, 0x3e06063e, 0x26a6be00, 0x7c0c0c7c, 0x4c4d7d00, 0x00263618, 0x0c363200, 0xf8c8c8f8, 0xc0c0f800, 0x3e30303c, 0x30b0be00, 0x7c606078, 0x60617d00, 0x00263618, 0x0c363200, 0xec2c2c2c, 0x2c2cec00, 0xfbcbcbcb, 0xcbcbfb00, 0xf0909090, 0x9096f600, 0x0199d961, 0x31d9c900, }; u32 sprite_env_time[] = { 0x80808080, 0x80808000, 0x0f0c0c0c, 0x0c6c6f00, 0x1f999919, 0x19999f00, 0x00090d06, 0x030d0c00, 0x60606060, 0x60606000, 0x181c1a19, 0x1fd8d800, 0x3e30303c, 0x30303e00, 0x00131b0c, 0x061b1900, 0xf8c0c0f8, 0x1818f800, 0x3e32323e, 0x32b2be00, 0x7c0c0c7c, 0x4c4d7d00, 0x00263618, 0x0c363200, 0xc0e0d0c8, 0xf8c0c000, 0x3e30303e, 0x0686be00, 0x7c64647c, 0x60617d00, 0x00263618, 0x0c363200, 0xe06060e0, 0x0000e000, 0xfbc0c063, 0x331b1b00, 0x30303030, 0x30363600, 0x00131b0c, 0x061b1900, 0xe0000080, 0xc0606000, 0x1b1b1b19, 0x18d8d800, 0x30383432, 0x3e303000, 0x00131b0c, 0x061b1900, 0xf8c8c8f8, 0xc8c8f800, 0x3e06063e, 0x30b0be00, 0x7c606030, 0x180d0d00, 0x00263618, 0x0c363200, 0xec2c2c2c, 0x2c2cec00, 0xfbcbcbcb, 0xcbcbfb00, 0xf0909090, 0x9096f600, 0x0199d961, 0x31d9c900, }; u32 sprite_env_sweep_direction[] = { 0x00000000, 0x00000000, 0xc8c8c8c8, 0xc8c8f800, 0x3e32323e, 0x02020200, 0x00000000, 0x00000000, 0xe0202020, 0x2020e000, 0x71cbcbcb, 0xcbcb7100, 0xb2b2b2b2, 0xb2ba9400, 0x0d0f0e0c, 0x0c0c0c00, }; u32 sprite_sweep_time_number[] = { 0x7c646464, 0x64647c00, 0x18181818, 0x18181800, 0x7c60607c, 0x0c0c7c00, 0x7c606078, 0x60607c00, 0x60706864, 0x7c606000, 0x7c0c0c7c, 0x60607c00, 0x7c0c0c7c, 0x4c4c7c00, 0x7c606030, 0x180c0c00, }; u32 sprite_synth_param_selector[] = { 0x07010100, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xe0808000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000101, 0x07000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00008080, 0xe0000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }; u32 sprite_channels[] = { 0xc0404040, 0x404040c0, 0xff005c44, 0xc45c00ff, 0xff002121, 0x212500ff, 0x03020202, 0x02020203, 0xc0404040, 0x404040c0, 0xff00aea2, 0xe2ae00ff, 0xff005050, 0x505200ff, 0x03020202, 0x02020203, 0xc0404040, 0x404040c0, 0xff005751, 0x715700ff, 0xff00a8a8, 0xa8a900ff, 0x03020202, 0x02020203, 0xc0404040, 0x404040c0, 0xff005751, 0x715700ff, 0xff00a8a8, 0xa8c900ff, 0x03020202, 0x02020203, 0xc0404040, 0x404040c0, 0xff007010, 0x301000ff, 0xff000507, 0x050500ff, 0x03020202, 0x02020203, }; u32 sprite_channels_selector[] = { 0x70101000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0e080800, 0x00000000, 0x00101070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0008080e, 0x00000000, }; static u8 sine_wave[] = { 0x89, 0xBC, 0xDE, 0xEF, 0xFE, 0xED, 0xCB, 0x98, 0x76, 0x43, 0x21, 0x10, 0x01, 0x12, 0x34, 0x67, }; static u8 saw_wave[16] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, }; // TODO: Should we split this up in individual trigger structs depending on the // channel? typedef struct SeqTrigger { bool trigger; Note note; u8 env_volume; u8 env_time; u8 env_direction; u8 duty_cycle; u8 sweep_number; u8 sweep_time; u8 sweep_direction; u8 wave_volume; u8 wave_mode; u8 *wave_a; u8 *wave_b; // TODO: Do we need other fields? } SeqTrigger; static SeqTrigger sequences[3][16] = { // Synth 1 { {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_C_4, 8, 4, 0, 2, 0, 0, 0}, }, // Synth 2 { {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0}, }, // Synth 3 { {true, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &saw_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &saw_wave, &saw_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, {false, NOTE_C_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &saw_wave, &sine_wave}, {true, NOTE_D_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &saw_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &saw_wave, &saw_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, {true, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &saw_wave, &sine_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &saw_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &saw_wave, &saw_wave}, {true, NOTE_D_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &saw_wave, &sine_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, {false, NOTE_G_5, 8, 4, 0, 2, 0, 0, 0, 3, 0, &sine_wave, &sine_wave}, }, }; // TODO: Support for copy paste anything, contextual. If we are on trigger // selection mode, pressing the copy key (SELECT? SELECT+B?), all synth // parameters will be copied for that trigger. Pressing paste (SELECT+A?) will // update the params. This only works on compatible channels (Not on the wave // channel for example). If an entire channel is copied, all trigs in the // sequence will be copied. We can choose to copy only one of the parameters as // well or the note/duration. // TODO: Allow muting and unmuting channels. Show in grey when muted. // TODO: Show channel beat indicator if a note is being played. // TODO: Parameters should change depending on the selected channel. For // example, we may want to hide the sweep parameters for synth 2. // TODO: Research a way of having pattern chains. // TODO: Enable control on channels 3-4. // TODO: Study how to save patterns and chains. static int bpm = 115; static int step_counter = 0; static Note active_note; void irq_timer_0(void) { { SeqTrigger *trig = &sequences[0][step_counter]; active_note = trig->note; if (trig->trigger) { SOUND_SQUARE1_SWEEP = SOUND_SWEEP_NUMBER(trig->sweep_number) | SOUND_SWEEP_DIR(trig->sweep_direction) | SOUND_SWEEP_TIME(trig->sweep_time); SOUND_SQUARE1_CTRL = SOUND_SQUARE_ENV_VOL(trig->env_volume) | SOUND_SQUARE_ENV_TIME(trig->env_time) | SOUND_SQUARE_ENV_DIR(trig->env_direction) | SOUND_SQUARE_DUTY(trig->duty_cycle); SOUND_SQUARE1_FREQ = SOUND_FREQ_RESET | sound_rates[active_note]; } } { SeqTrigger *trig = &sequences[1][step_counter]; active_note = trig->note; if (trig->trigger) { SOUND_SQUARE2_CTRL = SOUND_SQUARE_ENV_VOL(trig->env_volume) | SOUND_SQUARE_ENV_TIME(trig->env_time) | SOUND_SQUARE_ENV_DIR(trig->env_direction) | SOUND_SQUARE_DUTY(trig->duty_cycle); SOUND_SQUARE2_FREQ = SOUND_FREQ_RESET | sound_rates[active_note]; } } { SeqTrigger *trig = &sequences[2][step_counter]; active_note = trig->note; if (trig->trigger) { // Update both banks. // TODO: Actually depends on which bank is selected, no need to // update both if only one is playing. // TODO: Should we compare if previous and current wave are the // same before updating? SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(1); memcpy(SOUND_WAVE_RAM, trig->wave_a, 32); SOUND_WAVE_MODE = SOUND_WAVE_BANK_SELECT(0); memcpy(SOUND_WAVE_RAM, trig->wave_b, 32); switch (trig->wave_mode) { case 0: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(0); } break; case 1: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(0) | SOUND_WAVE_BANK_SELECT(1); } break; case 2: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(1) | SOUND_WAVE_BANK_SELECT(0); } break; case 3: { SOUND_WAVE_MODE = SOUND_WAVE_BANK_MODE(1) | SOUND_WAVE_BANK_SELECT(1); } break; } SOUND_WAVE_MODE |= SOUND_WAVE_ENABLE; switch (trig->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[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. // - Current step marker: 1 tile. // - Selected trigger marker: 16 tile. // - Env: Volume label: 4 tiles. // - Env: Volume text: 16x4 tiles // - Env: Time label: 4 tiles. // - Env: Time text: 8x4 tiles // - Env: Direction label: 4 tiles. // - Env: Direction text: 2x4 tiles // TODO: Update from sweep onwards. // // The order of OBJs correspond to: // // - 000-015 step note names. // - 015-031 step trigger steps. // - 032-032 current step marker. // - 033-033 trigger selection indicator. // - 034-034 envelope: volume label. // - 035-035 envelope: volume indicator. // - 036-036 envelope: time label. // - 037-037 envelope: time indicator. // - 038-038 envelope: direction label. // - 039-039 envelope: direction indicator. // - 040-040 envelope: envelope label. // - 041-041 duty-cycle label. // - 042-042 duty-cycle indicator. // - 043-043 sweep: number label. // - 044-044 sweep: number indicator. // - 045-045 sweep: time label. // - 046-046 sweep: time indicator. // - 047-047 sweep: direction label. // - 048-048 sweep: direction indicator. // - 049-049 sweep: sweep label. // - 050-050 synth parameter selector. // - 051-051 channel 1. // - 052-052 channel 2. // - 053-053 channel 3. // - 054-054 channel 4. // - 055-055 RESERVED: FM synth?. // - 056-056 channel selector. // size_t obj_counter = 0; typedef struct SeqSprite { u8 id; int x; int y; size_t base_tile; u16 obj_attr_0; u16 obj_attr_1; u16 obj_attr_2; } SeqSprite; typedef enum { SEQ_SELECT_TRIGGER, SEQ_SELECT_CHANNEL, SEQ_SELECT_PARAMETER, } SeqSelect; int trig_selection_loc = 0; int param_selection_loc = 0; int channel_selection_loc = 2; SeqSelect current_selection = SEQ_SELECT_TRIGGER; SeqSprite seq_sprites[57] = {0}; void draw_wave_pattern(u8 *pattern, int x, int y, Color clr) { for (size_t i = 0; i < 16; ++i) { u8 byte = pattern[i]; u8 first = (byte >> 4) & 0xF; u8 second = byte & 0xF; FRAMEBUFFER[y + 16 - first][x + i * 4] = clr; FRAMEBUFFER[y + 16 - first][x + i * 4 + 1] = clr; FRAMEBUFFER[y + 16 - second][x + i * 4 + 2] = clr; FRAMEBUFFER[y + 16 - second][x + i * 4 + 3] = clr; } } void init_sequencer_sprites(void) { // Load palette. init_sprite_pal(0, COLOR_WHITE); init_sprite_pal(16, COLOR_CYAN); init_sprite_pal(32, COLOR_RED); init_sprite_pal(48, COLOR_GREY); init_sprites(512); // 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_TRIG_POS_X + i * SEQ_TRIG_DIST_X; int y = SEQ_TRIG_POS_Y; int base_tile = sprites[sprite_id].tile_start; if (i >= 8) { y += SEQ_TRIG_DIST_Y; x -= 8 * SEQ_TRIG_DIST_X; } seq_sprites[i].x = x; seq_sprites[i].y = y; seq_sprites[i].id = obj_counter++; seq_sprites[i].base_tile = base_tile; seq_sprites[i].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_HIDDEN | OBJ_Y_COORD(y); seq_sprites[i].obj_attr_1 = OBJ_SIZE_SMALL | OBJ_X_COORD(x); seq_sprites[i].obj_attr_2 = base_tile; } // Trigger boxes. sprite_id = load_packed_sprite_data(&sprite_trigger_button, 8, 1); for (size_t i = 0; i < 16; ++i) { int x = SEQ_TRIG_POS_X + i * SEQ_TRIG_DIST_X; int y = SEQ_TRIG_POS_Y; int base_tile = sprites[sprite_id].tile_start; if (i >= 8) { y += 28; x -= 8 * SEQ_TRIG_DIST_X; } seq_sprites[i + 16].id = obj_counter++; seq_sprites[i + 16].base_tile = base_tile; 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 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_trigger_active_indicator, 1, 1); { int x = SEQ_TRIG_POS_X; int y = SEQ_TRIG_POS_Y + 15; int base_tile = sprites[sprite_id].tile_start; seq_sprites[32].id = obj_counter++; seq_sprites[32].base_tile = base_tile; 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 = base_tile | OBJ_PAL_BANK(1); } sprite_id = load_packed_sprite_data(&sprite_trigger_selection, 16, 1); { int x = SEQ_TRIG_POS_X - 1 + trig_selection_loc * SEQ_TRIG_DIST_X; int y = SEQ_TRIG_POS_Y - 2; int base_tile = sprites[sprite_id].tile_start; if (trig_selection_loc >= 8) { y += SEQ_TRIG_DIST_Y; x -= 8 * SEQ_TRIG_DIST_X; } seq_sprites[33].id = obj_counter++; seq_sprites[33].base_tile = base_tile; 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 = base_tile | OBJ_PAL_BANK(2); } sprite_id = load_packed_sprite_data(&sprite_env_label_volume, 4, 1); { int x = SEQ_ENV_POS_X; int y = SEQ_ENV_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[34].id = obj_counter++; seq_sprites[34].base_tile = base_tile; seq_sprites[34].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[34].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[34].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_env_volume, 4, 16); { int x = SEQ_ENV_POS_X; int y = SEQ_ENV_POS_Y + 8; int base_tile = sprites[sprite_id].tile_start; seq_sprites[35].id = obj_counter++; seq_sprites[35].base_tile = base_tile; seq_sprites[35].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[35].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[35].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_env_sweep_label_time, 4, 1); { int x = SEQ_ENV_POS_X + SEQ_ENV_DIST; int y = SEQ_ENV_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[36].id = obj_counter++; seq_sprites[36].base_tile = base_tile; seq_sprites[36].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[36].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[36].obj_attr_2 = base_tile; } { int x = SEQ_SWEEP_POS_X + SEQ_ENV_DIST; int y = SEQ_SWEEP_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[45].id = obj_counter++; seq_sprites[45].base_tile = base_tile; seq_sprites[45].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[45].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[45].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_env_time, 4, 8); { int x = SEQ_ENV_POS_X + SEQ_ENV_DIST; int y = SEQ_ENV_POS_Y + 8; int base_tile = sprites[sprite_id].tile_start; seq_sprites[37].id = obj_counter++; seq_sprites[37].base_tile = base_tile; seq_sprites[37].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[37].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[37].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_env_label_direction, 4, 1); { int x = SEQ_ENV_POS_X + SEQ_ENV_DIST * 2; int y = SEQ_ENV_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[38].id = obj_counter++; seq_sprites[38].base_tile = base_tile; seq_sprites[38].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[38].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[38].obj_attr_2 = base_tile; } { int x = SEQ_SWEEP_POS_X + SEQ_ENV_DIST * 2; int y = SEQ_SWEEP_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[47].id = obj_counter++; seq_sprites[47].base_tile = base_tile; seq_sprites[47].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[47].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[47].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_env_sweep_direction, 4, 2); { int x = SEQ_ENV_POS_X + SEQ_ENV_DIST * 2; int y = SEQ_ENV_POS_Y + 8; int base_tile = sprites[sprite_id].tile_start; seq_sprites[39].id = obj_counter++; seq_sprites[39].base_tile = base_tile; seq_sprites[39].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[39].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[39].obj_attr_2 = base_tile; } { int x = SEQ_SWEEP_POS_X + SEQ_ENV_DIST * 2; int y = SEQ_SWEEP_POS_Y + 8; int base_tile = sprites[sprite_id].tile_start; seq_sprites[48].id = obj_counter++; seq_sprites[48].base_tile = base_tile; seq_sprites[48].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[48].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[48].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_env_label_env, 4, 1); { int x = SEQ_ENV_POS_X + SEQ_ENV_DIST; int y = SEQ_ENV_POS_Y - 10; int base_tile = sprites[sprite_id].tile_start; seq_sprites[40].id = obj_counter++; seq_sprites[40].base_tile = base_tile; seq_sprites[40].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[40].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[40].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_duty_cycle_label, 8, 1); { int x = SEQ_DUTYCYCLE_POS_X; int y = SEQ_DUTYCYCLE_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[41].id = obj_counter++; seq_sprites[41].base_tile = base_tile; seq_sprites[41].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[41].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); seq_sprites[41].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_duty_cycle, 4, 4); { int x = SEQ_DUTYCYCLE_POS_X; int y = SEQ_DUTYCYCLE_POS_Y + 16; int base_tile = sprites[sprite_id].tile_start; seq_sprites[42].id = obj_counter++; seq_sprites[42].base_tile = base_tile; seq_sprites[42].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[42].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[42].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_sweep_number_label, 4, 1); { int x = SEQ_SWEEP_POS_X; int y = SEQ_SWEEP_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[43].id = obj_counter++; seq_sprites[43].base_tile = base_tile; seq_sprites[43].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[43].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[43].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_sweep_time_number, 1, 8); { int x = SEQ_SWEEP_POS_X + 12; int y = SEQ_SWEEP_POS_Y + 8; int base_tile = sprites[sprite_id].tile_start; seq_sprites[44].id = obj_counter++; seq_sprites[44].base_tile = base_tile; seq_sprites[44].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[44].obj_attr_1 = OBJ_SIZE_SMALL | OBJ_X_COORD(x); seq_sprites[44].obj_attr_2 = base_tile; } { int x = SEQ_SWEEP_POS_X + 12 + SEQ_ENV_DIST; int y = SEQ_SWEEP_POS_Y + 8; int base_tile = sprites[sprite_id].tile_start; seq_sprites[46].id = obj_counter++; seq_sprites[46].base_tile = base_tile; seq_sprites[46].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[46].obj_attr_1 = OBJ_SIZE_SMALL | OBJ_X_COORD(x); seq_sprites[46].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_sweep_label, 4, 1); { int x = SEQ_SWEEP_POS_X + SEQ_ENV_DIST; int y = SEQ_SWEEP_POS_Y - 10; int base_tile = sprites[sprite_id].tile_start; seq_sprites[49].id = obj_counter++; seq_sprites[49].base_tile = base_tile; seq_sprites[49].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[49].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[49].obj_attr_2 = base_tile; } sprite_id = load_packed_sprite_data(&sprite_synth_param_selector, 16, 1); { int x = SEQ_ENV_POS_X + 1; int y = SEQ_ENV_POS_Y - 3; int base_tile = sprites[sprite_id].tile_start; seq_sprites[50].id = obj_counter++; seq_sprites[50].base_tile = base_tile; seq_sprites[50].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[50].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); seq_sprites[50].obj_attr_2 = base_tile | OBJ_PAL_BANK(2); } sprite_id = load_packed_sprite_data(&sprite_channels, 4, 5); { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y; int base_tile = sprites[sprite_id].tile_start; seq_sprites[51].id = obj_counter++; seq_sprites[51].base_tile = base_tile; seq_sprites[51].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[51].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[51].obj_attr_2 = base_tile | OBJ_PAL_BANK(0); } { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y + SEQ_CHANNEL_DIST_Y; int base_tile = sprites[sprite_id].tile_start + 4; seq_sprites[52].id = obj_counter++; seq_sprites[52].base_tile = base_tile; seq_sprites[52].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[52].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[52].obj_attr_2 = base_tile | OBJ_PAL_BANK(0); } { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y + SEQ_CHANNEL_DIST_Y * 2; int base_tile = sprites[sprite_id].tile_start + 8; seq_sprites[53].id = obj_counter++; seq_sprites[53].base_tile = base_tile; seq_sprites[53].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[53].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[53].obj_attr_2 = base_tile | OBJ_PAL_BANK(0); } { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y + SEQ_CHANNEL_DIST_Y * 3; int base_tile = sprites[sprite_id].tile_start + 12; seq_sprites[54].id = obj_counter++; seq_sprites[54].base_tile = base_tile; seq_sprites[54].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y) | OBJ_HIDDEN; seq_sprites[54].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[54].obj_attr_2 = base_tile | OBJ_PAL_BANK(0); } { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y + SEQ_CHANNEL_DIST_Y * 4; int base_tile = sprites[sprite_id].tile_start + 16; seq_sprites[55].id = obj_counter++; seq_sprites[55].base_tile = base_tile; seq_sprites[55].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y) | OBJ_HIDDEN; seq_sprites[55].obj_attr_1 = OBJ_SIZE_MID | OBJ_X_COORD(x); seq_sprites[55].obj_attr_2 = base_tile | OBJ_PAL_BANK(0); } sprite_id = load_packed_sprite_data(&sprite_channels_selector, 8, 1); { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y - 2; int base_tile = sprites[sprite_id].tile_start; seq_sprites[56].id = obj_counter++; seq_sprites[56].base_tile = base_tile; // TODO: Start hidden. // seq_sprites[55].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y) | OBJ_HIDDEN; seq_sprites[56].obj_attr_0 = OBJ_SHAPE_WIDE | OBJ_Y_COORD(y); seq_sprites[56].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); seq_sprites[56].obj_attr_2 = base_tile | 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 = seq_sprites[i].base_tile + sequences[channel_selection_loc][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 (!sequences[channel_selection_loc][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_TRIG_POS_X + step_counter * SEQ_TRIG_DIST_X + 3; int y = SEQ_TRIG_POS_Y + 15; if (step_counter >= 8) { y += SEQ_TRIG_DIST_Y; x -= 8 * SEQ_TRIG_DIST_X; } 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_TRIG_POS_X - 1 + trig_selection_loc * SEQ_TRIG_DIST_X; int y = SEQ_TRIG_POS_Y - 2; if (trig_selection_loc >= 8) { y += SEQ_TRIG_DIST_Y; x -= 8 * SEQ_TRIG_DIST_X; } if (current_selection == SEQ_SELECT_TRIGGER || current_selection == SEQ_SELECT_PARAMETER) { seq_sprites[33].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); } else { seq_sprites[33].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y) | OBJ_HIDDEN; } seq_sprites[33].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); int base_tile = seq_sprites[33].base_tile; if (current_selection == SEQ_SELECT_TRIGGER) { seq_sprites[33].obj_attr_2 = base_tile | OBJ_PAL_BANK(2); } else { seq_sprites[33].obj_attr_2 = base_tile | OBJ_PAL_BANK(3); } } // 36: Envelope initial volume. { size_t tile_diff = sequences[channel_selection_loc][trig_selection_loc].env_volume * 4; size_t base_tile = seq_sprites[35].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[35].obj_attr_2 = tile; } // 38: Envelope time. { size_t tile_diff = sequences[channel_selection_loc][trig_selection_loc].env_time * 4; size_t base_tile = seq_sprites[37].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[37].obj_attr_2 = tile; } // 40: Envelope direction. { size_t tile_diff = 0; if (sequences[channel_selection_loc][trig_selection_loc].env_direction == 0) { tile_diff = 4; } size_t base_tile = seq_sprites[39].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[39].obj_attr_2 = tile; } // 43: Duty cycle. { size_t tile_diff = sequences[channel_selection_loc][trig_selection_loc].duty_cycle * 4; size_t base_tile = seq_sprites[42].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[42].obj_attr_2 = tile; } // 45: Sweep number. { size_t tile_diff = sequences[channel_selection_loc][trig_selection_loc].sweep_number; size_t base_tile = seq_sprites[44].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[44].obj_attr_2 = tile; } // 47: Sweep time. { size_t tile_diff = sequences[channel_selection_loc][trig_selection_loc].sweep_time; size_t base_tile = seq_sprites[46].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[46].obj_attr_2 = tile; } // 49: Sweep direction. { size_t tile_diff = sequences[channel_selection_loc][trig_selection_loc].sweep_direction * 4; size_t base_tile = seq_sprites[48].base_tile; size_t tile = base_tile + tile_diff; seq_sprites[48].obj_attr_2 = tile; } // 51: Parameter selector. { int x = SEQ_ENV_POS_X + 1 + (param_selection_loc % 7) * SEQ_ENV_DIST; int y = SEQ_ENV_POS_Y - 3; int hidden = 0; if (current_selection != SEQ_SELECT_PARAMETER) { hidden = OBJ_HIDDEN; } seq_sprites[50].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y) | hidden; seq_sprites[50].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); } // 57: Parameter selector. { int x = SEQ_CHANNEL_POS_X; int y = SEQ_CHANNEL_POS_Y - 2; y += (channel_selection_loc % SEQ_N_CHANNELS * SEQ_CHANNEL_DIST_Y); seq_sprites[56].obj_attr_0 = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(y); seq_sprites[56].obj_attr_1 = OBJ_SIZE_BIG | OBJ_X_COORD(x); int base_tile = seq_sprites[56].base_tile; if (current_selection == SEQ_SELECT_CHANNEL) { seq_sprites[56].obj_attr_2 = base_tile | OBJ_PAL_BANK(2); } else { seq_sprites[56].obj_attr_2 = base_tile | OBJ_PAL_BANK(3); } } // TODO: Hide all parameter control sprites for now, we unhide it later. // Very inefficient but we may change the parameter rendering of all modes // to use bitmaps instead of sprites later. for (size_t i = 34; i <= 50; ++i) { seq_sprites[i].obj_attr_0 |= OBJ_HIDDEN; } if (current_selection == SEQ_SELECT_TRIGGER && channel_selection_loc == 2) { u8 *wave_a = sequences[channel_selection_loc][trig_selection_loc].wave_a; u8 *wave_b = sequences[channel_selection_loc][trig_selection_loc].wave_b; // Draw wave patterns for this trig. int x = SEQ_ENV_POS_X + 8; int y = SEQ_ENV_POS_Y; // Clear wave A and draw. draw_fill_rect(x, y, x + 64, y + 16, COLOR_BLACK); draw_wave_pattern(wave_a, x, y, COLOR_RED); // Clear wave B and draw. draw_fill_rect(x + 64 + 16, y, x + 64 * 2 + 16, y + 16, COLOR_BLACK); draw_wave_pattern(wave_b, x + 64 + 16, y, COLOR_CYAN); } else if ((current_selection == SEQ_SELECT_TRIGGER || current_selection == SEQ_SELECT_PARAMETER) && channel_selection_loc == 0) { for (size_t i = 34; i <= 50; ++i) { seq_sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; } } else if ((current_selection == SEQ_SELECT_TRIGGER || current_selection == SEQ_SELECT_PARAMETER) && channel_selection_loc == 1) { for (size_t i = 34; i < 43; ++i) { seq_sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; } seq_sprites[50].obj_attr_0 &= ~OBJ_HIDDEN; } else { int x = SEQ_ENV_POS_X + 8; int y = SEQ_ENV_POS_Y; // Clear parameters for channel 3. draw_fill_rect(x, y, x + 64, y + 16, COLOR_BLACK); draw_fill_rect(x + 64 + 16, y, x + 64 * 2 + 16, y + 16, COLOR_BLACK); } } void handle_sequencer_input(void) { SeqTrigger *trig = &sequences[channel_selection_loc][trig_selection_loc]; if (current_selection == SEQ_SELECT_TRIGGER) { if (key_pressed(KEY_LEFT)) { if (trig_selection_loc == 0 || trig_selection_loc == 8) { current_selection = SEQ_SELECT_CHANNEL; } else { trig_selection_loc = MAX(trig_selection_loc - 1, 0); } } else if (key_pressed(KEY_RIGHT)) { if (trig_selection_loc != 7) { trig_selection_loc = MIN(trig_selection_loc + 1, 15); } } else if (key_pressed(KEY_UP) || key_pressed(KEY_DOWN)) { trig_selection_loc = (trig_selection_loc + 8) % 16; } else if (key_pressed(KEY_B)) { trig->trigger ^= 1; } else if (key_pressed(KEY_L)) { trig->note = MAX(trig->note - 1, NOTE_C_2); } else if (key_pressed(KEY_R)) { trig->note = MIN( trig->note + 1, NOTE_C_8); } else if (key_pressed(KEY_A)) { // Switch to parameter selection. current_selection = SEQ_SELECT_PARAMETER; } } else if (current_selection == SEQ_SELECT_PARAMETER) { // Move through the selected synth parameters. if (key_pressed(KEY_LEFT)) { int max_param = 6; if (channel_selection_loc == 1) { max_param = 3; } if (param_selection_loc == 0) { param_selection_loc = max_param; } else { param_selection_loc = MAX(param_selection_loc - 1, 0); } } if (key_pressed(KEY_RIGHT)) { int max_param = 6; if (channel_selection_loc == 1) { max_param = 3; } if (param_selection_loc == max_param) { param_selection_loc = 0; } else { param_selection_loc = MIN(param_selection_loc + 1, max_param); } } // Adjust the parameters up or down. if (key_pressed(KEY_L)) { switch (param_selection_loc) { case 0: { trig->env_volume = MAX(trig->env_volume - 1, 0); } break; case 1: { trig->env_time = MAX(trig->env_time - 1, 0); } break; case 2: { trig->env_direction ^= 1; } break; case 3: { trig->duty_cycle = MAX(trig->duty_cycle - 1, 0); } break; case 4: { trig->sweep_number = MAX(trig->sweep_number - 1, 0); } break; case 5: { trig->sweep_time = MAX(trig->sweep_time - 1, 0); } break; case 6: { if (trig->sweep_direction == 0) { trig->sweep_direction = 1; } else { trig->sweep_direction = 0; } } break; } } if (key_pressed(KEY_R)) { switch (param_selection_loc) { case 0: { trig->env_volume = MIN(trig->env_volume + 1, 15); } break; case 1: { trig->env_time = MIN(trig->env_time + 1, 7); } break; case 2: { trig->env_direction ^= 1; } break; case 3: { trig->duty_cycle = MIN(trig->duty_cycle + 1, 3); } break; case 4: { trig->sweep_number = MIN(trig->sweep_number + 1, 7); } break; case 5: { trig->sweep_time = MIN(trig->sweep_time + 1, 7); } break; case 6: { trig->sweep_direction ^= 1; } break; } } // Go back to trigger selection. if (key_pressed(KEY_A)) { current_selection = SEQ_SELECT_TRIGGER; } // Enable disable trigger. if (key_pressed(KEY_B)) { trig->trigger ^= 1; } } else if (current_selection == SEQ_SELECT_CHANNEL) { if (key_pressed(KEY_RIGHT)) { current_selection = SEQ_SELECT_TRIGGER; trig_selection_loc = 0; } if (key_pressed(KEY_UP)) { if (channel_selection_loc == 0) { channel_selection_loc = SEQ_N_CHANNELS - 1; } else { channel_selection_loc = MAX(channel_selection_loc - 1, 0); } } if (key_pressed(KEY_DOWN)) { if (channel_selection_loc == SEQ_N_CHANNELS - 1) { channel_selection_loc = 0; } else { channel_selection_loc = MIN(channel_selection_loc + 1, SEQ_N_CHANNELS); } } } if (key_pressed(KEY_START)) { step_counter = 0; if ((TIMER_CTRL_0 & TIMER_CTRL_ENABLE) == 0) { set_time(bpm); } else { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; } } if (key_pressed(KEY_SELECT)) { TIMER_CTRL_0 ^= TIMER_CTRL_ENABLE; SOUND_SQUARE1_CTRL = 0; SOUND_SQUARE2_CTRL = 0; SOUND_WAVE_CTRL = 0; } } 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; } } void init_sequencer() { init_sequencer_sprites(); SOUND_STATUS = SOUND_ENABLE; SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1 | SOUND_SQUARE2 | SOUND_WAVE, 3); SOUND_DSOUND_MASTER = SOUND_DMG25; }