/* Copyright (c) 2023 Bad Diode Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ // TODO: A list of features I would like to get to implement in the near future. // // Remaining issues: // // + Higher resolution clock to allow for microtiming and more accurate tempo. // + Look back again at the emulator issues... (I give up) // + Sync via MIDI with the Analogue cables. // + Fix bank switching behaviour (bug) // + Fix bug: Clipboard doesn't copy probability/pan? maybe others? // + Add more sync options // + When switching sync, play status acts wonky (bug) // + Channel params should show if there are some already on all triggers and // modify only the selected parameter, not all of them. // + Channel params (ALL) editing should only copy the value under the cursor. // + Fix scale being active for noise channel (makes no sense) // + Save scale on metadata // + Allow using B + dpad to nudge trigs. This will potentially mean switching // + Hold L/R retriggers at short intervals? // to key release to enable trigs. // + Fix bug with not being able to stop the sound when synced // + Add attack and decay envelope for ch3 (synced to tempo) // + Fix A+B on ch3 // + Make sure Attack/Decay are grey for A+B // + Add help for attack/decay on ch3 // + Bad performance when selecting patterns? // + Make sure the chain is always shown on the notification bar if playing // + Add more sync in/out options // + Add more help hints when holding SEL // + Add AD envelope drawing instead of numbers. // - Fix any bugs we currently have // - Add clipboard sharing between banks. // - Study saving overhauls for bootleg cartridges. // - Add tap tempo for BPM. // - Improve interrupt handler to allow nesting/prioritization. // #include "gba/gba.h" #define PROF_ENABLE 0 #include "profiling.c" #include "renderer_m0.c" #include "globals.c" #include "assets.c" #include "patterns.c" #include "settings.c" #include "dsound.c" #include "scale.c" #include "sequencer.c" static int frames = 0; void render_sequencer(void) { if (redraw_trigs) { draw_triggers(); redraw_trigs = false; } if (redraw_channels) { draw_channels(); redraw_channels = false; } if (redraw_pattern_buttons) { draw_pattern_buttons(); redraw_pattern_buttons = false; } if (redraw_bank_buttons) { draw_bank_buttons(); redraw_bank_buttons = false; } if (redraw_bpm) { draw_bpm(); redraw_bpm = false; } if (redraw_play_pause) { draw_play(); draw_stop(); draw_settings(); redraw_play_pause = false; } if (redraw_scale) { draw_scale(); redraw_scale = false; } if (redraw_params) { draw_parameters(); redraw_params = false; } if (input_handler == handle_pattern_selection || input_handler == handle_pattern_chain) { if (frames & 0x1) { draw_pattern_chain(); } } if (frames & 0x1) { draw_notif_bar(); } if (frames++ % 0x4 == 0) { draw_piano_notes(); } draw_cursors(); } void render_settings(void) { size_t x0 = 8; size_t x1 = x0 + 64; size_t y0 = 6; // Header. txt_drawf("SETTINGS", x0, y0, COL_FG) draw_wrench(SCREEN_WIDTH - 32, y0, COL_FG); y0 = 16; draw_filled_rect(x0, y0 - 1, SCREEN_WIDTH - 9, y0, COL_FG); y0 = 20; // Globals. txt_drawf("GLOBAL", x0, y0, COL_FG); draw_line(x0, y0 + 9, SCREEN_WIDTH - 9, y0 + 9, COL_FG); y0 += 17; draw_rect(x0, y0, x1, y0 + 10, COL_FG); txt_drawf("MUTE", x0 + 2, y0 + 1, COL_FG); txt_drawf("%s", x1 + 8, y0 + 1, COL_FG, toggle_settings_str[settings.global_mute]); y0 += 17; draw_rect(x0, y0, x1, y0 + 10, COL_FG); txt_drawf("BPM", x0 + 2, y0 + 1, COL_FG); txt_drawf("%s", x1 + 8, y0 + 1, COL_FG, toggle_settings_str[settings.global_bpm]); y0 += 17; txt_drawf("GENERAL", x0, y0, COL_FG); draw_line(x0, y0 + 9, SCREEN_WIDTH - 9, y0 + 9, COL_FG); y0 += 17; draw_rect(x0, y0, x1, y0 + 10, COL_FG); txt_drawf("AUTO-SAVE", x0 + 2, y0 + 1, COL_FG); txt_drawf("%s", x1 + 8, y0 + 1, COL_FG, toggle_settings_str[settings.auto_save]); y0 += 17; draw_rect(x0, y0, x1, y0 + 10, COL_FG); txt_drawf("SYNC", x0 + 2, y0 + 1, COL_FG); txt_drawf("%s", x1 + 8, y0 + 1, COL_FG, sync_setting_str[settings.sync]); y0 += 17; draw_rect(x0, y0, x1, y0 + 10, COL_FG); txt_drawf("THEME", x0 + 2, y0 + 1, COL_FG); txt_drawf("%s", x1 + 8, y0 + 1, COL_FG, theme_setting_str[settings.theme]); y0 += 17; draw_rect(x0, y0, x1, y0 + 10, COL_FG); txt_drawf("HELP", x0 + 2, y0 + 1, COL_FG); txt_drawf("%s", x1 + 8, y0 + 1, COL_FG, toggle_settings_str[settings.help]); draw_settings_cursor(); } void render(void) { if (clear_screen) { PROF(screen_fill(COL_BG), PROF_FILL); clear_screen = false; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; redraw_bank_buttons = true; redraw_bpm = true; redraw_play_pause = true; redraw_params = true; redraw_scale = true; } switch (scene) { case SCENE_SETTINGS: { render_settings(); } break; case SCENE_SEQUENCER: { render_sequencer(); } break; } // DEBUG: Ensuring the saved data don't exceed SRAM size (32k). // txt_drawf("SIZE: %lu", 0, 0, COL_ACC_1, sizeof(Metadata) + sizeof(patterns) * 6 + sizeof(chain) * 6); } void handle_input(void) { #if PROF_ENABLE > 0 if (key_hold(KEY_SELECT)) { if (key_tap(KEY_DOWN)) { PROF_SHOW(); clear_screen = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; redraw_bank_buttons = true; redraw_bpm = true; redraw_play_pause = true; redraw_params = true; redraw_scale = true; return; } static int prof_detail = 0; if (key_tap(KEY_RIGHT)) { prof_detail = CLAMP(prof_detail + 1, 0, 3); PROF_DETAIL(prof_detail); clear_screen = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; redraw_bank_buttons = true; redraw_bpm = true; redraw_play_pause = true; redraw_params = true; redraw_scale = true; return; } if (key_tap(KEY_LEFT)) { prof_detail = CLAMP(prof_detail - 1, 0, 3); PROF_DETAIL(prof_detail); clear_screen = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; redraw_bank_buttons = true; redraw_bpm = true; redraw_play_pause = true; redraw_params = true; redraw_scale = true; return; } } #endif switch (scene) { case SCENE_SETTINGS: { handle_settings_input(); } break; case SCENE_SEQUENCER: { handle_sequencer_input(); } break; } } void update(void) { if (next_scene != scene) { scene = next_scene; clear_screen = true; redraw_trigs = true; redraw_channels = true; redraw_pattern_buttons = true; redraw_bank_buttons = true; redraw_bpm = true; redraw_play_pause = true; redraw_params = true; redraw_scale = true; } last_trig_loc = trig_selection_loc; last_channel_loc = channel_selection_loc; last_pattern_loc = pattern_selection_loc; last_right_col_loc = right_col_selection_loc; if (update_bpm) { if (settings.global_bpm) { set_time(settings.bpm); } else { set_time(patterns[current_pattern].bpm); } update_bpm = false; } if (audio_sync_click) { play_click(); } if (notif.time > 0) { notif.time--; } rng_state++; } int main(void) { // Adjust system wait times. SYSTEM_WAIT = SYSTEM_WAIT_CARTRIDGE; // Initialize renderer. renderer_init(); // Register interrupts. irq_init(); irs_set(IRQ_VBLANK, sound_vsync); // Initialize sequencer. sequencer_init(); txt_spacing(6); // Main loop. PROF_INIT(); while (true) { poll_keys(); bios_vblank_wait(); FRAME_START(); PROF(flip_buffer(), PROF_FLIP); PROF(update(), PROF_UPDATE); PROF(handle_input(), PROF_INPUT); PROF(render(), PROF_RENDER); FRAME_END(); } return 0; }