/* Copyright (c) 2021 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. */ #include #include "common.h" #include "bitmap.h" #include "filesystem.c" #include "rom.c" #include "uxn/uxn.h" #include "uxn/uxn.c" #include "uxn/devices/ppu.h" #include "uxn/devices/ppu.c" #include "text.h" // // Config parameters. // #ifndef TEXT_LAYER #define TEXT_LAYER ppu.fg #endif #ifndef CONTROL_METHODS #define CONTROL_METHODS CONTROL_CONTROLLER,CONTROL_MOUSE,CONTROL_KEYBOARD #endif typedef enum { CONTROL_CONTROLLER, CONTROL_MOUSE, CONTROL_KEYBOARD, } ControlMethod; const ControlMethod ctrl_methods[] = { CONTROL_METHODS }; static ControlMethod ctrl_idx = 0; #define MOUSE_DELTA 1 typedef struct Mouse { int x; int y; } Mouse; static Ppu ppu; static Device *devscreen; static Device *devctrl; static Device *devmouse; static Mouse mouse = {SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2}; void nil_talk(Device *d, u8 b0, u8 w) { (void)d; (void)b0; (void)w; } void console_talk(Device *d, u8 b0, u8 w) { char stmp[2]; if(!w) { return; } switch(b0) { case 0x8: stmp[0] = d->dat[0x8]; stmp[1] = 0; txt_printf(stmp); break; case 0x9: txt_printf("0x%02x", d->dat[0x9]); break; case 0xb: txt_printf("0x%04x", mempeek16(d->dat, 0xa)); break; case 0xd: txt_printf("%s", &d->mem[mempeek16(d->dat, 0xc)]); break; } } void system_talk(Device *d, u8 b0, u8 w) { if(!w) { d->dat[0x2] = d->u->wst.ptr; d->dat[0x3] = d->u->rst.ptr; } else { putcolors(&d->dat[0x8]); } (void)b0; } IWRAM_CODE void screen_talk(Device *d, u8 b0, u8 w) { if(w && b0 == 0xe) { u16 x = mempeek16(d->dat, 0x8); u16 y = mempeek16(d->dat, 0xa); u8 *addr = &d->mem[mempeek16(d->dat, 0xc)]; u8 *layer = d->dat[0xe] >> 4 & 0x1 ? ppu.fg : ppu.bg; u8 mode = d->dat[0xe] >> 5; if(!mode) { putpixel(layer, x, y, d->dat[0xe] & 0x3); } else if(mode-- & 0x1) { puticn(layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); } else { putchr(layer, x, y, addr, d->dat[0xe] & 0xf, mode & 0x2, mode & 0x4); } } } void datetime_talk(Device *d, u8 b0, u8 w) { (void)d; (void)b0; (void)w; } void file_talk(Device *d, u8 b0, u8 w) { u8 read = b0 == 0xd; if(w && (read || b0 == 0xf)) { char *name = (char *)&d->mem[mempeek16(d->dat, 0x8)]; u16 result = 0, length = mempeek16(d->dat, 0xa); u16 offset = mempeek16(d->dat, 0x4); u16 addr = mempeek16(d->dat, b0 - 1); OpenMode mode = FS_OPEN_READ; if (!read) { mode = offset ? FS_OPEN_APPEND : FS_OPEN_WRITE; } File file = fs_open_file(name, mode); if (file.index != FS_NULL) { if(fs_seek(&file, offset, SEEK_SET) != -1) { if (read) { result = fs_read(&d->mem[addr], length, &file); } else { result = fs_write(&d->mem[addr], length, &file); } } } mempoke16(d->dat, 0x2, result); } } void init_uxn(Uxn *u) { // Initialize PPU. initppu(&ppu, 30, 20, 0); // Copy rom to VM. memcpy(u->ram.dat + PAGE_PROGRAM, uxn_rom, sizeof(uxn_rom)); // Prepare devices. portuxn(u, 0x0, "system", system_talk); portuxn(u, 0x1, "console", console_talk); devscreen = portuxn(u, 0x2, "screen", screen_talk); portuxn(u, 0x3, "---", nil_talk); portuxn(u, 0x4, "---", nil_talk); portuxn(u, 0x5, "---", nil_talk); portuxn(u, 0x6, "---", nil_talk); portuxn(u, 0x7, "---", nil_talk); devctrl = portuxn(u, 0x8, "controller", nil_talk); devmouse = portuxn(u, 0x9, "mouse", nil_talk); portuxn(u, 0xa, "file", file_talk); portuxn(u, 0xb, "datetime", datetime_talk); portuxn(u, 0xc, "---", nil_talk); portuxn(u, 0xd, "---", nil_talk); portuxn(u, 0xe, "---", nil_talk); portuxn(u, 0xf, "---", nil_talk); mempoke16(devscreen->dat, 2, ppu.hor * 8); mempoke16(devscreen->dat, 4, ppu.ver * 8); } IWRAM_CODE void handle_input(Uxn *u) { poll_keys(); if (key_tap(KEY_SELECT)) { // Reset control variables on method switch. switch (ctrl_methods[ctrl_idx]) { case CONTROL_CONTROLLER: { devctrl->dat[2] = 0; evaluxn(u, mempeek16(devctrl->dat, 0)); devctrl->dat[3] = 0; } break; case CONTROL_MOUSE: { devmouse->dat[6] = 0; devmouse->dat[7] = 0; mempoke16(devmouse->dat, 0x2, -10); mempoke16(devmouse->dat, 0x4, -10); evaluxn(u, mempeek16(devmouse->dat, 0)); } break; case CONTROL_KEYBOARD: { toggle_keyboard(); } break; } // Update ctrl_idx. ctrl_idx = (ctrl_idx + 1 > (int)LEN(ctrl_methods) - 1) ? 0 : ctrl_idx + 1; // Initialize controller variables here. if (ctrl_methods[ctrl_idx] == CONTROL_KEYBOARD) { toggle_keyboard(); } } if (ctrl_methods[ctrl_idx] == CONTROL_CONTROLLER) { // TODO: We don't need ifs if we use KEY_INPUTS directly and mayvbe just // swap some things if needed. u8 *flag = &devctrl->dat[2]; if (key_pressed(KEY_A)) { *flag |= 0x01; } else { *flag &= ~0x01; } if (key_pressed(KEY_B)) { *flag |= 0x02; } else { *flag &= ~0x02; } if (key_pressed(KEY_L)) { *flag |= 0x04; } else { *flag &= ~0x04; } if (key_pressed(KEY_R)) { *flag |= 0x08; } else { *flag &= ~0x08; } if (key_pressed(KEY_UP)) { *flag |= 0x10; } else { *flag &= ~0x10; } if (key_pressed(KEY_DOWN)) { *flag |= 0x20; } else { *flag &= ~0x20; } if (key_pressed(KEY_LEFT)) { *flag |= 0x40; } else { *flag &= ~0x40; } if (key_pressed(KEY_RIGHT)) { *flag |= 0x80; } else { *flag &= ~0x80; } evaluxn(u, mempeek16(devctrl->dat, 0)); devctrl->dat[3] = 0; } else if (ctrl_methods[ctrl_idx] == CONTROL_MOUSE) { // Detect "mouse key press". u8 flag = devmouse->dat[6]; if (key_tap(KEY_B)) { flag |= 0x01; } else if (key_released(KEY_B)) { flag &= ~0x01; } if (key_tap(KEY_A)) { flag |= 0x10; } else if (key_released(KEY_A)) { flag &= ~0x10; } // Handle chording. devmouse->dat[6] = flag; if(flag == 0x10 && (devmouse->dat[6] & 0x01)) { devmouse->dat[7] = 0x01; } if(flag == 0x01 && (devmouse->dat[6] & 0x10)) { devmouse->dat[7] = 0x10; } // Detect mouse movement. if (key_pressed(KEY_UP)) { mouse.y = CLAMP(mouse.y - MOUSE_DELTA, 0, SCREEN_HEIGHT - 8); } else if (key_pressed(KEY_DOWN)) { mouse.y = CLAMP(mouse.y + MOUSE_DELTA, 0, SCREEN_HEIGHT - 8); } if (key_pressed(KEY_LEFT)) { mouse.x = CLAMP(mouse.x - MOUSE_DELTA, 0, SCREEN_WIDTH - 8); } else if (key_pressed(KEY_RIGHT)) { mouse.x = CLAMP(mouse.x + MOUSE_DELTA, 0, SCREEN_WIDTH - 8); } // Eval mouse. mempoke16(devmouse->dat, 0x2, mouse.x); mempoke16(devmouse->dat, 0x4, mouse.y); evaluxn(u, mempeek16(devmouse->dat, 0)); } else if (ctrl_methods[ctrl_idx] == CONTROL_KEYBOARD) { if (key_tap(KEY_LEFT)) { update_cursor(cursor_position - 1); } else if (key_tap(KEY_RIGHT)) { update_cursor(cursor_position + 1); } if (key_tap(KEY_UP) && cursor_position >= KEYBOARD_ROW_SIZE) { update_cursor(cursor_position - KEYBOARD_ROW_SIZE); } else if (key_tap(KEY_DOWN) && cursor_position < LEN(keyboard) - KEYBOARD_ROW_SIZE) { update_cursor(cursor_position + KEYBOARD_ROW_SIZE); } if (key_tap(KEY_B)) { u8 symbol = keyboard[cursor_position].symbol; switch (symbol) { case 0x7f: { // Backspace. devctrl->dat[3] = 0x08; } break; case 0x14: { // New line. devctrl->dat[3] = 0x0d; } break; case 0x18: { // Arrow up. devctrl->dat[2] = 0x10; } break; case 0x19: { // Arrow down. devctrl->dat[2] = 0x20; } break; case 0x1b: { // Arrow left. devctrl->dat[2] = 0x40; } break; case 0x1a: { // Arrow right. devctrl->dat[2] = 0x80; } break; default: { devctrl->dat[3] = symbol; } break; } evaluxn(u, mempeek16(devctrl->dat, 0)); devctrl->dat[3] = 0; } } } static Uxn u; EWRAM_BSS static u8 umem[65536]; #include "kick.c" typedef struct AudioChannel { u32 *samples; u32 n_samples; u16 sampling_freq; bool loop; // TODO: u16 adsr; // attack, decay, sustain, release // TODO: u8 pitch; // Bit 8 is the "loop" bit // TODO: u8 volume; // VOL_LEFT | (VOL_RIGHT << 4) } AudioChannel; typedef struct APU { AudioChannel chan_0; // u32 *samples_1; // u32 *samples_2; // u32 *samples_3; } APU; static APU apu = {0}; void reset_sound(AudioChannel *chan) { TIMER_CTRL_0 = 0; TIMER_CTRL_1 = 0; DMA_CTRL(1) = 0; // Set max volume, left-right sound, fifo reset and use timer 0 for // DirectSound A. SOUND_DSOUND_MASTER = SOUND_DSOUND_RATIO_A | SOUND_DSOUND_LEFT_A | SOUND_DSOUND_RIGHT_A | SOUND_DSOUND_RESET_A; // Prepare DMA copy. dma_transfer_copy(SOUND_FIFO_A, chan->samples, 1, 1, DMA_CHUNK_32 | DMA_REFRESH | DMA_REPEAT | DMA_ENABLE); // Timer 1 used to stop playing samples. u32 sample_duration = chan->n_samples; TIMER_DATA_1 = 0xFFFF - sample_duration; TIMER_CTRL_1 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; // Timer 0 used to stop sample playing. TIMER_DATA_0 = 0xFFFF - CPU_FREQUENCY / chan->sampling_freq; TIMER_CTRL_0 = TIMER_CTRL_ENABLE; } void irs_stop_sample(void) { if (apu.chan_0.loop) { reset_sound(&apu.chan_0); } else { TIMER_CTRL_0 = 0; DMA_CTRL(1) = 0; } } void init_sound(AudioChannel *chan) { chan->samples = voiceraw; chan->n_samples = LEN(voiceraw); chan->sampling_freq = 44100; chan->loop = true; } int main(void) { // Initialize filesystem. fs_init(); // Register interrupts. irq_init(); irs_set(IRQ_VBLANK, irs_stub); irs_set(IRQ_TIMER_1, irs_stop_sample); // Initialize VM. memset(&u, 0, sizeof(u)); u.ram.dat = umem; init_uxn(&u); // Initialize text engine. txt_init(1, TEXT_LAYER); txt_position(0,0); txt_printf("VOICE LOADED: %lu\n", LEN(voiceraw)); // Enable sound. SOUND_STATUS = SOUND_ENABLE; init_sound(&apu.chan_0); reset_sound(&apu.chan_0); // Main loop. // int frame_counter = 0; // evaluxn(&u, 0x0100); // u32 flip_cycles = 0; while(true) { bios_vblank_wait(); // profile_start(); // handle_input(&u); // u32 input_cycles = profile_stop(); // profile_start(); // evaluxn(&u, mempeek16(devscreen->dat, 0)); // u32 eval_cycles = profile_stop(); // txt_position(0, 8); // txt_printf("INPUT: %lu \n", input_cycles); // txt_printf("EVAL: %lu \n", eval_cycles); // txt_printf("FLIP: %lu \n", flip_cycles); // profile_start(); flipbuf(&ppu); // flip_cycles = profile_stop(); // frame_counter++; poll_keys(); if (key_tap(KEY_B)) { txt_printf("TAP B\n"); reset_sound(&apu.chan_0); } } return 0; }