diff --git a/Makefile b/Makefile index 78c85ee..af1ff70 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,7 @@ sec: $(BUILDDIR) sec/*.h sec/*.c | $(BUILDDIR) tools: $(BUILDDIR) $(TOOLSDIR)/*.c vm/*.h | $(BUILDDIR) $(CC) $(CFLAGS) $(TINY16_LDFLAGS) -o $(BUILDDIR)/png2tiles$(EXE_EXT) $(TOOLSDIR)/png2tiles.c -lm + $(CC) $(CFLAGS) $(TINY16_LDFLAGS) -o $(BUILDDIR)/midi2music$(EXE_EXT) $(TOOLSDIR)/midi2music.c -lm emulator: $(BUILDDIR) vm/*.c vm/*.h emulator/*.c $(RAYLIB_LIB_NATIVE) | $(BUILDDIR) $(CC) $(CFLAGS) $(TINY16_LDFLAGS) $(RAYLIB_INCLUDE) -o $(BUILDDIR)/tiny16-emu$(EXE_EXT) \ @@ -157,8 +158,11 @@ EXAMPLES_ASM_OUTPUTS := $(patsubst examples/asm/%.asm,$(BUILDDIR)/%.tiny16,$(EXA EXAMPLES_SE := $(wildcard examples/se/*.se) EXAMPLES_SE_OUTPUTS := $(patsubst examples/se/%.se,$(BUILDDIR)/%_se.tiny16,$(EXAMPLES_SE)) -EXAMPLES_ASSETS := $(wildcard examples/assets/*.png) -EXAMPLES_ASSETS_OUTPUTS := $(patsubst examples/assets/%.png,examples/includes/%.inc,$(EXAMPLES_ASSETS)) +EXAMPLES_ASSETS_PNG := $(wildcard examples/assets/*.png) +EXAMPLES_ASSETS_PNG_OUTPUTS := $(patsubst examples/assets/%.png,examples/includes/%.inc,$(EXAMPLES_ASSETS_PNG)) + +EXAMPLES_ASSETS_MIDI := $(wildcard examples/assets/*.mid) +EXAMPLES_ASSETS_MIDI_OUTPUTS := $(patsubst examples/assets/%.mid,stdlib/se/%.se,$(EXAMPLES_ASSETS_MIDI)) EXAMPLE_INCLUDES := $(wildcard stdlib/*.inc) $(wildcard examples/includes/*.inc) $(wildcard asm/tutorial/includes/*.inc) @@ -178,11 +182,15 @@ $(BUILDDIR)/%_se.tiny16: examples/se/%.se $(EXAMPLE_INCLUDES) | $(BUILDDIR) $(BUILDDIR)/tiny16-asm$(EXE_EXT) $(BUILDDIR)/$*_se.asm $@ # PNG -> INC: convert PNG assets to assembly include files -examples-assets: tools $(EXAMPLES_ASSETS_OUTPUTS) +# MIDI -> SE: convert MIDI assets to S-expression music modules +examples-assets: tools $(EXAMPLES_ASSETS_PNG_OUTPUTS) $(EXAMPLES_ASSETS_MIDI_OUTPUTS) examples/includes/%.inc: examples/assets/%.png | tools $(BUILDDIR)/png2tiles$(EXE_EXT) $< $@ +stdlib/se/%.se: examples/assets/%.mid | tools + $(BUILDDIR)/midi2music$(EXE_EXT) $< -n $* -v 5 -s 0.5 -o $@ + $(RAYLIB_LIB_NATIVE): @echo "Building raylib for native..." @if [ -f "$(RAYLIB_LIB_WEB)" ]; then \ diff --git a/emulator/tiny16_core.c b/emulator/tiny16_core.c index 52bf311..c466b94 100644 --- a/emulator/tiny16_core.c +++ b/emulator/tiny16_core.c @@ -8,60 +8,12 @@ // Global pointer for audio callback (raylib doesn't support user data in callback) static Tiny16Emulator* g_emu = NULL; -#define TINY16_AUDIO_RING_MASK (TINY16_AUDIO_RING_FRAMES - 1u) - -static void tiny16_audio_ring_write(Tiny16Emulator* emu, const float* samples, uint32_t frames) { - uint32_t write = emu->audio_write; - uint32_t read = emu->audio_read; - uint32_t free_frames = TINY16_AUDIO_RING_FRAMES - (write - read); - if (free_frames == 0) return; - if (frames > free_frames) frames = free_frames; - for (uint32_t i = 0; i < frames; i++) { - emu->audio_ring[write & TINY16_AUDIO_RING_MASK] = samples[i]; - write++; - } - emu->audio_write = write; -} - -static uint32_t tiny16_audio_ring_read(Tiny16Emulator* emu, float* buffer, uint32_t frames) { - uint32_t write = emu->audio_write; - uint32_t read = emu->audio_read; - uint32_t available = write - read; - if (available == 0) return 0; - if (frames > available) frames = available; - for (uint32_t i = 0; i < frames; i++) { - buffer[i] = emu->audio_ring[read & TINY16_AUDIO_RING_MASK]; - read++; - } - emu->audio_read = read; - return frames; -} - static void tiny16_audio_callback(void* buffer, unsigned int frames) { if (g_emu) { - float* out = (float*)buffer; - uint32_t got = tiny16_audio_ring_read(g_emu, out, frames); - if (got < frames) { - memset(out + got, 0, (frames - got) * sizeof(float)); - } - } else { - memset(buffer, 0, frames * sizeof(float)); - } -} - -static void tiny16_emu_audio_produce(Tiny16Emulator* emu, uint32_t cpu_cycles) { - if (cpu_cycles == 0) return; - uint32_t frames_needed = - tiny16_apu_samples_for_cycles(&emu->vm->apu, cpu_cycles, TINY16_CPU_HZ); - if (frames_needed == 0) return; - - float temp[1024]; - while (frames_needed > 0) { - uint32_t chunk = frames_needed > 1024 ? 1024 : frames_needed; - tiny16_apu_generate_samples(&emu->vm->apu, temp, chunk); - tiny16_audio_ring_write(emu, temp, chunk); - frames_needed -= chunk; + tiny16_apu_generate_samples(&g_emu->vm->apu, (float*)buffer, frames); + return; } + memset(buffer, 0, frames * sizeof(float)); } Tiny16Emulator* tiny16_emu_create(Tiny16VM* vm, bool program_loaded) { @@ -73,8 +25,6 @@ Tiny16Emulator* tiny16_emu_create(Tiny16VM* vm, bool program_loaded) { emu->program_loaded = program_loaded; emu->audio_enabled = false; memset(emu->back_buffer, 0, sizeof(emu->back_buffer)); - emu->audio_read = 0; - emu->audio_write = 0; // Pre-compute RGB332 -> RGBA lookup table for (int i = 0; i < 256; i++) { @@ -225,15 +175,12 @@ void tiny16_emu_update_frame(Tiny16Emulator* emu) { tiny16_emu_update_input(vm); - uint32_t instr_executed = 0; for (uint32_t step = 0; step < instr_this_frame; ++step) { if (!tiny16_vm_step(vm)) { emu->paused = true; break; } - instr_executed++; } - tiny16_emu_audio_produce(emu, instr_executed); } BeginDrawing(); diff --git a/emulator/tiny16_core.h b/emulator/tiny16_core.h index bda29c8..805d434 100644 --- a/emulator/tiny16_core.h +++ b/emulator/tiny16_core.h @@ -12,7 +12,6 @@ #define TINY16_EMU_SCREEN_HEIGHT (TINY16_EMU_PIXEL_HEIGHT * 8) #define TINY16_EMU_TARGET_IPS (60.0f * 30000) // 1.8M IPS = ~30000 instr/frame at 60 FPS #define TINY16_CPU_HZ 1800000u -#define TINY16_AUDIO_RING_FRAMES 16384u typedef struct { Tiny16VM* vm; @@ -26,9 +25,6 @@ typedef struct { bool paused; bool program_loaded; bool audio_enabled; - float audio_ring[TINY16_AUDIO_RING_FRAMES]; - volatile uint32_t audio_read; - volatile uint32_t audio_write; } Tiny16Emulator; Tiny16Emulator* tiny16_emu_create(Tiny16VM* vm, bool program_loaded); diff --git a/examples/asm/apu_demo.asm b/examples/asm/apu_demo.asm deleted file mode 100644 index 616da87..0000000 --- a/examples/asm/apu_demo.asm +++ /dev/null @@ -1,3779 +0,0 @@ -; Generated by tiny16se compiler -; Source: examples/se/apu_demo.se - -; Constants -LAST_FRAME_ADDR = 0x4000 -SPRITE_INDEX_ADDR = 0x4002 -GFX_TILES_BASE = 0x5000 -GFX_OAM_BASE = 0x9000 -OAM_HI = 0x78 -OAM_LO = 0x00 -MMIO_BASE = 0xBF00 -KEYS_PRESSED_ADDR = 0xBF01 -FRAME_COUNT_ADDR = 0xBF22 -PPU_CTRL_ADDR = 0xBF30 -APU_CTRL_ADDR = 0xBF40 -APU_CH0_FREQ_LO = 0xBF42 -APU_CH0_FREQ_HI = 0xBF43 -APU_CH0_VOL = 0xBF44 -APU_CH0_CTRL = 0xBF45 -APU_CH0_ENV_AD = 0xBF51 -APU_CH0_ENV_SR = 0xBF52 -APU_CH0_LEN = 0xBF5B -APU_CH0_SWEEP = 0xBF5F -APU_CH1_FREQ_LO = 0xBF46 -APU_CH1_FREQ_HI = 0xBF47 -APU_CH1_VOL = 0xBF48 -APU_CH1_CTRL = 0xBF49 -APU_CH1_ENV_AD = 0xBF53 -APU_CH1_ENV_SR = 0xBF54 -APU_CH1_LEN = 0xBF5C -APU_CH1_SWEEP = 0xBF60 -APU_CH2_FREQ_LO = 0xBF4A -APU_CH2_FREQ_HI = 0xBF4B -APU_CH2_VOL = 0xBF4C -APU_CH2_CTRL = 0xBF4D -APU_CH2_ENV_AD = 0xBF55 -APU_CH2_ENV_SR = 0xBF56 -APU_CH2_LEN = 0xBF5D -APU_CH3_PERIOD = 0xBF4E -APU_CH3_VOL = 0xBF4F -APU_CH3_CTRL = 0xBF50 -APU_CH3_ENV_AD = 0xBF57 -APU_CH3_ENV_SR = 0xBF58 -APU_CH3_LEN = 0xBF5E -APU_WAVE_FREQ_LO = 0xBF61 -APU_WAVE_FREQ_HI = 0xBF62 -APU_WAVE_VOL = 0xBF63 -APU_WAVE_CTRL = 0xBF64 -APU_WAVE_ENV_AD = 0xBF59 -APU_WAVE_ENV_SR = 0xBF5A -APU_WAVE_LEN = 0xBF65 -APU_WAVE_RAM = 0xBF70 -KEY_A = 0x04 -KEY_B = 0x08 -KEY_SELECT = 0x10 -KEY_START = 0x20 -KEY_UP = 0x40 -KEY_DOWN = 0x80 -KEY_LEFT = 0x01 -KEY_RIGHT = 0x02 -APU_DUTY_12_5 = 0x00 -APU_DUTY_25 = 0x10 -APU_DUTY_50 = 0x20 -NOTE_C3 = 0x71A -NOTE_D3 = 0x72B -NOTE_E3 = 0x73B -NOTE_G3 = 0x75D -NOTE_A3 = 0x76C -NOTE_C4 = 0x757 -NOTE_D4 = 0x767 -NOTE_E4 = 0x77A -NOTE_F4 = 0x784 -NOTE_G4 = 0x78F -NOTE_A4 = 0x79C -NOTE_B4 = 0x7A6 -NOTE_C5 = 0x7AB -NOTE_D5 = 0x7B6 -NOTE_E5 = 0x7BD -NOTE_G5 = 0x7CA -NOTE_A5 = 0x7D4 - -section .code - -; Entry point -_start: - CALL main - HALT - -read_frame_count: - MOVSPR R4:R5 - LOADI R6, 0xBF - LOADI R7, 0x22 - MOV R0, R7 - LOAD R0, [R6:R7] - RET - -wait_frame: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R6, 0x40 - LOADI R7, 0x00 - MOV R0, R7 - LOAD R0, [R6:R7] - PUSH R0 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL read_frame_count - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R0 -__L0: - LOAD R0, [R2:R3 - 1] - PUSH R0 - LOAD R0, [R2:R3 + 0] - MOV R1, R0 - POP R0 - CMP R0, R1 - JZ __L2 - LOADI R0, 0 - JMP __L3 -__L2: - LOADI R0, 1 -__L3: - OR R0, R0 - JZ __L1 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL read_frame_count - POP R5 - POP R4 - POP R3 - POP R2 - STORE R0, [R2:R3 - 1] - JMP __L0 -__L1: - LOADI R0, 0 - LOAD R0, [R2:R3 - 1] - PUSH R0 - LOADI R6, 0x40 - LOADI R7, 0x00 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R1 - POP R3 - POP R2 - RET - -wait_frames: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R0, 0x00 - PUSH R0 -__L4: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOAD R0, [R4:R5 + 3] - MOV R1, R0 - POP R0 - CMP R0, R1 - JC __L6 - LOADI R0, 0 - JMP __L7 -__L6: - LOADI R0, 1 -__L7: - OR R0, R0 - JZ __L5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL wait_frame - POP R5 - POP R4 - POP R3 - POP R2 - LOAD R0, [R2:R3 + 0] - INC R0 - STORE R0, [R2:R3 + 0] - JMP __L4 -__L5: - LOADI R0, 0 - POP R1 - POP R3 - POP R2 - RET - -render_frame: - MOVSPR R4:R5 - LOADI R0, 0x82 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x30 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -read_keys_pressed: - MOVSPR R4:R5 - LOADI R6, 0xBF - LOADI R7, 0x01 - MOV R0, R7 - LOAD R0, [R6:R7] - RET - -apu_init: - MOVSPR R4:R5 - LOADI R0, 0x01 - PUSH R0 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x40 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch0_note: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x42 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x43 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x44 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x03 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x45 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch0_env: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x51 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x52 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch0_len: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x5B - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch0_sweep: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - SHL R0 - SHL R0 - SHL R0 - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOAD R0, [R4:R5 + 5] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x5F - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch0_off: - MOVSPR R4:R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x45 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch1_note: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x46 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x47 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x48 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x03 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x49 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch1_env: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x53 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x54 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch1_len: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x5C - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch1_sweep: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - SHL R0 - SHL R0 - SHL R0 - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOAD R0, [R4:R5 + 5] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x60 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch1_off: - MOVSPR R4:R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x49 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch2_note: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4A - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4B - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4C - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x03 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4D - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch2_env: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x55 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x56 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch2_len: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x5D - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch2_off: - MOVSPR R4:R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4D - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch3_noise: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOAD R0, [R4:R5 + 5] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4E - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4F - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x03 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x50 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch3_env: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x57 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x58 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch3_len: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x5E - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -ch3_off: - MOVSPR R4:R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x50 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -wave_note: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x61 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x62 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x63 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x03 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x64 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -wave_env: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 4] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x59 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - SHL R0 - SHL R0 - SHL R0 - SHL R0 - PUSH R0 - LOAD R0, [R4:R5 + 6] - MOV R1, R0 - POP R0 - OR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x5A - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -wave_len: - MOVSPR R4:R5 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x65 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -wave_write: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R0, 0x70 - PUSH R0 - LOAD R0, [R4:R5 + 3] - MOV R1, R0 - POP R0 - ADD R0, R1 - PUSH R0 - LOADI R0, 0xBF - PUSH R0 - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOAD R0, [R2:R3 - 1] - MOV R6, R0 - LOAD R0, [R2:R3 + 0] - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R1 - POP R3 - POP R2 - RET - -wave_off: - MOVSPR R4:R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x64 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - RET - -all_off: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch0_off - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch1_off - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch2_off - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch3_off - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL wave_off - POP R5 - POP R4 - POP R3 - POP R2 - RET - -toggle_ch0: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R6, 0xBF - LOADI R7, 0x45 - MOV R0, R7 - LOAD R0, [R6:R7] - PUSH R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - XOR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x45 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R3 - POP R2 - RET - -toggle_ch1: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R6, 0xBF - LOADI R7, 0x49 - MOV R0, R7 - LOAD R0, [R6:R7] - PUSH R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - XOR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x49 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R3 - POP R2 - RET - -toggle_ch2: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R6, 0xBF - LOADI R7, 0x4D - MOV R0, R7 - LOAD R0, [R6:R7] - PUSH R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - XOR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x4D - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R3 - POP R2 - RET - -toggle_ch3: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R6, 0xBF - LOADI R7, 0x50 - MOV R0, R7 - LOAD R0, [R6:R7] - PUSH R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - XOR R0, R1 - PUSH R0 - LOADI R6, 0xBF - LOADI R7, 0x50 - MOV R0, R7 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R3 - POP R2 - RET - -hide_all_sprites: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R0, 0x00 - PUSH R0 -__L8: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x40 - MOV R1, R0 - POP R0 - CMP R0, R1 - JC __L10 - LOADI R0, 0 - JMP __L11 -__L10: - LOADI R0, 1 -__L11: - OR R0, R0 - JZ __L9 - LOADI R0, 0x00 - PUSH R0 - LOAD R0, [R2:R3 + 0] - SHL R0 - SHL R0 - MOV R1, R0 - POP R0 - ADD R0, R1 - PUSH R0 - LOADI R0, 0xFF - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 - 1] - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 - 1] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - ADD R0, R1 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 - 1] - PUSH R0 - LOADI R0, 0x02 - MOV R1, R0 - POP R0 - ADD R0, R1 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 - 1] - PUSH R0 - LOADI R0, 0x03 - MOV R1, R0 - POP R0 - ADD R0, R1 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - POP R1 - LOAD R0, [R2:R3 + 0] - INC R0 - STORE R0, [R2:R3 + 0] - JMP __L8 -__L9: - LOADI R0, 0 - POP R1 - POP R3 - POP R2 - RET - -draw_char: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R0, 0x00 - PUSH R0 - LOAD R0, [R4:R5 + 6] - SHL R0 - SHL R0 - MOV R1, R0 - POP R0 - ADD R0, R1 - PUSH R0 - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 + 0] - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - ADD R0, R1 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - LOAD R0, [R4:R5 + 5] - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x02 - MOV R1, R0 - POP R0 - ADD R0, R1 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x78 - MOV R6, R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x03 - MOV R1, R0 - POP R0 - ADD R0, R1 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R3 - POP R2 - RET - -draw_text: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - LOADI R0, 0x40 - MOV R6, R0 - LOADI R0, 0x02 - MOV R7, R0 - LOAD R0, [R6:R7] - PUSH R0 - LOAD R0, [R4:R5 + 3] - PUSH R0 - LOAD R0, [R4:R5 + 5] - PUSH R0 - LOAD R0, [R4:R5 + 6] - PUSH R0 -__L12: - LOAD R0, [R2:R3 - 2] - MOV R6, R0 - LOAD R0, [R2:R3 - 3] - MOV R7, R0 - LOAD R0, [R6:R7] - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L14 - LOADI R0, 0 - JMP __L15 -__L14: - LOADI R0, 1 -__L15: - OR R0, R0 - JZ __L13 - LOAD R0, [R2:R3 - 2] - MOV R6, R0 - LOAD R0, [R2:R3 - 3] - MOV R7, R0 - LOAD R0, [R6:R7] - PUSH R0 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOAD R0, [R2:R3 - 4] - PUSH R0 - LOAD R0, [R4:R5 + 4] - PUSH R0 - LOAD R0, [R2:R3 - 1] - PUSH R0 - CALL draw_char - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - LOAD R0, [R2:R3 - 1] - PUSH R0 - LOADI R0, 0x06 - MOV R1, R0 - POP R0 - ADD R0, R1 - STORE R0, [R2:R3 - 1] - LOAD R0, [R2:R3 + 0] - INC R0 - STORE R0, [R2:R3 + 0] - LOAD R0, [R2:R3 - 3] - INC R0 - STORE R0, [R2:R3 - 3] - LOAD R0, [R2:R3 - 3] - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JZ __L18 - LOADI R0, 0 - JMP __L19 -__L18: - LOADI R0, 1 -__L19: - OR R0, R0 - JZ __L16 - LOAD R0, [R2:R3 - 2] - INC R0 - STORE R0, [R2:R3 - 2] - JMP __L17 -__L16: - LOADI R0, 0x00 -__L17: - POP R1 - JMP __L12 -__L13: - LOADI R0, 0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x40 - MOV R6, R0 - LOADI R0, 0x02 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - POP R1 - POP R1 - POP R1 - POP R1 - POP R3 - POP R2 - RET - -init_text_display: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL hide_all_sprites - POP R5 - POP R4 - POP R3 - POP R2 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x40 - MOV R6, R0 - LOADI R0, 0x02 - MOV R7, R0 - POP R0 - STORE R0, [R6:R7] - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0E - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x15 - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x12 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x1D - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x1A - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x25 - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x26 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x2D - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x2E - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x35 - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x36 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x3C - PUSH R0 - LOADI R0, 0x40 - PUSH R0 - LOADI R0, 0x3E - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL draw_text - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL render_frame - POP R5 - POP R4 - POP R3 - POP R2 - RET - -setup_wave_ram: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x00 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x03 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x08 - PUSH R0 - LOADI R0, 0x04 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x05 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0E - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0F - PUSH R0 - LOADI R0, 0x08 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0E - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x0A - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x0B - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x08 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x0D - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x0E - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - LOADI R0, 0x0F - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x01 - PUSH R0 - LOADI R0, 0x10 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x11 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x05 - PUSH R0 - LOADI R0, 0x12 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x13 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x14 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0B - PUSH R0 - LOADI R0, 0x15 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0D - PUSH R0 - LOADI R0, 0x16 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0F - PUSH R0 - LOADI R0, 0x17 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0D - PUSH R0 - LOADI R0, 0x18 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0B - PUSH R0 - LOADI R0, 0x19 - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x1A - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x1B - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x05 - PUSH R0 - LOADI R0, 0x1C - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x1D - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x01 - PUSH R0 - LOADI R0, 0x1E - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x1F - PUSH R0 - CALL wave_write - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - RET - -play_timing_melody: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xAB - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x7A - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x1A - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x57 - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - CALL ch3_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - CALL ch3_noise - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xB6 - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x84 - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x2B - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x67 - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xBD - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x8F - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x3B - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x7A - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - CALL ch3_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - CALL ch3_noise - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xCA - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xA6 - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x5D - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x8F - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xD4 - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xAB - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x6C - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x9C - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - CALL ch3_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - CALL ch3_noise - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xCA - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xA6 - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x5D - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x8F - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xBD - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x8F - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x3B - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x7A - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - CALL ch3_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - CALL ch3_noise - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xB6 - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x09 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x84 - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x2B - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x67 - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - RET - -play_timing_test: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL ch0_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x05 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - CALL ch1_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - CALL ch2_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x00 - PUSH R0 - CALL ch3_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - CALL wave_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL setup_wave_ram - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL ch0_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL ch1_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL ch2_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wave_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - CALL ch3_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x03 - PUSH R0 - CALL ch0_sweep - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x01 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - LOADI R0, 0x04 - PUSH R0 - CALL ch1_sweep - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL play_timing_melody - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x78 - PUSH R0 - CALL ch0_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x78 - PUSH R0 - CALL ch1_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x78 - PUSH R0 - CALL ch2_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x78 - PUSH R0 - CALL wave_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x20 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xAB - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x8F - PUSH R0 - CALL ch1_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x1A - PUSH R0 - CALL ch2_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0x57 - PUSH R0 - CALL wave_note - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x78 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL all_off - POP R5 - POP R4 - POP R3 - POP R2 - RET - -play_explosion: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - CALL ch3_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x0F - PUSH R0 - LOADI R0, 0x08 - PUSH R0 - CALL ch3_noise - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x12 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch3_off - POP R5 - POP R4 - POP R3 - POP R2 - RET - -play_coin: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x03 - PUSH R0 - LOADI R0, 0x08 - PUSH R0 - LOADI R0, 0x02 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - CALL ch0_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x04 - PUSH R0 - CALL ch0_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xA6 - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x03 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL ch0_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x10 - PUSH R0 - LOADI R0, 0x0C - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xBD - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch0_off - POP R5 - POP R4 - POP R3 - POP R2 - RET - -play_jump: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x06 - PUSH R0 - LOADI R0, 0x04 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - CALL ch0_env - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x02 - PUSH R0 - LOADI R0, 0x01 - PUSH R0 - LOADI R0, 0x04 - PUSH R0 - CALL ch0_sweep - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0C - PUSH R0 - CALL ch0_len - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x00 - PUSH R0 - LOADI R0, 0x0A - PUSH R0 - LOADI R0, 0x07 - PUSH R0 - LOADI R0, 0xAB - PUSH R0 - CALL ch0_note - POP R1 - POP R1 - POP R1 - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x0C - PUSH R0 - CALL wait_frames - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL ch0_off - POP R5 - POP R4 - POP R3 - POP R2 - RET - -handle_input: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - MOVSPR R2:R3 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL read_keys_pressed - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R0 - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x04 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L22 - LOADI R0, 0 - JMP __L23 -__L22: - LOADI R0, 1 -__L23: - OR R0, R0 - JZ __L20 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL play_timing_test - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L21 -__L20: - LOADI R0, 0x00 -__L21: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x08 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L26 - LOADI R0, 0 - JMP __L27 -__L26: - LOADI R0, 1 -__L27: - OR R0, R0 - JZ __L24 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL play_explosion - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L25 -__L24: - LOADI R0, 0x00 -__L25: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x40 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L30 - LOADI R0, 0 - JMP __L31 -__L30: - LOADI R0, 1 -__L31: - OR R0, R0 - JZ __L28 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL play_coin - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L29 -__L28: - LOADI R0, 0x00 -__L29: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x80 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L34 - LOADI R0, 0 - JMP __L35 -__L34: - LOADI R0, 1 -__L35: - OR R0, R0 - JZ __L32 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL play_jump - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L33 -__L32: - LOADI R0, 0x00 -__L33: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x10 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L38 - LOADI R0, 0 - JMP __L39 -__L38: - LOADI R0, 1 -__L39: - OR R0, R0 - JZ __L36 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL toggle_ch0 - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L37 -__L36: - LOADI R0, 0x00 -__L37: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x20 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L42 - LOADI R0, 0 - JMP __L43 -__L42: - LOADI R0, 1 -__L43: - OR R0, R0 - JZ __L40 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL toggle_ch1 - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L41 -__L40: - LOADI R0, 0x00 -__L41: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x01 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L46 - LOADI R0, 0 - JMP __L47 -__L46: - LOADI R0, 1 -__L47: - OR R0, R0 - JZ __L44 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL toggle_ch2 - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L45 -__L44: - LOADI R0, 0x00 -__L45: - LOAD R0, [R2:R3 + 0] - PUSH R0 - LOADI R0, 0x02 - MOV R1, R0 - POP R0 - AND R0, R1 - PUSH R0 - LOADI R0, 0x00 - MOV R1, R0 - POP R0 - CMP R0, R1 - JNZ __L50 - LOADI R0, 0 - JMP __L51 -__L50: - LOADI R0, 1 -__L51: - OR R0, R0 - JZ __L48 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL toggle_ch3 - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L49 -__L48: - LOADI R0, 0x00 -__L49: - POP R1 - POP R3 - POP R2 - RET - -main: - MOVSPR R4:R5 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - LOADI R0, 0x08 - PUSH R0 - CALL apu_init - POP R1 - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL init_text_display - POP R5 - POP R4 - POP R3 - POP R2 -__L52: - LOADI R0, 0x01 - OR R0, R0 - JZ __L53 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL wait_frame - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL handle_input - POP R5 - POP R4 - POP R3 - POP R2 - PUSH R2 - PUSH R3 - PUSH R4 - PUSH R5 - CALL render_frame - POP R5 - POP R4 - POP R3 - POP R2 - JMP __L52 -__L53: - LOADI R0, 0 - RET - -section .data - - ORG 0x4000 -user_data: - DB 0x00 - DB 0x00 - DB 0x00 - - ORG 0x4003 -text_line1: - DB 0x41, 0x20, 0x54, 0x49, 0x4D, 0x49, 0x4E, 0x47, 0x20, 0x50 - DB 0x00 - - ORG 0x400E -text_line2: - DB 0x42, 0x20, 0x42, 0x4F, 0x4F, 0x4D - DB 0x00 - - ORG 0x4015 -text_line3: - DB 0x55, 0x50, 0x20, 0x43, 0x4F, 0x49, 0x4E - DB 0x00 - - ORG 0x401D -text_line4: - DB 0x44, 0x4E, 0x20, 0x4A, 0x55, 0x4D, 0x50 - DB 0x00 - - ORG 0x4025 -text_line5: - DB 0x53, 0x45, 0x4C, 0x20, 0x43, 0x48, 0x30 - DB 0x00 - - ORG 0x402D -text_line6: - DB 0x53, 0x54, 0x41, 0x20, 0x43, 0x48, 0x31 - DB 0x00 - - ORG 0x4035 -text_line7: - DB 0x4C, 0x54, 0x20, 0x54, 0x52, 0x49 - DB 0x00 - - ORG 0x403C -text_line8: - DB 0x52, 0x54, 0x20, 0x4E, 0x4F, 0x49, 0x53, 0x45 - DB 0x00 - -.include "../includes/tileset.inc" diff --git a/examples/assets/vivaldi-winter.mid b/examples/assets/vivaldi-winter.mid new file mode 100644 index 0000000..61003b0 Binary files /dev/null and b/examples/assets/vivaldi-winter.mid differ diff --git a/examples/se/apu_demo.se b/examples/se/apu_demo.se deleted file mode 100644 index 27822cd..0000000 --- a/examples/se/apu_demo.se +++ /dev/null @@ -1,511 +0,0 @@ -; ============================================================================= -; APU Demo - Demonstrates the tiny16 Audio Processing Unit -; ============================================================================= -; Controls: -; A: Timing + stress demo (press P to pause emulator) -; B: Play explosion/boom sound effect -; Up: Play coin/pickup sound effect -; Down: Play jump sound effect -; Select: Toggle pulse channel 0 (CH0) -; Start: Toggle pulse channel 1 (CH1) -; Left: Toggle triangle channel (TRI) -; Right: Toggle noise channel (NOISE) -; ============================================================================= -; -(def LAST_FRAME_ADDR 0x4000) -(def SPRITE_INDEX_ADDR 0x4002) - -(def GFX_TILES_BASE 0x5000) -(def GFX_OAM_BASE 0x9000) -(def OAM_HI (hi GFX_OAM_BASE)) -(def OAM_LO (lo GFX_OAM_BASE)) - -(def MMIO_BASE 0xBF00) -(def KEYS_PRESSED_ADDR (+ MMIO_BASE 0x01)) -(def FRAME_COUNT_ADDR (+ MMIO_BASE 0x22)) -(def PPU_CTRL_ADDR (+ MMIO_BASE 0x30)) - -(def APU_CTRL_ADDR (+ MMIO_BASE 0x40)) - -(def APU_CH0_FREQ_LO (+ MMIO_BASE 0x42)) -(def APU_CH0_FREQ_HI (+ MMIO_BASE 0x43)) -(def APU_CH0_VOL (+ MMIO_BASE 0x44)) -(def APU_CH0_CTRL (+ MMIO_BASE 0x45)) -(def APU_CH0_ENV_AD (+ MMIO_BASE 0x51)) -(def APU_CH0_ENV_SR (+ MMIO_BASE 0x52)) -(def APU_CH0_LEN (+ MMIO_BASE 0x5B)) -(def APU_CH0_SWEEP (+ MMIO_BASE 0x5F)) - -(def APU_CH1_FREQ_LO (+ MMIO_BASE 0x46)) -(def APU_CH1_FREQ_HI (+ MMIO_BASE 0x47)) -(def APU_CH1_VOL (+ MMIO_BASE 0x48)) -(def APU_CH1_CTRL (+ MMIO_BASE 0x49)) -(def APU_CH1_ENV_AD (+ MMIO_BASE 0x53)) -(def APU_CH1_ENV_SR (+ MMIO_BASE 0x54)) -(def APU_CH1_LEN (+ MMIO_BASE 0x5C)) -(def APU_CH1_SWEEP (+ MMIO_BASE 0x60)) - -(def APU_CH2_FREQ_LO (+ MMIO_BASE 0x4A)) -(def APU_CH2_FREQ_HI (+ MMIO_BASE 0x4B)) -(def APU_CH2_VOL (+ MMIO_BASE 0x4C)) -(def APU_CH2_CTRL (+ MMIO_BASE 0x4D)) -(def APU_CH2_ENV_AD (+ MMIO_BASE 0x55)) -(def APU_CH2_ENV_SR (+ MMIO_BASE 0x56)) -(def APU_CH2_LEN (+ MMIO_BASE 0x5D)) - -(def APU_CH3_PERIOD (+ MMIO_BASE 0x4E)) -(def APU_CH3_VOL (+ MMIO_BASE 0x4F)) -(def APU_CH3_CTRL (+ MMIO_BASE 0x50)) -(def APU_CH3_ENV_AD (+ MMIO_BASE 0x57)) -(def APU_CH3_ENV_SR (+ MMIO_BASE 0x58)) -(def APU_CH3_LEN (+ MMIO_BASE 0x5E)) - -(def APU_WAVE_FREQ_LO (+ MMIO_BASE 0x61)) -(def APU_WAVE_FREQ_HI (+ MMIO_BASE 0x62)) -(def APU_WAVE_VOL (+ MMIO_BASE 0x63)) -(def APU_WAVE_CTRL (+ MMIO_BASE 0x64)) -(def APU_WAVE_ENV_AD (+ MMIO_BASE 0x59)) -(def APU_WAVE_ENV_SR (+ MMIO_BASE 0x5A)) -(def APU_WAVE_LEN (+ MMIO_BASE 0x65)) -(def APU_WAVE_RAM (+ MMIO_BASE 0x70)) - -(def KEY_A 0x04) -(def KEY_B 0x08) -(def KEY_SELECT 0x10) -(def KEY_START 0x20) -(def KEY_UP 0x40) -(def KEY_DOWN 0x80) -(def KEY_LEFT 0x01) -(def KEY_RIGHT 0x02) - -; Duty cycles (for volume register bits 4-5) -(def APU_DUTY_12_5 0x00) -(def APU_DUTY_25 0x10) -(def APU_DUTY_50 0x20) - -; Note frequencies (11-bit values) -(def NOTE_C3 1818) -(def NOTE_D3 1835) -(def NOTE_E3 1851) -(def NOTE_G3 1885) -(def NOTE_A3 1900) -(def NOTE_C4 1879) ; Middle C -(def NOTE_D4 1895) -(def NOTE_E4 1914) -(def NOTE_F4 1924) -(def NOTE_G4 1935) -(def NOTE_A4 1948) -(def NOTE_B4 1958) -(def NOTE_C5 1963) -(def NOTE_D5 1974) -(def NOTE_E5 1981) -(def NOTE_G5 1994) -(def NOTE_A5 2004) - -(defn read-frame-count () - (peek16 FRAME_COUNT_ADDR)) - -(defn wait-frame () - (let (last (peek16 LAST_FRAME_ADDR) - current (read-frame-count)) - (while (= current last) - (set current (read-frame-count))) - (poke16 LAST_FRAME_ADDR current))) - -(defn wait-frames (n) - (let (i 0) - (while (< i n) - (wait-frame) - (set i (inc i))))) - -(defn render-frame () - (poke16 PPU_CTRL_ADDR 0x82)) - -(defn read-keys-pressed () - (peek16 KEYS_PRESSED_ADDR)) - -; ============================================================================= -; APU Functions -; ============================================================================= - -; Initialize APU with master volume -(defn apu-init (volume) - (poke16 APU_CTRL_ADDR (| 0x01 (<< volume 4)))) - -; --- Channel 0 Functions --- -(defn ch0-note (freq-lo freq-hi vol duty) - (poke16 APU_CH0_FREQ_LO freq-lo) - (poke16 APU_CH0_FREQ_HI freq-hi) - (poke16 APU_CH0_VOL (| vol duty)) - (poke16 APU_CH0_CTRL 0x03)) - -(defn ch0-env (attack decay sustain release) - (poke16 APU_CH0_ENV_AD (| (<< attack 4) decay)) - (poke16 APU_CH0_ENV_SR (| (<< sustain 4) release))) - -(defn ch0-len (len) - (poke16 APU_CH0_LEN len)) - -(defn ch0-sweep (time direction shift) - (poke16 APU_CH0_SWEEP (| (| (<< time 4) (<< direction 3)) shift))) - -(defn ch0-off () - (poke16 APU_CH0_CTRL 0)) - -; --- Channel 1 Functions --- -(defn ch1-note (freq-lo freq-hi vol duty) - (poke16 APU_CH1_FREQ_LO freq-lo) - (poke16 APU_CH1_FREQ_HI freq-hi) - (poke16 APU_CH1_VOL (| vol duty)) - (poke16 APU_CH1_CTRL 0x03)) - -(defn ch1-env (attack decay sustain release) - (poke16 APU_CH1_ENV_AD (| (<< attack 4) decay)) - (poke16 APU_CH1_ENV_SR (| (<< sustain 4) release))) - -(defn ch1-len (len) - (poke16 APU_CH1_LEN len)) - -(defn ch1-sweep (time direction shift) - (poke16 APU_CH1_SWEEP (| (| (<< time 4) (<< direction 3)) shift))) - -(defn ch1-off () - (poke16 APU_CH1_CTRL 0)) - -; --- Channel 2 (Triangle) Functions --- -(defn ch2-note (freq-lo freq-hi vol) - (poke16 APU_CH2_FREQ_LO freq-lo) - (poke16 APU_CH2_FREQ_HI freq-hi) - (poke16 APU_CH2_VOL vol) - (poke16 APU_CH2_CTRL 0x03)) - -(defn ch2-env (attack decay sustain release) - (poke16 APU_CH2_ENV_AD (| (<< attack 4) decay)) - (poke16 APU_CH2_ENV_SR (| (<< sustain 4) release))) - -(defn ch2-len (len) - (poke16 APU_CH2_LEN len)) - -(defn ch2-off () - (poke16 APU_CH2_CTRL 0)) - -; --- Channel 3 (Noise) Functions --- -(defn ch3-noise (period vol mode) - (poke16 APU_CH3_PERIOD (| period (<< mode 4))) - (poke16 APU_CH3_VOL vol) - (poke16 APU_CH3_CTRL 0x03)) - -(defn ch3-env (attack decay sustain release) - (poke16 APU_CH3_ENV_AD (| (<< attack 4) decay)) - (poke16 APU_CH3_ENV_SR (| (<< sustain 4) release))) - -(defn ch3-len (len) - (poke16 APU_CH3_LEN len)) - -(defn ch3-off () - (poke16 APU_CH3_CTRL 0)) - -; --- Wave Channel Functions --- -(defn wave-note (freq-lo freq-hi vol) - (poke16 APU_WAVE_FREQ_LO freq-lo) - (poke16 APU_WAVE_FREQ_HI freq-hi) - (poke16 APU_WAVE_VOL vol) - (poke16 APU_WAVE_CTRL 0x03)) - -(defn wave-env (attack decay sustain release) - (poke16 APU_WAVE_ENV_AD (| (<< attack 4) decay)) - (poke16 APU_WAVE_ENV_SR (| (<< sustain 4) release))) - -(defn wave-len (len) - (poke16 APU_WAVE_LEN len)) - -(defn wave-write (index value) - (let (addr-lo (+ (lo APU_WAVE_RAM) index) - addr-hi (hi APU_WAVE_RAM)) - (store (addr addr-hi addr-lo) value))) - -(defn wave-off () - (poke16 APU_WAVE_CTRL 0)) - -; --- All channels off --- -(defn all-off () - (ch0-off) - (ch1-off) - (ch2-off) - (ch3-off) - (wave-off)) - -; --- Toggle channel functions --- -(defn toggle-ch0 () - (let (val (peek16 APU_CH0_CTRL)) - (poke16 APU_CH0_CTRL (^ val 0x01)))) - -(defn toggle-ch1 () - (let (val (peek16 APU_CH1_CTRL)) - (poke16 APU_CH1_CTRL (^ val 0x01)))) - -(defn toggle-ch2 () - (let (val (peek16 APU_CH2_CTRL)) - (poke16 APU_CH2_CTRL (^ val 0x01)))) - -(defn toggle-ch3 () - (let (val (peek16 APU_CH3_CTRL)) - (poke16 APU_CH3_CTRL (^ val 0x01)))) - -; Hide all 64 OAM sprites by setting Y=0xFF -(defn hide-all-sprites () - (let (i 0) - (while (< i 64) - (let (lo (+ OAM_LO (<< i 2))) - (poke OAM_HI lo 0xFF) - (poke OAM_HI (+ lo 1) 0) - (poke OAM_HI (+ lo 2) 0) - (poke OAM_HI (+ lo 3) 0)) - (set i (inc i))))) - -; Draw a single character sprite -; x, y: position in pixels -; char-code: ASCII character value -; sprite-idx: OAM sprite slot (0-63) -(defn draw-char (x y char-code sprite-idx) - (let (tile (+ 96 char-code) - lo (+ OAM_LO (<< sprite-idx 2))) - (poke OAM_HI lo y) - (poke OAM_HI (+ lo 1) x) - (poke OAM_HI (+ lo 2) tile) - (poke OAM_HI (+ lo 3) 0))) - -; Draw null-terminated text string -; x, y: starting position in pixels -; str-hi, str-lo: 16-bit address of null-terminated string (separate bytes) -; Returns: number of characters drawn -(defn draw-text (x y str-hi str-lo) - (let (sprite-idx (peek (hi SPRITE_INDEX_ADDR) (lo SPRITE_INDEX_ADDR)) - char-x x - current-hi str-hi - current-lo str-lo) - (while (!= (peek current-hi current-lo) 0) - (let (ch (peek current-hi current-lo)) - (draw-char char-x y ch sprite-idx) - (set char-x (+ char-x 6)) - (set sprite-idx (inc sprite-idx)) - (set current-lo (inc current-lo)) - (if (= current-lo 0) - (set current-hi (inc current-hi)) - 0))) - (poke (hi SPRITE_INDEX_ADDR) (lo SPRITE_INDEX_ADDR) sprite-idx))) - -(defn init-text-display () - (hide-all-sprites) - (poke (hi SPRITE_INDEX_ADDR) (lo SPRITE_INDEX_ADDR) 0) - - (draw-text 2 2 (hi text_line1) (lo text_line1)) - (draw-text 2 10 (hi text_line2) (lo text_line2)) - (draw-text 2 18 (hi text_line3) (lo text_line3)) - (draw-text 2 26 (hi text_line4) (lo text_line4)) - (draw-text 2 38 (hi text_line5) (lo text_line5)) - (draw-text 2 46 (hi text_line6) (lo text_line6)) - (draw-text 2 54 (hi text_line7) (lo text_line7)) - (draw-text 2 62 (hi text_line8) (lo text_line8)) - - (render-frame)) - -; ============================================================================= -; Sound Effects -; ============================================================================= - -; Helper: Setup wave RAM for timing test -(defn setup-wave-ram () - (wave-write 0 0) - (wave-write 1 2) - (wave-write 2 4) - (wave-write 3 6) - (wave-write 4 8) - (wave-write 5 10) - (wave-write 6 12) - (wave-write 7 14) - (wave-write 8 15) - (wave-write 9 14) - (wave-write 10 12) - (wave-write 11 10) - (wave-write 12 8) - (wave-write 13 6) - (wave-write 14 4) - (wave-write 15 2) - (wave-write 16 1) - (wave-write 17 3) - (wave-write 18 5) - (wave-write 19 7) - (wave-write 20 9) - (wave-write 21 11) - (wave-write 22 13) - (wave-write 23 15) - (wave-write 24 13) - (wave-write 25 11) - (wave-write 26 9) - (wave-write 27 7) - (wave-write 28 5) - (wave-write 29 3) - (wave-write 30 1) - (wave-write 31 0)) - -; Helper: Play timing test melody sequence -(defn play-timing-melody () - ; Step 1 - (ch0-note (lo NOTE_C5) (hi NOTE_C5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_E4) (hi NOTE_E4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_C3) (hi NOTE_C3) 6) - (wave-note (lo NOTE_C4) (hi NOTE_C4) 6) - (wait-frames 6) - - ; Step 2 (hat) - (ch3-len 2) - (ch3-noise 6 7 0x04) - (ch0-note (lo NOTE_D5) (hi NOTE_D5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_F4) (hi NOTE_F4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_D3) (hi NOTE_D3) 6) - (wave-note (lo NOTE_D4) (hi NOTE_D4) 6) - (wait-frames 6) - - ; Step 3 - (ch0-note (lo NOTE_E5) (hi NOTE_E5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_G4) (hi NOTE_G4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_E3) (hi NOTE_E3) 6) - (wave-note (lo NOTE_E4) (hi NOTE_E4) 6) - (wait-frames 6) - - ; Step 4 (hat) - (ch3-len 2) - (ch3-noise 6 7 0x04) - (ch0-note (lo NOTE_G5) (hi NOTE_G5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_B4) (hi NOTE_B4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_G3) (hi NOTE_G3) 6) - (wave-note (lo NOTE_G4) (hi NOTE_G4) 6) - (wait-frames 6) - - ; Step 5 - (ch0-note (lo NOTE_A5) (hi NOTE_A5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_C5) (hi NOTE_C5) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_A3) (hi NOTE_A3) 6) - (wave-note (lo NOTE_A4) (hi NOTE_A4) 6) - (wait-frames 6) - - ; Step 6 (hat) - (ch3-len 2) - (ch3-noise 6 7 0x04) - (ch0-note (lo NOTE_G5) (hi NOTE_G5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_B4) (hi NOTE_B4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_G3) (hi NOTE_G3) 6) - (wave-note (lo NOTE_G4) (hi NOTE_G4) 6) - (wait-frames 6) - - ; Step 7 - (ch0-note (lo NOTE_E5) (hi NOTE_E5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_G4) (hi NOTE_G4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_E3) (hi NOTE_E3) 6) - (wave-note (lo NOTE_E4) (hi NOTE_E4) 6) - (wait-frames 6) - - ; Step 8 (hat) - (ch3-len 2) - (ch3-noise 6 7 0x04) - (ch0-note (lo NOTE_D5) (hi NOTE_D5) 12 APU_DUTY_25) - (ch1-note (lo NOTE_F4) (hi NOTE_F4) 9 APU_DUTY_12_5) - (ch2-note (lo NOTE_D3) (hi NOTE_D3) 6) - (wave-note (lo NOTE_D4) (hi NOTE_D4) 6) - (wait-frames 6)) - -; Timing + stress demo - Uses all channels with sweeps and envelopes -(defn play-timing-test () - ; Setup envelopes for layered voices - (ch0-env 2 3 10 6) - (ch1-env 1 3 12 5) - (ch2-env 1 4 12 6) - (ch3-env 0 3 0 4) - (wave-env 2 4 10 6) - - ; Setup wave RAM with asymmetric organ-ish shape - (setup-wave-ram) - - ; Length and sweep settings - (ch0-len 6) - (ch1-len 6) - (ch2-len 6) - (wave-len 6) - (ch3-len 2) - (ch0-sweep 3 0 2) - (ch1-sweep 4 1 1) - - ; Play melody sequence - (play-timing-melody) - - ; Long hold (press P to pause emulator and compare timing) - (ch0-len 120) - (ch1-len 120) - (ch2-len 120) - (wave-len 120) - (ch0-note (lo NOTE_C5) (hi NOTE_C5) 12 APU_DUTY_50) - (ch1-note (lo NOTE_G4) (hi NOTE_G4) 10 APU_DUTY_25) - (ch2-note (lo NOTE_C3) (hi NOTE_C3) 6) - (wave-note (lo NOTE_C4) (hi NOTE_C4) 6) - (wait-frames 120) - - (all-off)) - -(defn play-explosion () - (ch3-env 1 6 0 6) - (ch3-noise 8 15 0) - (wait-frames 18) - (ch3-off)) - -(defn play-coin () - (ch0-env 1 2 8 3) - (ch0-len 4) - (ch0-note (lo NOTE_B4) (hi NOTE_B4) 12 APU_DUTY_25) - (wait-frames 3) - (ch0-len 6) - (ch0-note (lo NOTE_E5) (hi NOTE_E5) 12 APU_DUTY_25) - (wait-frames 6) - (ch0-off)) - -(defn play-jump () - (ch0-env 1 4 6 6) - (ch0-sweep 4 1 2) - (ch0-len 12) - (ch0-note (lo NOTE_C5) (hi NOTE_C5) 10 APU_DUTY_12_5) - (wait-frames 12) - (ch0-off)) - -(defn handle-input () - (let (keys (read-keys-pressed)) - (if (!= (& keys KEY_A) 0) (play-timing-test) 0) - (if (!= (& keys KEY_B) 0) (play-explosion) 0) - (if (!= (& keys KEY_UP) 0) (play-coin) 0) - (if (!= (& keys KEY_DOWN) 0) (play-jump) 0) - (if (!= (& keys KEY_SELECT) 0) (toggle-ch0) 0) - (if (!= (& keys KEY_START) 0) (toggle-ch1) 0) - (if (!= (& keys KEY_LEFT) 0) (toggle-ch2) 0) - (if (!= (& keys KEY_RIGHT) 0) (toggle-ch3) 0))) - -(defn main () - (apu-init 8) ; volume 8 - (init-text-display) - (while 1 - (wait-frame) - (handle-input) - (render-frame))) - -(data user_data 0x4000 - (db 0 0) ; last_frame (16-bit) - at 0x4000-0x4001 - (db 0)) ; sprite_index (8-bit) - at 0x4002 - -(data text_line1 (db "A TIMING P" 0)) -(data text_line2 (db "B BOOM" 0)) -(data text_line3 (db "UP COIN" 0)) -(data text_line4 (db "DN JUMP" 0)) -(data text_line5 (db "SEL CH0" 0)) -(data text_line6 (db "STA CH1" 0)) -(data text_line7 (db "LT TRI" 0)) -(data text_line8 (db "RT NOISE" 0)) - -(import "../examples/includes/tileset.inc") diff --git a/examples/se/apu_sfx_demo.se b/examples/se/apu_sfx_demo.se new file mode 100644 index 0000000..d50c604 --- /dev/null +++ b/examples/se/apu_sfx_demo.se @@ -0,0 +1,208 @@ +; APU SFX Bank & Music Demo +; +; Demonstrates the new data-driven audio system: +; - SFX bank: define sounds once, play by ID +; - Music sequencer: background music with automatic playback +; +; Controls: +; - A: Play jump sound +; - B: Play coin sound +; - UP: Play explosion +; - DOWN: Play hit sound +; - START: Toggle background music +; +; BUILD: +; make examples-se +; ./build/tiny16-emu build/apu_sfx_demo_se.tiny16 + +(require (core sprite tilemap input apu ppu)) + +(import "../examples/includes/tileset.inc") + +; ============================================================================= +; Constants +; ============================================================================= + +(def KEY_A 0x04) +(def KEY_B 0x08) +(def KEY_UP 0x40) +(def KEY_DOWN 0x80) +(def KEY_START 0x02) + +; SFX IDs +(def SFX_JUMP 0) +(def SFX_COIN 1) +(def SFX_HIT 2) +(def SFX_EXPLOSION 3) +(def SFX_COUNT 4) + +; Song length (number of notes) +(def SONG_LENGTH 16) + +; Display +(def MSG_ROW 5) +(def MSG_COL 2) + +; ============================================================================= +; SFX Bank Definition +; ============================================================================= +; Format: channel, freq_lo, freq_hi, period, volume, duty, env_ad, env_sr, duration + +(data sfx-table + ; ID 0: Jump (pulse 0, high pitch, short) + (db 0 0xC8 0x07 0 18 2 0x11 0x81 3) + ; ID 1: Coin (pulse 0, bright, medium) + (db 0 0xD0 0x07 0 44 2 0x11 0x61 4) + ; ID 2: Hit (noise, harsh, quick) + (db 3 0 0 9 14 0 0x15 0x04 18) + ; ID 3: Explosion (noise, long decay) + (db 3 0 0 12 15 0 0x28 0x26 30)) + +; ============================================================================= +; Music Sequence Definition +; ============================================================================= +; Format: note, volume, duration, reserved +; A simple melody: C-E-G-C pattern + +(data demo-song + ; Bar 1 + (db 13 4 8 0) ; C4 + (db 17 4 8 0) ; E4 + (db 20 4 8 0) ; G4 + (db 25 4 16 0) ; C5 (longer) + ; Bar 2 + (db 20 4 8 0) ; G4 + (db 17 4 8 0) ; E4 + (db 13 4 16 0) ; C4 (longer) + (db 0 0 8 0) ; rest + ; Bar 3 (variation) + (db 15 4 8 0) ; D4 + (db 18 4 8 0) ; F4 + (db 22 4 8 0) ; A4 + (db 27 4 16 0) ; D5 (longer) + ; Bar 4 + (db 22 2 8 0) ; A4 + (db 18 2 8 0) ; F4 + (db 15 2 16 0) ; D4 (longer) + (db 0 0 8 0)) ; rest + +; ============================================================================= +; State +; ============================================================================= + +(data music-on (db 0)) +(data initialized (db 0)) + +; ============================================================================= +; Display Functions +; ============================================================================= + +(defn clear-row (row) + (let (x 0) + (while (< x 16) + (tilemap/put x row 0) + (set x (inc x))))) + +(defn show-title () + (tilemap/text 1 2 (hi str-title) (lo str-title)) + (tilemap/text 1 3 (hi str-subtitle) (lo str-subtitle))) + +(defn show-controls () + (tilemap/text 1 6 (hi str-a) (lo str-a)) + (tilemap/text 1 7 (hi str-b) (lo str-b)) + (tilemap/text 1 8 (hi str-up) (lo str-up)) + (tilemap/text 1 9 (hi str-down) (lo str-down)) + (tilemap/text 1 10 (hi str-start) (lo str-start))) + +(defn show-music-status () + (clear-row 12) + (if (= (peek16 music-on) 1) + (tilemap/text 1 12 (hi str-music-on) (lo str-music-on)) + (tilemap/text 1 12 (hi str-music-off) (lo str-music-off)))) + +; ============================================================================= +; Initialization +; ============================================================================= + +(defn init-display () + (tilemap/fill-row 0 2) + (tilemap/fill-row 1 1) + (tilemap/fill-row 14 81) + (tilemap/fill-row 15 97) + (show-title) + (show-controls) + (show-music-status)) + +(defn init-audio () + (apu/init) + ; Initialize SFX bank + (apu/sfx-init (hi sfx-table) (lo sfx-table) SFX_COUNT)) + +; ============================================================================= +; Input Handling +; ============================================================================= + +(defn handle-input () + (let (pressed (input/keys-pressed)) + ; A button - jump sound + (if (!= (& pressed KEY_A) 0) + (apu/sfx-play SFX_JUMP) + 0) + ; B button - coin sound + (if (!= (& pressed KEY_B) 0) + (apu/sfx-play SFX_COIN) + 0) + ; UP - explosion + (if (!= (& pressed KEY_UP) 0) + (apu/sfx-play SFX_EXPLOSION) + 0) + ; DOWN - hit + (if (!= (& pressed KEY_DOWN) 0) + (apu/sfx-play SFX_HIT) + 0) + ; START - toggle music + (if (!= (& pressed KEY_START) 0) + (if (= (peek16 music-on) 0) + (do + (poke16 music-on 1) + (apu/music-play (hi demo-song) (lo demo-song) 0 SONG_LENGTH 1) + (show-music-status)) + (do + (poke16 music-on 0) + (apu/music-stop) + (show-music-status))) + 0))) + +; ============================================================================= +; Main +; ============================================================================= + +(defn main () + ; One-time initialization + (if (!= (peek16 initialized) 0xAA) + (do + (sprite/hide-all) + (init-audio) + (init-display) + (poke16 initialized 0xAA)) + 0) + + ; Main loop + (while 1 + (core/wait-frame) + (handle-input) + (ppu/render-all))) + +; ============================================================================= +; Strings +; ============================================================================= + +(data str-title (db "SFX BANK DEMO" 0)) +(data str-subtitle (db "& MUSIC SEQ" 0)) +(data str-a (db "A: JUMP" 0)) +(data str-b (db "B: COIN" 0)) +(data str-up (db "UP: EXPLOSION" 0)) +(data str-down (db "DOWN: HIT" 0)) +(data str-start (db "START: MUSIC" 0)) +(data str-music-on (db "MUSIC: ON" 0)) +(data str-music-off (db "MUSIC: OFF" 0)) diff --git a/examples/se/floppy.se b/examples/se/floppy.se index 013e375..9cce46d 100644 --- a/examples/se/floppy.se +++ b/examples/se/floppy.se @@ -17,7 +17,7 @@ ; make examples-se ; ./build/tiny16-emu build/floppy_se.tiny16 -(require (core sprite tilemap input apu ppu math)) +(require (core sprite tilemap input apu ppu math vivaldi-winter)) (import "../examples/includes/tileset.inc") @@ -28,6 +28,12 @@ (def KEY_A 0x04) (def SPRITE_BEHIND_BG 0x20) +; SFX IDs (using new SFX bank system) +(def SFX_FLAP 0) +(def SFX_DIE 1) +(def SFX_SCORE 2) +(def SFX_COUNT 3) + ; Game states (def STATE_WAITING 0) (def STATE_PLAYING 1) @@ -147,20 +153,17 @@ (tilemap/text (+ TEXT_COL 1) TEXT_ROW2 (hi str_press_a) (lo str_press_a))) ; ============================================================================= -; Sound Effects +; Sound Effects (using SFX bank - play by ID) ; ============================================================================= (defn sfx-flap () - (apu/ch0-set-envelope 1 1 8 1) - (apu/ch0-play-note 0xC8 0x07 0x12 3)) + (apu/sfx-play SFX_FLAP)) (defn sfx-die () - (apu/ch3-set-envelope 1 5 0 4) - (apu/ch3-play-noise 9 14 18)) + (apu/sfx-play SFX_DIE)) (defn sfx-score () - (apu/ch0-set-envelope 1 1 6 1) - (apu/ch0-play-note 0xD0 0x07 0x2C 4)) + (apu/sfx-play SFX_SCORE)) ; ============================================================================= ; Fixed-Point Physics @@ -235,12 +238,14 @@ (defn die () (poke16 game_state STATE_DEAD) (poke16 dead_timer 0) + (apu/music-stop) (show-game-over) (sfx-die)) (defn start-game () (clear-text) - (poke16 game_state STATE_PLAYING)) + (poke16 game_state STATE_PLAYING) + (apu/music-play (hi vivaldi-winter/data) (lo vivaldi-winter/data) vivaldi-winter/length-hi vivaldi-winter/length-lo 1)) (defn update-bird (keys) (if (!= (& keys KEY_A) 0) @@ -410,6 +415,7 @@ (if (!= (peek16 initialized) INIT_FLAG) (do (apu/init) + (apu/sfx-init (hi sfx_table) (lo sfx_table) SFX_COUNT) (init-tilemap) (init-game) (poke16 initialized INIT_FLAG)) @@ -470,3 +476,16 @@ (data str_press_a (db "PRESS " (- TILE_BUTTON_A 96) 0)) (data str_game_over (db "GAME OVER" 0)) + +; ============================================================================= +; SFX Bank Table +; ============================================================================= +; Format: channel, freq_lo, freq_hi, period, volume, duty, env_ad, env_sr, duration + +(data sfx_table + ; ID 0: Flap (pulse 0, high pitch chirp) + (db 0 0xC8 0x07 0 18 2 0x11 0x81 3) + ; ID 1: Die (noise, harsh) + (db 3 0 0 9 14 0 0x15 0x04 18) + ; ID 2: Score (pulse 0, bright coin sound) + (db 0 0xD0 0x07 0 44 2 0x11 0x61 4)) diff --git a/examples/se/music_demo.se b/examples/se/music_demo.se deleted file mode 100644 index a373c2e..0000000 --- a/examples/se/music_demo.se +++ /dev/null @@ -1,60 +0,0 @@ -; Music Demo - Demonstrates music module -; -; Controls: -; A: Play C major scale -; B: Play beep -; Up: Play success sound -; Down: Play error sound -; -; BUILD: -; make examples-se -; ./build/tiny16-emu build/music_demo_se.tiny16 - -(require (core input sprite ppu apu tilemap music)) -(import "../examples/includes/tileset.inc") - -(defn play-scale () - (music/note music/NOTE-C4 15 12) - (music/wait 15) - (music/note music/NOTE-D4 15 12) - (music/wait 15) - (music/note music/NOTE-E4 15 12) - (music/wait 15) - (music/note music/NOTE-F4 15 12) - (music/wait 15) - (music/note music/NOTE-G4 15 12) - (music/wait 15) - (music/note music/NOTE-A4 15 12) - (music/wait 15) - (music/note music/NOTE-B4 15 12) - (music/wait 15) - (music/note music/NOTE-C5 30 15) - (music/wait 30)) - -(defn main () - (apu/init) - (sprite/hide-all) - (tilemap/fill-row 0 2) - (tilemap/text 3 7 (hi str_title) (lo str_title)) - (tilemap/text 2 9 (hi str_help) (lo str_help)) - (ppu/render-all) - - (while 1 - (core/wait-frame) - - (let (pressed (input/keys-pressed)) - (if (!= (& pressed input/KEY_A) 0) - (play-scale) - 0) - (if (!= (& pressed input/KEY_B) 0) - (music/beep music/NOTE-G4) - 0) - (if (!= (& pressed input/KEY_UP) 0) - (music/success) - 0) - (if (!= (& pressed input/KEY_DOWN) 0) - (music/error-sound) - 0)))) - -(data str_title (db "MUSIC DEMO" 0)) -(data str_help (db "A B UP DOWN" 0)) diff --git a/misc/vscode/README.md b/misc/vscode/README.md new file mode 100644 index 0000000..78da4a6 --- /dev/null +++ b/misc/vscode/README.md @@ -0,0 +1,156 @@ +# tiny16 VSCode/Cursor Extension + +Syntax highlighting for tiny16 assembly (`.asm`) and tiny16 SE (`.se`) files in VSCode and Cursor. + +## Features + +- **Syntax Highlighting**: Full syntax support for both `.asm` and `.se` files +- **Auto-completion**: Bracket and quote pairing +- **Comment Toggle**: Use `Cmd+/` (Mac) or `Ctrl+/` (Windows/Linux) to toggle comments +- **Code Folding**: Fold `.macro` / `.endmacro` blocks in assembly files + +## Installation + +### Method 1: Install as Local Extension (Recommended) + +1. Copy the extension to your VSCode/Cursor extensions folder: + +```bash +# For VSCode +mkdir -p ~/.vscode/extensions/tiny16-syntax +cp -r misc/vscode/* ~/.vscode/extensions/tiny16-syntax/ + +# For Cursor +mkdir -p ~/.cursor/extensions/tiny16-syntax +cp -r misc/vscode/* ~/.cursor/extensions/tiny16-syntax/ +``` + +2. Restart VSCode/Cursor +3. Open any `.asm` or `.se` file to see syntax highlighting + +### Method 2: Use from Workspace (Development) + +For testing or development, you can load the extension directly: + +1. Press `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux) +2. Type "Developer: Install Extension from Location" +3. Navigate to `/path/to/tiny16/misc/vscode` +4. Select the folder + +### Method 3: Symlink (Auto-update) + +Create a symlink so changes are reflected automatically: + +```bash +# For VSCode +ln -s "$(pwd)/misc/vscode" ~/.vscode/extensions/tiny16-syntax + +# For Cursor +ln -s "$(pwd)/misc/vscode" ~/.cursor/extensions/tiny16-syntax +``` + +## Syntax Highlighting + +### Assembly (`.asm` files) + +- **Instructions**: Data movement, arithmetic, bitwise, comparison, control flow, stack operations +- **Registers**: `R0`-`R7`, `SP`, `PC`, `FP`, register pairs (`R6:R7`) +- **Directives**: `.macro`, `.endmacro`, `.include`, `section`, `ORG`, `TIMES`, `DB` +- **Sections**: `.code`, `.data` +- **Labels**: Function/jump labels with `:` suffix +- **Constants**: Uppercase names with `=` assignment +- **Numbers**: Decimal, hexadecimal (`0x`), binary (`0b`) +- **Strings**: With escape sequences (`\n`, `\r`, `\t`, etc.) +- **Macros**: Common stdlib macros (SETADDR, LOAD16, PUSH2, etc.) +- **Comments**: Lines starting with `;` + +### SE Language (`.se` files) + +- **Special Forms**: `def`, `defn`, `let`, `set`, `if`, `while`, `do`, `data`, `db`, `repeat`, `include` +- **Builtins**: Arithmetic (`add`, `sub`, `inc`, `dec`), bitwise (`and`, `or`, `xor`, `not`, `shl`, `shr`), comparison (`eq`, `ne`, `lt`, `gt`, `le`, `ge`) +- **Operators**: `+`, `-`, `*`, `&`, `|`, `^`, `~`, `<<`, `>>`, `=`, `!=`, `<`, `>`, `<=`, `>=`, `!` +- **Memory Operations**: `load`, `store`, `addr`, `peek`, `poke`, `peek16`, `poke16` +- **Function Definitions**: Highlighted function names after `defn` +- **Constants**: Highlighted constant names after `def` +- **Data Labels**: Highlighted data labels after `data` +- **Booleans**: `true`, `false` +- **Comments**: Lines starting with `;` + +## Customization + +You can customize colors in your VSCode/Cursor `settings.json`: + +```json +{ + "editor.tokenColorCustomizations": { + "textMateRules": [ + { + "scope": "keyword.control.tiny16asm", + "settings": { + "foreground": "#569cd6", + "fontStyle": "bold" + } + }, + { + "scope": "variable.other.register.tiny16asm", + "settings": { + "foreground": "#9cdcfe" + } + }, + { + "scope": "entity.name.function.tiny16se", + "settings": { + "foreground": "#dcdcaa", + "fontStyle": "bold" + } + } + ] + } +} +``` + +## Language Configuration + +Both languages support: + +- Line comments with `;` +- Auto-closing brackets and quotes +- Bracket matching and highlighting + +## File Association + +The extension automatically applies syntax highlighting to: + +- `.asm` files → tiny16 Assembly +- `.se` files → tiny16 SE + +To manually set the language for a file: + +1. Click the language indicator in the bottom-right corner of VSCode/Cursor +2. Select "tiny16 Assembly" or "tiny16 SE" from the list + +Or add a comment at the top of your file: + +``` +; Language: tiny16asm +``` + +## Troubleshooting + +### Syntax highlighting not working + +1. Make sure you've restarted VSCode/Cursor after installation +2. Check that the file extension is `.asm` or `.se` +3. Manually select the language from the status bar +4. Run "Developer: Reload Window" from the command palette + +### Colors don't match my theme + +The extension uses semantic token scopes that work with most themes. If colors don't look right: + +1. Try a different theme +2. Customize colors using `editor.tokenColorCustomizations` (see Customization section) + +## Related + +See also the Neovim/Vim syntax files in `misc/nvim/` for terminal-based editing. diff --git a/misc/vscode/language-configuration-asm.json b/misc/vscode/language-configuration-asm.json new file mode 100644 index 0000000..2fabbac --- /dev/null +++ b/misc/vscode/language-configuration-asm.json @@ -0,0 +1,25 @@ +{ + "comments": { + "lineComment": ";" + }, + "brackets": [ + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "\"", "close": "\"", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["[", "]"], + ["(", ")"], + ["\"", "\""] + ], + "folding": { + "markers": { + "start": "^\\s*\\.macro\\b", + "end": "^\\s*\\.endmacro\\b" + } + } +} diff --git a/misc/vscode/language-configuration-se.json b/misc/vscode/language-configuration-se.json new file mode 100644 index 0000000..d4f2808 --- /dev/null +++ b/misc/vscode/language-configuration-se.json @@ -0,0 +1,14 @@ +{ + "comments": { + "lineComment": ";" + }, + "brackets": [["(", ")"]], + "autoClosingPairs": [ + { "open": "(", "close": ")" }, + { "open": "\"", "close": "\"", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["(", ")"], + ["\"", "\""] + ] +} diff --git a/misc/vscode/package.json b/misc/vscode/package.json new file mode 100644 index 0000000..6d9a637 --- /dev/null +++ b/misc/vscode/package.json @@ -0,0 +1,51 @@ +{ + "name": "tiny16-syntax", + "displayName": "tiny16 Syntax Highlighting", + "description": "Syntax highlighting for tiny16 assembly (.asm) and tiny16 SE (.se) files", + "version": "1.0.0", + "publisher": "tiny16", + "engines": { + "vscode": "^1.60.0" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "languages": [ + { + "id": "tiny16asm", + "aliases": [ + "tiny16 Assembly", + "tiny16asm" + ], + "extensions": [ + ".asm" + ], + "configuration": "./language-configuration-asm.json" + }, + { + "id": "tiny16se", + "aliases": [ + "tiny16 SE", + "tiny16se" + ], + "extensions": [ + ".se" + ], + "configuration": "./language-configuration-se.json" + } + ], + "grammars": [ + { + "language": "tiny16asm", + "scopeName": "source.tiny16asm", + "path": "./syntaxes/tiny16asm.tmLanguage.json" + }, + { + "language": "tiny16se", + "scopeName": "source.tiny16se", + "path": "./syntaxes/tiny16se.tmLanguage.json" + } + ] + } +} diff --git a/misc/vscode/syntaxes/tiny16asm.tmLanguage.json b/misc/vscode/syntaxes/tiny16asm.tmLanguage.json new file mode 100644 index 0000000..d1be57b --- /dev/null +++ b/misc/vscode/syntaxes/tiny16asm.tmLanguage.json @@ -0,0 +1,155 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "tiny16 Assembly", + "scopeName": "source.tiny16asm", + "patterns": [ + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#numbers" }, + { "include": "#labels" }, + { "include": "#constants" }, + { "include": "#registers" }, + { "include": "#instructions" }, + { "include": "#directives" }, + { "include": "#sections" }, + { "include": "#macros" }, + { "include": "#operators" } + ], + "repository": { + "comments": { + "patterns": [ + { + "name": "comment.line.semicolon.tiny16asm", + "match": ";.*$", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.codetag.notation.tiny16asm", + "match": "\\b(TODO|FIXME|XXX|NOTE|HACK|BUG)\\b" + } + ] + } + } + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.double.tiny16asm", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.tiny16asm", + "match": "\\\\[nrt\\\\\"0]" + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "name": "constant.numeric.hex.tiny16asm", + "match": "\\b0x[0-9a-fA-F]+\\b" + }, + { + "name": "constant.numeric.binary.tiny16asm", + "match": "\\b0b[01]+\\b" + }, + { + "name": "constant.numeric.decimal.tiny16asm", + "match": "\\b\\d+\\b" + } + ] + }, + "registers": { + "patterns": [ + { + "name": "variable.other.register-pair.tiny16asm", + "match": "\\bR[0-7]:R[0-7]\\b" + }, + { + "name": "variable.other.register.tiny16asm", + "match": "\\b(R[0-7]|SP|PC|FP)\\b" + } + ] + }, + "labels": { + "patterns": [ + { + "name": "entity.name.function.tiny16asm", + "match": "^\\s*(@?[a-zA-Z_][a-zA-Z0-9_]*):", + "captures": { + "1": { "name": "entity.name.function.tiny16asm" } + } + } + ] + }, + "constants": { + "patterns": [ + { + "name": "constant.other.tiny16asm", + "match": "^\\s*([A-Z_][A-Z0-9_]*)\\s*=", + "captures": { + "1": { "name": "constant.other.tiny16asm" } + } + }, + { + "name": "constant.other.predefined.tiny16asm", + "match": "\\b(PPU_CTRL_HI|PPU_CTRL_LO|FRAME_COUNT_HI|FRAME_COUNT_LO)\\b" + } + ] + }, + "instructions": { + "patterns": [ + { + "name": "keyword.control.tiny16asm", + "match": "(?i)\\b(LOADI|LOAD|STORE|MOV|ADD|SUB|ADC|SBC|INC|DEC|AND|OR|XOR|SHL|SHR|NOT|CMP|CMPI|JMP|JZ|JNZ|JC|JNC|CALL|RET|HALT|PUSH|POP|MOVSPR|MOVRSP)\\b" + } + ] + }, + "directives": { + "patterns": [ + { + "name": "keyword.control.directive.tiny16asm", + "match": "(?i)\\.(macro|endmacro|include)\\b" + }, + { + "name": "keyword.control.directive.tiny16asm", + "match": "(?i)\\b(section|ORG|TIMES|DB)\\b" + } + ] + }, + "sections": { + "patterns": [ + { + "name": "storage.type.section.tiny16asm", + "match": "\\.(code|data)\\b" + } + ] + }, + "macros": { + "patterns": [ + { + "name": "support.function.macro.tiny16asm", + "match": "(?i)\\b(SETADDR|LOAD16|STORE16|STORE16I|PUSH2|POP2|PUSH4|POP4|MUL2|MUL4|DIV2|DIV4|CLEAR|SUBI|ADDI|APU_INIT|APU_CH0_NOTE|APU_CH0_OFF|OAM_WRITE_SPRITE|READ_FRAME_COUNT|WAIT_VSYNC)\\b" + }, + { + "name": "variable.parameter.macro.tiny16asm", + "match": "@[a-zA-Z_][a-zA-Z0-9_]*" + } + ] + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.tiny16asm", + "match": "[+\\-*/%&|^~]|<<|>>" + } + ] + } + } +} diff --git a/misc/vscode/syntaxes/tiny16se.tmLanguage.json b/misc/vscode/syntaxes/tiny16se.tmLanguage.json new file mode 100644 index 0000000..f83e367 --- /dev/null +++ b/misc/vscode/syntaxes/tiny16se.tmLanguage.json @@ -0,0 +1,147 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "tiny16 SE", + "scopeName": "source.tiny16se", + "patterns": [ + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#numbers" }, + { "include": "#special-forms" }, + { "include": "#function-definitions" }, + { "include": "#constant-definitions" }, + { "include": "#data-definitions" }, + { "include": "#operators" }, + { "include": "#builtins" }, + { "include": "#booleans" }, + { "include": "#symbols" }, + { "include": "#parentheses" } + ], + "repository": { + "comments": { + "patterns": [ + { + "name": "comment.line.semicolon.tiny16se", + "match": ";.*$", + "captures": { + "0": { + "patterns": [ + { + "name": "keyword.codetag.notation.tiny16se", + "match": "\\b(TODO|FIXME|XXX|NOTE|HACK|BUG)\\b" + } + ] + } + } + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.double.tiny16se", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.tiny16se", + "match": "\\\\[nrt\\\\\"0]" + } + ] + } + ] + }, + "numbers": { + "patterns": [ + { + "name": "constant.numeric.hex.tiny16se", + "match": "\\b0x[0-9a-fA-F]+\\b" + }, + { + "name": "constant.numeric.decimal.tiny16se", + "match": "\\b\\d+\\b" + } + ] + }, + "special-forms": { + "patterns": [ + { + "name": "keyword.control.tiny16se", + "match": "\\b(def|defn|let|set|if|while|do|data|db|repeat|include)\\b" + } + ] + }, + "function-definitions": { + "patterns": [ + { + "match": "\\(\\s*(defn)\\s+([a-zA-Z_\\-][a-zA-Z0-9_\\-]*)", + "captures": { + "1": { "name": "keyword.control.tiny16se" }, + "2": { "name": "entity.name.function.tiny16se" } + } + } + ] + }, + "constant-definitions": { + "patterns": [ + { + "match": "\\(\\s*(def)\\s+([a-zA-Z_\\-][a-zA-Z0-9_\\-]*)", + "captures": { + "1": { "name": "keyword.control.tiny16se" }, + "2": { "name": "constant.other.tiny16se" } + } + } + ] + }, + "data-definitions": { + "patterns": [ + { + "match": "\\(\\s*(data)\\s+([a-zA-Z_\\-][a-zA-Z0-9_\\-]*)", + "captures": { + "1": { "name": "keyword.control.tiny16se" }, + "2": { "name": "storage.type.data.tiny16se" } + } + } + ] + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.tiny16se", + "match": "(?<=\\()\\s*(\\+|\\-|\\*|&|\\||\\^|~|<<|>>|=|!=|<=|>=|<|>|!)(?=\\s)" + } + ] + }, + "builtins": { + "patterns": [ + { + "name": "support.function.builtin.tiny16se", + "match": "\\b(add|sub|neg|inc|dec|and|or|xor|not|shl|shr|eq|ne|lt|gt|le|ge|lnot|load|store|addr|addr\\+|addr16|peek|peek16|peek\\*|poke|poke16|hi|lo)\\b" + } + ] + }, + "booleans": { + "patterns": [ + { + "name": "constant.language.boolean.tiny16se", + "match": "\\b(true|false)\\b" + } + ] + }, + "symbols": { + "patterns": [ + { + "name": "variable.other.tiny16se", + "match": "[a-zA-Z_\\-][a-zA-Z0-9_\\-]*" + } + ] + }, + "parentheses": { + "patterns": [ + { + "name": "punctuation.section.parens.tiny16se", + "match": "[()]" + } + ] + } + } +} diff --git a/sec/ast.c b/sec/ast.c index 4d37fc5..d81c2f5 100644 --- a/sec/ast.c +++ b/sec/ast.c @@ -1,5 +1,6 @@ #include "ast.h" +#include #include const char* ast_kind_name(AstKind kind) { @@ -53,13 +54,121 @@ const char* ast_kind_name(AstKind kind) { } } +// Pool management +void ast_pool_init(AstPool* pool) { + pool->chunks = NULL; + pool->chunk_count = 0; + pool->chunk_capacity = 0; + pool->current_index = AST_CHUNK_SIZE; // Force allocation on first use +} + +void ast_pool_free(AstPool* pool) { + for (size_t i = 0; i < pool->chunk_count; i++) { + free(pool->chunks[i]); + } + free(pool->chunks); + pool->chunks = NULL; + pool->chunk_count = 0; + pool->chunk_capacity = 0; + pool->current_index = AST_CHUNK_SIZE; +} + +void ast_pool_reset(AstPool* pool) { + // Reset to beginning of first chunk (keep allocated memory) + pool->current_index = 0; + if (pool->chunk_count > 0) { + // Clear first chunk + memset(pool->chunks[0], 0, AST_CHUNK_SIZE * sizeof(AstNode)); + } + // Reset chunk count to 1 if we have any chunks + if (pool->chunk_count > 1) { + for (size_t i = 1; i < pool->chunk_count; i++) { + free(pool->chunks[i]); + } + pool->chunk_count = 1; + } +} + +static bool ast_pool_grow(AstPool* pool) { + // Grow chunks array if needed + if (pool->chunk_count >= pool->chunk_capacity) { + size_t new_cap = pool->chunk_capacity == 0 ? 4 : pool->chunk_capacity * 2; + AstNode** new_chunks = realloc(pool->chunks, new_cap * sizeof(AstNode*)); + if (!new_chunks) return false; + pool->chunks = new_chunks; + pool->chunk_capacity = new_cap; + } + + // Allocate new chunk + AstNode* chunk = calloc(AST_CHUNK_SIZE, sizeof(AstNode)); + if (!chunk) return false; + + pool->chunks[pool->chunk_count++] = chunk; + pool->current_index = 0; + return true; +} + AstNode* ast_alloc(AstPool* pool) { - if (pool->count >= sizeof(pool->nodes) / sizeof(pool->nodes[0])) { - return NULL; + // Need new chunk? + if (pool->current_index >= AST_CHUNK_SIZE) { + if (!ast_pool_grow(pool)) { + return NULL; + } } - AstNode* node = &pool->nodes[pool->count++]; + + AstNode* node = &pool->chunks[pool->chunk_count - 1][pool->current_index++]; memset(node, 0, sizeof(*node)); return node; } -void ast_pool_reset(AstPool* pool) { pool->count = 0; } +// Array management +void ast_array_init(AstNodeArray* arr) { + arr->items = NULL; + arr->count = 0; + arr->capacity = 0; +} + +bool ast_array_push(AstNodeArray* arr, AstNode* node) { + if (arr->count >= arr->capacity) { + size_t new_cap = arr->capacity == 0 ? 8 : arr->capacity * 2; + AstNode** new_items = realloc(arr->items, new_cap * sizeof(AstNode*)); + if (!new_items) return false; + arr->items = new_items; + arr->capacity = new_cap; + } + arr->items[arr->count++] = node; + return true; +} + +void ast_array_free(AstNodeArray* arr) { + free(arr->items); + arr->items = NULL; + arr->count = 0; + arr->capacity = 0; +} + +// Program management +void ast_program_init(AstProgram* prog) { + prog->nodes = NULL; + prog->node_count = 0; + prog->node_capacity = 0; +} + +bool ast_program_add(AstProgram* prog, AstNode* node) { + if (prog->node_count >= prog->node_capacity) { + size_t new_cap = prog->node_capacity == 0 ? 64 : prog->node_capacity * 2; + AstNode** new_nodes = realloc(prog->nodes, new_cap * sizeof(AstNode*)); + if (!new_nodes) return false; + prog->nodes = new_nodes; + prog->node_capacity = new_cap; + } + prog->nodes[prog->node_count++] = node; + return true; +} + +void ast_program_free(AstProgram* prog) { + free(prog->nodes); + prog->nodes = NULL; + prog->node_count = 0; + prog->node_capacity = 0; +} diff --git a/sec/ast.h b/sec/ast.h index 887a0b8..55c46dc 100644 --- a/sec/ast.h +++ b/sec/ast.h @@ -4,11 +4,9 @@ #include #include -#define SE_MAX_CHILDREN 64 -#define SE_MAX_PARAMS 16 -#define SE_MAX_FUNCTIONS 128 -#define SE_MAX_CONSTANTS 256 -#define SE_MAX_SYMBOL_LEN 64 +// Compiler limits +#define SE_MAX_PARAMS 32 // Max function parameters (fixed, small) +#define SE_MAX_SYMBOL_LEN 64 // Max symbol name length typedef enum { // Atoms @@ -78,6 +76,13 @@ const char* ast_kind_name(AstKind kind); // Forward declaration typedef struct AstNode AstNode; +// Dynamic array of AstNode pointers +typedef struct { + AstNode** items; + size_t count; + size_t capacity; +} AstNodeArray; + struct AstNode { AstKind kind; size_t line; @@ -103,8 +108,7 @@ struct AstNode { char name[SE_MAX_SYMBOL_LEN]; char params[SE_MAX_PARAMS][SE_MAX_SYMBOL_LEN]; size_t param_count; - AstNode* body[SE_MAX_CHILDREN]; - size_t body_count; + AstNodeArray body; } defn; // AST_LET: (let (bindings) body...) @@ -112,8 +116,7 @@ struct AstNode { char vars[SE_MAX_PARAMS][SE_MAX_SYMBOL_LEN]; AstNode* vals[SE_MAX_PARAMS]; size_t binding_count; - AstNode* body[SE_MAX_CHILDREN]; - size_t body_count; + AstNodeArray body; } let; // AST_SET: (set var value) @@ -132,14 +135,12 @@ struct AstNode { // AST_WHILE: (while cond body...) struct { AstNode* cond; - AstNode* body[SE_MAX_CHILDREN]; - size_t body_count; + AstNodeArray body; } while_expr; // AST_DO: (do expr...), AST_DB, AST_REQUIRE struct { - AstNode* exprs[SE_MAX_CHILDREN]; - size_t expr_count; + AstNodeArray exprs; } block; // AST_DATA: (data name addr body...) @@ -147,8 +148,7 @@ struct AstNode { char name[SE_MAX_SYMBOL_LEN]; int32_t addr; // -1 if not specified or needs evaluation AstNode* addr_expr; // expression for address (evaluated at codegen time) - AstNode* body[SE_MAX_CHILDREN]; - size_t body_count; + AstNodeArray body; } data; // AST_REPEAT: (repeat count form) @@ -194,15 +194,37 @@ struct AstNode { } as; }; +// Dynamic program structure typedef struct { - AstNode* nodes[SE_MAX_FUNCTIONS + SE_MAX_CONSTANTS]; + AstNode** nodes; size_t node_count; + size_t node_capacity; } AstProgram; +// Dynamic AST node pool typedef struct { - AstNode nodes[4096]; - size_t count; + AstNode** chunks; // Array of chunk pointers + size_t chunk_count; + size_t chunk_capacity; + size_t current_index; // Index within current chunk } AstPool; +#define AST_CHUNK_SIZE 1024 // Nodes per chunk + +// Pool management +void ast_pool_init(AstPool* pool); +void ast_pool_free(AstPool* pool); AstNode* ast_alloc(AstPool* pool); + +// Array management +void ast_array_init(AstNodeArray* arr); +bool ast_array_push(AstNodeArray* arr, AstNode* node); +void ast_array_free(AstNodeArray* arr); + +// Program management +void ast_program_init(AstProgram* prog); +bool ast_program_add(AstProgram* prog, AstNode* node); +void ast_program_free(AstProgram* prog); + +// Legacy compatibility void ast_pool_reset(AstPool* pool); diff --git a/sec/codegen.c b/sec/codegen.c index 27b219c..30c16a4 100644 --- a/sec/codegen.c +++ b/sec/codegen.c @@ -108,8 +108,8 @@ static int32_t calc_data_size(AstNode* node) { case AST_STRING: return (int32_t)strlen(node->as.symbol.name); case AST_DB: { int32_t size = 0; - for (size_t i = 0; i < node->as.block.expr_count; i++) { - size += calc_data_size(node->as.block.exprs[i]); + for (size_t i = 0; i < node->as.block.exprs.count; i++) { + size += calc_data_size(node->as.block.exprs.items[i]); } return size; } @@ -285,8 +285,8 @@ bool se_codegen_collect(SeCodegen* cg, AstProgram* program) { // Calculate size of this data block int32_t size = 0; - for (size_t j = 0; j < node->as.data.body_count; j++) { - size += calc_data_size(node->as.data.body[j]); + for (size_t j = 0; j < node->as.data.body.count; j++) { + size += calc_data_size(node->as.data.body.items[j]); } // Store data label info @@ -816,8 +816,8 @@ static void emit_expr(SeCodegen* cg, AstNode* node) { emit_line(cg, "OR R0, R0"); emit_line(cg, "JZ __L%d", lbl_end); - for (size_t i = 0; i < node->as.while_expr.body_count; i++) { - emit_expr(cg, node->as.while_expr.body[i]); + for (size_t i = 0; i < node->as.while_expr.body.count; i++) { + emit_expr(cg, node->as.while_expr.body.items[i]); } emit_line(cg, "JMP __L%d", lbl_loop); @@ -827,8 +827,8 @@ static void emit_expr(SeCodegen* cg, AstNode* node) { } case AST_DO: - for (size_t i = 0; i < node->as.block.expr_count; i++) { - emit_expr(cg, node->as.block.exprs[i]); + for (size_t i = 0; i < node->as.block.exprs.count; i++) { + emit_expr(cg, node->as.block.exprs.items[i]); } break; @@ -864,8 +864,8 @@ static void emit_expr(SeCodegen* cg, AstNode* node) { } // Evaluate body - for (size_t i = 0; i < node->as.let.body_count; i++) { - emit_expr(cg, node->as.let.body[i]); + for (size_t i = 0; i < node->as.let.body.count; i++) { + emit_expr(cg, node->as.let.body.items[i]); } // Deallocate locals @@ -960,8 +960,8 @@ static void emit_data_value(SeCodegen* cg, AstNode* node) { } case AST_DB: - for (size_t i = 0; i < node->as.block.expr_count; i++) { - emit_data_value(cg, node->as.block.exprs[i]); + for (size_t i = 0; i < node->as.block.exprs.count; i++) { + emit_data_value(cg, node->as.block.exprs.items[i]); } break; @@ -970,9 +970,9 @@ static void emit_data_value(SeCodegen* cg, AstNode* node) { // For TIMES, we need inline DB if (node->as.repeat.form->kind == AST_DB) { emit(cg, "DB "); - for (size_t i = 0; i < node->as.repeat.form->as.block.expr_count; i++) { + for (size_t i = 0; i < node->as.repeat.form->as.block.exprs.count; i++) { if (i > 0) emit(cg, ", "); - AstNode* val = node->as.repeat.form->as.block.exprs[i]; + AstNode* val = node->as.repeat.form->as.block.exprs.items[i]; if (val->kind == AST_NUMBER) { emit(cg, "0x%02X", val->as.number & 0xFF); } @@ -1025,8 +1025,8 @@ static void emit_function(SeCodegen* cg, AstNode* node) { add_local(cg, node->as.defn.params[i], offset); // positive offset = parameter } - for (size_t i = 0; i < node->as.defn.body_count; i++) { - emit_expr(cg, node->as.defn.body[i]); + for (size_t i = 0; i < node->as.defn.body.count; i++) { + emit_expr(cg, node->as.defn.body.items[i]); } emit_line(cg, "RET"); @@ -1048,8 +1048,8 @@ static void emit_data(SeCodegen* cg, AstNode* node) { } emit(cg, "%s:\n", name); - for (size_t i = 0; i < node->as.data.body_count; i++) { - emit_data_value(cg, node->as.data.body[i]); + for (size_t i = 0; i < node->as.data.body.count; i++) { + emit_data_value(cg, node->as.data.body.items[i]); } emit(cg, "\n"); } diff --git a/sec/codegen.h b/sec/codegen.h index 56ed4a5..f635973 100644 --- a/sec/codegen.h +++ b/sec/codegen.h @@ -6,9 +6,12 @@ #include #include +// Codegen limits (these are fixed-size for simplicity) #define SE_MAX_LOCALS 32 #define SE_MAX_LABEL_ID 9999 #define SE_MAX_DATA_LABELS 128 +#define SE_MAX_CONSTANTS 4096 +#define SE_MAX_FUNCTIONS 1024 typedef struct { char name[SE_MAX_SYMBOL_LEN]; diff --git a/sec/parser.c b/sec/parser.c index fc4253c..85b34f1 100644 --- a/sec/parser.c +++ b/sec/parser.c @@ -185,16 +185,15 @@ static AstNode* parse_defn(SeParser* parser) { } advance(parser); - // Parse body forms - node->as.defn.body_count = 0; + // Parse body forms (dynamic array) + ast_array_init(&node->as.defn.body); while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.defn.body_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many body expressions"); - return NULL; - } AstNode* form = se_parser_parse_form(parser); if (!form) return NULL; - node->as.defn.body[node->as.defn.body_count++] = form; + if (!ast_array_push(&node->as.defn.body, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add body form"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -251,16 +250,15 @@ static AstNode* parse_let(SeParser* parser) { } advance(parser); - // Parse body forms - node->as.let.body_count = 0; + // Parse body forms (dynamic array) + ast_array_init(&node->as.let.body); while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.let.body_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many body expressions"); - return NULL; - } AstNode* form = se_parser_parse_form(parser); if (!form) return NULL; - node->as.let.body[node->as.let.body_count++] = form; + if (!ast_array_push(&node->as.let.body, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add body form"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -349,15 +347,15 @@ static AstNode* parse_while(SeParser* parser) { return NULL; } - node->as.while_expr.body_count = 0; + // Parse body forms (dynamic array) + ast_array_init(&node->as.while_expr.body); while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.while_expr.body_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many body expressions"); - return NULL; - } AstNode* form = se_parser_parse_form(parser); if (!form) return NULL; - node->as.while_expr.body[node->as.while_expr.body_count++] = form; + if (!ast_array_push(&node->as.while_expr.body, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add body form"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -376,15 +374,15 @@ static AstNode* parse_do(SeParser* parser) { AstNode* node = alloc_node(parser, AST_DO); if (!node) return NULL; - node->as.block.expr_count = 0; + // Parse expressions (dynamic array) + ast_array_init(&node->as.block.exprs); while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.block.expr_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many expressions"); - return NULL; - } AstNode* form = se_parser_parse_form(parser); if (!form) return NULL; - node->as.block.exprs[node->as.block.expr_count++] = form; + if (!ast_array_push(&node->as.block.exprs, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add expression"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -435,16 +433,15 @@ static AstNode* parse_data(SeParser* parser) { // Otherwise, it's body content - don't consume it } - // Parse body - node->as.data.body_count = 0; + // Parse body (dynamic array) + ast_array_init(&node->as.data.body); while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.data.body_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many data forms"); - return NULL; - } AstNode* form = se_parser_parse_form(parser); if (!form) return NULL; - node->as.data.body[node->as.data.body_count++] = form; + if (!ast_array_push(&node->as.data.body, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add data form"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -463,15 +460,15 @@ static AstNode* parse_db(SeParser* parser) { AstNode* node = alloc_node(parser, AST_DB); if (!node) return NULL; - node->as.block.expr_count = 0; + // Parse values (dynamic array) + ast_array_init(&node->as.block.exprs); while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.block.expr_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many values"); - return NULL; - } AstNode* form = se_parser_parse_form(parser); if (!form) return NULL; - node->as.block.exprs[node->as.block.expr_count++] = form; + if (!ast_array_push(&node->as.block.exprs, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add db value"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -562,7 +559,8 @@ static AstNode* parse_require(SeParser* parser) { AstNode* node = alloc_node(parser, AST_REQUIRE); if (!node) return NULL; - node->as.block.expr_count = 0; + // Initialize dynamic array + ast_array_init(&node->as.block.exprs); // Check if first element is a list (Clojure-style) if (parser->current.kind == SE_TOKEN_LPAREN) { @@ -570,10 +568,6 @@ static AstNode* parse_require(SeParser* parser) { // Parse symbols/strings inside the list while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.block.expr_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many require entries"); - return NULL; - } if (parser->current.kind != SE_TOKEN_SYMBOL && parser->current.kind != SE_TOKEN_STRING) { parser_set_error(parser, SE_PARSE_ERROR_UNEXPECTED_TOKEN, @@ -583,7 +577,11 @@ static AstNode* parse_require(SeParser* parser) { AstNode* entry = se_parser_parse_form(parser); if (!entry) return NULL; - node->as.block.exprs[node->as.block.expr_count++] = entry; + if (!ast_array_push(&node->as.block.exprs, entry)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, + "failed to add require entry"); + return NULL; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -594,10 +592,6 @@ static AstNode* parse_require(SeParser* parser) { } else { // Original style: (require name1 name2...) while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (node->as.block.expr_count >= SE_MAX_CHILDREN) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many require entries"); - return NULL; - } if (parser->current.kind != SE_TOKEN_SYMBOL && parser->current.kind != SE_TOKEN_STRING) { parser_set_error(parser, SE_PARSE_ERROR_UNEXPECTED_TOKEN, @@ -607,7 +601,11 @@ static AstNode* parse_require(SeParser* parser) { AstNode* entry = se_parser_parse_form(parser); if (!entry) return NULL; - node->as.block.exprs[node->as.block.expr_count++] = entry; + if (!ast_array_push(&node->as.block.exprs, entry)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, + "failed to add require entry"); + return NULL; + } } } @@ -1166,18 +1164,19 @@ bool se_parser_parse_ns_with_requires(SeParser* parser, AstProgram* program) { copy_token_text(ns_node->as.symbol.name, &parser->current, SE_MAX_SYMBOL_LEN); advance(parser); - program->nodes[program->node_count++] = ns_node; + if (!ast_program_add(program, ns_node)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add ns node"); + return true; + } // Parse body forms (mainly require) while (parser->current.kind != SE_TOKEN_RPAREN && parser->current.kind != SE_TOKEN_END) { - if (program->node_count >= SE_MAX_FUNCTIONS + SE_MAX_CONSTANTS) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many forms"); - return true; - } - AstNode* form = se_parser_parse_form(parser); if (!form) return true; - program->nodes[program->node_count++] = form; + if (!ast_program_add(program, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add form"); + return true; + } } if (parser->current.kind != SE_TOKEN_RPAREN) { @@ -1189,14 +1188,9 @@ bool se_parser_parse_ns_with_requires(SeParser* parser, AstProgram* program) { } bool se_parser_parse_program(SeParser* parser, AstProgram* program) { - program->node_count = 0; + ast_program_init(program); while (parser->current.kind != SE_TOKEN_END && parser->error == SE_PARSE_OK) { - if (program->node_count >= SE_MAX_FUNCTIONS + SE_MAX_CONSTANTS) { - parser_set_error(parser, SE_PARSE_ERROR_TOO_MANY_ARGS, "too many top-level forms"); - return false; - } - // Try to parse (ns ...) with nested forms if (se_parser_parse_ns_with_requires(parser, program)) { continue; @@ -1205,7 +1199,10 @@ bool se_parser_parse_program(SeParser* parser, AstProgram* program) { AstNode* form = se_parser_parse_form(parser); if (!form) break; - program->nodes[program->node_count++] = form; + if (!ast_program_add(program, form)) { + parser_set_error(parser, SE_PARSE_ERROR_OUT_OF_MEMORY, "failed to add top-level form"); + return false; + } } return parser->error == SE_PARSE_OK; diff --git a/sec/tiny16se.c b/sec/tiny16se.c index 49015b7..eb850ed 100644 --- a/sec/tiny16se.c +++ b/sec/tiny16se.c @@ -89,6 +89,7 @@ static void qualify_name(char* name, const char* ns) { static void apply_namespace(AstNode* node, const char* ns, SeScope* scope); static void apply_namespace_list(AstNode** nodes, size_t count, const char* ns, SeScope* scope) { + if (!nodes || count == 0) return; for (size_t i = 0; i < count; i++) { apply_namespace(nodes[i], ns, scope); } @@ -122,7 +123,7 @@ static void apply_namespace(AstNode* node, const char* ns, SeScope* scope) { for (size_t i = 0; i < node->as.defn.param_count; i++) { scope_push(scope, node->as.defn.params[i]); } - apply_namespace_list(node->as.defn.body, node->as.defn.body_count, ns, scope); + apply_namespace_list(node->as.defn.body.items, node->as.defn.body.count, ns, scope); scope_pop_to(scope, saved); return; } @@ -133,7 +134,7 @@ static void apply_namespace(AstNode* node, const char* ns, SeScope* scope) { apply_namespace(node->as.let.vals[i], ns, scope); scope_push(scope, node->as.let.vars[i]); } - apply_namespace_list(node->as.let.body, node->as.let.body_count, ns, scope); + apply_namespace_list(node->as.let.body.items, node->as.let.body.count, ns, scope); scope_pop_to(scope, saved); return; } @@ -148,12 +149,13 @@ static void apply_namespace(AstNode* node, const char* ns, SeScope* scope) { case AST_WHILE: apply_namespace(node->as.while_expr.cond, ns, scope); - apply_namespace_list(node->as.while_expr.body, node->as.while_expr.body_count, ns, scope); + apply_namespace_list(node->as.while_expr.body.items, node->as.while_expr.body.count, ns, + scope); return; case AST_DO: case AST_DB: - apply_namespace_list(node->as.block.exprs, node->as.block.expr_count, ns, scope); + apply_namespace_list(node->as.block.exprs.items, node->as.block.exprs.count, ns, scope); return; case AST_DATA: @@ -161,7 +163,7 @@ static void apply_namespace(AstNode* node, const char* ns, SeScope* scope) { if (node->as.data.addr_expr) { apply_namespace(node->as.data.addr_expr, ns, scope); } - apply_namespace_list(node->as.data.body, node->as.data.body_count, ns, scope); + apply_namespace_list(node->as.data.body.items, node->as.data.body.count, ns, scope); return; case AST_REPEAT: apply_namespace(node->as.repeat.form, ns, scope); return; @@ -252,10 +254,7 @@ static bool namespace_to_path(const char* ns, char* out, size_t out_size) { static bool is_absolute_path(const char* path) { return path[0] == '/'; } static bool append_program_node(AstProgram* program, AstNode* node) { - size_t max_nodes = SE_MAX_FUNCTIONS + SE_MAX_CONSTANTS; - if (program->node_count >= max_nodes) return false; - program->nodes[program->node_count++] = node; - return true; + return ast_program_add(program, node); } static bool parse_file_into_program(const char* filename, AstPool* pool, AstProgram* program) { @@ -335,8 +334,8 @@ static bool parse_requires_recursive(const char* filename, AstPool* pool, AstPro AstNode* node = program.nodes[i]; if (node->kind != AST_REQUIRE) continue; - for (size_t j = 0; j < node->as.block.expr_count; j++) { - AstNode* req = node->as.block.exprs[j]; + for (size_t j = 0; j < node->as.block.exprs.count; j++) { + AstNode* req = node->as.block.exprs.items[j]; char req_path[PATH_MAX]; const char* raw_name = NULL; bool is_symbol = false; @@ -425,11 +424,12 @@ int main(int argc, char** argv) { g_search_path = "stdlib/se"; } - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); AstProgram program; if (!parse_with_requires(args.source_filename, &pool, &program)) { + ast_pool_free(&pool); return EXIT_FAILURE; } @@ -437,6 +437,8 @@ int main(int argc, char** argv) { FILE* output = fopen(args.output_filename, "w"); if (!output) { perror("could not open output file"); + ast_program_free(&program); + ast_pool_free(&pool); return EXIT_FAILURE; } @@ -447,16 +449,22 @@ int main(int argc, char** argv) { if (!se_codegen_collect(&codegen, &program)) { se_codegen_print_error(&codegen); fclose(output); + ast_program_free(&program); + ast_pool_free(&pool); return EXIT_FAILURE; } if (!se_codegen_emit(&codegen, &program)) { se_codegen_print_error(&codegen); fclose(output); + ast_program_free(&program); + ast_pool_free(&pool); return EXIT_FAILURE; } fclose(output); + ast_program_free(&program); + ast_pool_free(&pool); return EXIT_SUCCESS; } diff --git a/stdlib/se/apu.se b/stdlib/se/apu.se index 242bbed..3f9b69c 100644 --- a/stdlib/se/apu.se +++ b/stdlib/se/apu.se @@ -94,27 +94,144 @@ (poke16 CH3_ENV_AD (| (* attack 16) decay)) (poke16 CH3_ENV_SR (| (* sustain 16) release))) -; Common sound effects -(defn sfx-jump () - (ch0-set-envelope 1 1 8 1) - (ch0-play-note 0xC8 0x07 0x12 3)) - -(defn sfx-hit () - (ch3-set-envelope 1 5 0 4) - (ch3-play-noise 9 14 18)) - -(defn sfx-coin () - (ch0-set-envelope 1 1 6 1) - (ch0-play-note 0xD0 0x07 0x2C 4)) - -(defn sfx-explosion () - (ch3-set-envelope 2 8 2 6) - (ch3-play-noise 12 15 30)) - -(defn sfx-select () - (ch0-set-envelope 1 1 4 1) - (ch0-play-note 0xA0 0x06 0x18 2)) - -(defn sfx-move () - (ch0-set-envelope 1 1 2 1) - (ch0-play-note 0x80 0x05 0x10 1)) +; ============================================================================= +; SFX Bank System +; ============================================================================= +; +; SFX entry format (9 bytes per entry, indices 0-8): +; [0] channel (0-3: pulse1/pulse2/tri/noise) +; [1] freq_lo +; [2] freq_hi +; [3] period (noise only) +; [4] volume (0-15) +; [5] duty (0-3, pulse only) +; [6] env_ad (attack << 4 | decay) +; [7] env_sr (sustain << 4 | release) +; [8] duration (frames) +; +; Example SFX table definition: +; (data sfx-table +; ; ID 0: Jump sound (pulse 0) +; (db 0 0xC8 0x07 0 18 2 0x11 0x81 3) +; ; ID 1: Hit sound (noise) +; (db 3 0 0 9 14 0 0x15 0x04 18)) + +(def SFX_PLAY (+ MMIO_BASE 0x90)) +(def SFX_STOP (+ MMIO_BASE 0x91)) +(def SFX_STATUS (+ MMIO_BASE 0x92)) +(def SFX_TABLE_HI (+ MMIO_BASE 0x93)) +(def SFX_TABLE_LO (+ MMIO_BASE 0x94)) +(def SFX_COUNT (+ MMIO_BASE 0x95)) + +; Initialize SFX system with table address and count +; table-hi: high byte of SFX table address +; table-lo: low byte of SFX table address +; count: number of SFX entries in table +(defn sfx-init (table-hi table-lo count) + (poke16 SFX_TABLE_HI table-hi) + (poke16 SFX_TABLE_LO table-lo) + (poke16 SFX_COUNT count)) + +; Play sound effect by ID (0 to count-1) +(defn sfx-play (sfx-id) + (poke16 SFX_PLAY sfx-id)) + +; Stop sound effect on channel (0-3) +(defn sfx-stop (channel) + (poke16 SFX_STOP channel)) + +; Check if any SFX is playing (returns bitmask of active channels) +(defn sfx-status () + (peek16 SFX_STATUS)) + +; ============================================================================= +; Music Sequencer System +; ============================================================================= +; +; Music note format (4 bytes per note): +; [0] note (0=rest, 1-36=notes C3-B5) +; [1] volume (0-15) +; [2] duration (frames) +; [3] reserved +; +; Note values: +; 0 = rest (silence) +; 1 = C3, 2 = C#3, 3 = D3, 4 = D#3, 5 = E3, 6 = F3 +; 7 = F#3, 8 = G3, 9 = G#3, 10 = A3, 11 = A#3, 12 = B3 +; 13 = C4, 14 = C#4, 15 = D4, 16 = D#4, 17 = E4, 18 = F4 +; 19 = F#4, 20 = G4, 21 = G#4, 22 = A4, 23 = A#4, 24 = B4 +; 25 = C5, 26 = C#5, 27 = D5, 28 = D#5, 29 = E5, 30 = F5 +; 31 = F#5, 32 = G5, 33 = G#5, 34 = A5, 35 = A#5, 36 = B5 +; +; Example song definition: +; (data my-song +; (db 13 12 8 0) ; C4, vol=12, dur=8 +; (db 17 12 8 0) ; E4 +; (db 20 12 8 0) ; G4 +; (db 25 12 16 0)) ; C5 + +; Music note constants +(def NOTE_REST 0) +(def NOTE_C3 1) +(def NOTE_CS3 2) +(def NOTE_D3 3) +(def NOTE_DS3 4) +(def NOTE_E3 5) +(def NOTE_F3 6) +(def NOTE_FS3 7) +(def NOTE_G3 8) +(def NOTE_GS3 9) +(def NOTE_A3 10) +(def NOTE_AS3 11) +(def NOTE_B3 12) +(def NOTE_C4 13) +(def NOTE_CS4 14) +(def NOTE_D4 15) +(def NOTE_DS4 16) +(def NOTE_E4 17) +(def NOTE_F4 18) +(def NOTE_FS4 19) +(def NOTE_G4 20) +(def NOTE_GS4 21) +(def NOTE_A4 22) +(def NOTE_AS4 23) +(def NOTE_B4 24) +(def NOTE_C5 25) +(def NOTE_CS5 26) +(def NOTE_D5 27) +(def NOTE_DS5 28) +(def NOTE_E5 29) +(def NOTE_F5 30) +(def NOTE_FS5 31) +(def NOTE_G5 32) +(def NOTE_GS5 33) +(def NOTE_A5 34) +(def NOTE_AS5 35) +(def NOTE_B5 36) + +(def MUSIC_CTRL (+ MMIO_BASE 0xA0)) +(def MUSIC_STATUS (+ MMIO_BASE 0xA1)) +(def MUSIC_ADDR_HI (+ MMIO_BASE 0xA2)) +(def MUSIC_ADDR_LO (+ MMIO_BASE 0xA3)) +(def MUSIC_LEN_HI (+ MMIO_BASE 0xA4)) +(def MUSIC_LEN_LO (+ MMIO_BASE 0xA5)) + +; Start playing music +; song-hi: high byte of song data address +; song-lo: low byte of song data address +; len: number of notes in song +; loop: 0=play once, 1=loop +(defn music-play (song-hi song-lo len-hi len-lo loop) + (poke16 MUSIC_ADDR_HI song-hi) + (poke16 MUSIC_ADDR_LO song-lo) + (poke16 MUSIC_LEN_HI len-hi) + (poke16 MUSIC_LEN_LO len-lo) + (poke16 MUSIC_CTRL (| 0x01 (if loop 0x04 0x00)))) + +; Stop music playback +(defn music-stop () + (poke16 MUSIC_CTRL 0x02)) + +; Check if music is playing (returns 1 if playing, 0 if stopped) +(defn music-playing () + (peek16 MUSIC_STATUS)) diff --git a/stdlib/se/music.se b/stdlib/se/music.se deleted file mode 100644 index 62535c6..0000000 --- a/stdlib/se/music.se +++ /dev/null @@ -1,112 +0,0 @@ -; music - higher-level sound and music API -(ns music) - -; Note indices for note() function -(def NOTE-C3 0) -(def NOTE-D3 1) -(def NOTE-E3 2) -(def NOTE-F3 3) -(def NOTE-G3 4) -(def NOTE-A3 5) -(def NOTE-B3 6) -(def NOTE-C4 7) -(def NOTE-D4 8) -(def NOTE-E4 9) -(def NOTE-F4 10) -(def NOTE-G4 11) -(def NOTE-A4 12) -(def NOTE-B4 13) -(def NOTE-C5 14) -(def NOTE-D5 15) -(def NOTE-E5 16) -(def NOTE-F5 17) -(def NOTE-G5 18) -(def NOTE-A5 19) -(def NOTE-B5 20) -(def NOTE-REST 255) - -; Note frequency table (stored as data) -(data note-freqs-lo (db 0x1A 0x2B 0x3B 0x4C 0x5D 0x6C 0x7A - 0x57 0x67 0x7A 0x84 0x8F 0x9C 0xA6 - 0xAB 0xB6 0xBD 0xC3 0xCA 0xD4 0xDE)) - -(data note-freqs-hi (db 0x07 0x07 0x07 0x07 0x07 0x07 0x07 - 0x07 0x07 0x07 0x07 0x07 0x07 0x07 - 0x07 0x07 0x07 0x07 0x07 0x07 0x07)) - -; Get frequency lo byte for note index -(defn get-freq-lo (note-idx) - (peek (hi note-freqs-lo) (+ (lo note-freqs-lo) note-idx))) - -; Get frequency hi byte for note index -(defn get-freq-hi (note-idx) - (peek (hi note-freqs-hi) (+ (lo note-freqs-hi) note-idx))) - -(def MMIO-BASE 0xBF00) -(def FRAME-COUNT (+ MMIO-BASE 0x22)) -(def MAX-NOTE-INDEX 21) - -; Play note on channel 0 (square wave) -; IMPORTANT: Non-blocking - returns immediately! -; -; Calling this again OVERWRITES the channel immediately. -; You MUST wait between notes or only the last one plays. -; -; Example: -; (music/note NOTE-C4 15 12) -; (music/wait 15) ; REQUIRED -; (music/note NOTE-D4 15 12) -(defn note (note-idx duration volume) - (if (< note-idx MAX-NOTE-INDEX) - (let (lo (get-freq-lo note-idx) - hi (get-freq-hi note-idx)) - (apu/ch0-set-envelope 1 1 8 1) - (apu/ch0-play-note lo hi volume duration)) - 0)) - -; Wait for specified number of frames -(defn wait (frames) - (poke16 music-frame-start (peek16 FRAME-COUNT)) - (poke16 music-frame-count 0) - (while (< (peek16 music-frame-count) frames) - (let (current (peek16 FRAME-COUNT) - start (peek16 music-frame-start)) - (if (!= current start) - (do - (poke16 music-frame-start current) - (poke16 music-frame-count (inc (peek16 music-frame-count)))) - 0)))) - -(data music-frame-start (db 0)) -(data music-frame-count (db 0)) - -(def BEEP-DURATION 5) -(def BEEP-VOLUME 12) - -; Simple beep -(defn beep (note-idx) - (note note-idx BEEP-DURATION BEEP-VOLUME)) - -(def NOISE-PERIOD 12) -(def NOISE-VOLUME 14) -(def NOISE-DURATION 20) -(def NOISE-ENV-AD 0x15) -(def NOISE-ENV-SR 0x04) - -; Error/negative sound -(defn error-sound () - (apu/ch3-set-envelope 1 5 0 4) - (apu/ch3-play-noise NOISE-PERIOD NOISE-VOLUME NOISE-DURATION)) - -(def SUCCESS-DURATION 8) -(def SUCCESS-VOLUME 15) -(def SELECT-DURATION 3) -(def SELECT-VOLUME 10) - -; Success sound -(defn success () - (note NOTE-C5 SUCCESS-DURATION SUCCESS-VOLUME)) - -; Menu selection beep -(defn select () - (note NOTE-G4 SELECT-DURATION SELECT-VOLUME)) diff --git a/stdlib/se/vivaldi-winter.se b/stdlib/se/vivaldi-winter.se new file mode 100644 index 0000000..e4765c4 --- /dev/null +++ b/stdlib/se/vivaldi-winter.se @@ -0,0 +1,693 @@ +; vivaldi-winter - music module generated by midi2music +; Source: examples/assets/vivaldi-winter.mid (track 0) +; 676 notes, time scale 0.50 +; Format: note (0=rest, 1-36=C3-B5), volume (0-15), duration (frames), reserved +; +; Usage: +; (require (vivaldi-winter)) +; (apu/music-play (hi vivaldi-winter/data) (lo vivaldi-winter/data) vivaldi-winter/length 1) + +(ns vivaldi-winter) + +(def length 676) +(def length-hi 2) +(def length-lo 164) + +(data data + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 7 5 5 0) ; F#3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 1 5 23 0) ; C3 + (db 0 0 120 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 23 0) ; C3 + (db 0 0 120 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 23 0) ; C3 + (db 0 0 255 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 1 5 23 0) ; C3 + (db 0 0 23 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 23 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 23 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 23 0) ; rest + (db 1 5 5 0) ; C3 + (db 1 5 5 0) ; C3 + (db 1 5 5 0) ; C3 + (db 1 5 5 0) ; C3 + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 11 5 5 0) ; A#3 + (db 0 0 5 0) ; rest + (db 11 5 5 0) ; A#3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 8 5 5 0) ; G3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 9 5 5 0) ; G#3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 6 5 5 0) ; F3 + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 5 5 2 0) ; E3 + (db 5 5 5 0) ; E3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 3 5 5 0) ; D3 + (db 0 0 5 0) ; rest + (db 2 5 5 0) ; C#3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 5 0) ; C3 + (db 0 0 5 0) ; rest + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 6 5 23 0) ; F3 + (db 0 0 23 0) ; rest + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 0 0 23 0) ; rest + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 8 5 2 0) ; G3 + (db 1 5 23 0) ; C3 + (db 0 0 23 0) ; rest + (db 4 5 23 0) ; D#3 + (db 0 0 23 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 3 5 5 0) ; D3 + (db 0 0 5 0) ; rest + (db 3 5 5 0) ; D3 + (db 0 0 5 0) ; rest + (db 3 5 5 0) ; D3 + (db 0 0 5 0) ; rest + (db 3 5 5 0) ; D3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 4 5 5 0) ; D#3 + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 0 0 5 0) ; rest + (db 4 5 23 0) ; D#3 + (db 0 0 23 0) ; rest + (db 8 5 23 0) ; G3 + (db 4 5 23 0) ; D#3 + (db 0 0 23 0) ; rest + (db 10 5 23 0) ; A3 + (db 6 5 23 0) ; F3 + (db 0 0 23 0) ; rest + (db 12 5 23 0) ; B3 + (db 8 5 23 0) ; G3 + (db 1 5 23 0) ; C3 + (db 0 0 255 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 23 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 4 5 2 0) ; D#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 23 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 2 5 2 0) ; C#3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 23 0) ; rest + (db 13 5 2 0) ; C4 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 13 5 2 0) ; C4 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 13 5 2 0) ; C4 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 13 5 2 0) ; C4 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 6 5 2 0) ; F3 + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 0 0 2 0) ; rest + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 2 0) ; C3 + (db 1 5 3 0) ; C3 + (db 1 5 4 0) ; C3 + (db 1 5 5 0) ; C3 + (db 1 5 5 0) ; C3 + (db 0 0 7 0) ; rest + (db 1 5 7 0) ; C3 + (db 0 0 7 0) ; rest + (db 1 5 9 0) ; C3 + (db 0 0 9 0) ; rest + (db 1 5 10 0) ; C3 + (db 0 0 12 0) ; rest +) diff --git a/tests/sec_test.c b/tests/sec_test.c index 817efb4..b8b1fc3 100644 --- a/tests/sec_test.c +++ b/tests/sec_test.c @@ -327,8 +327,8 @@ void test_lexer_special_symbols(void) { void test_parser_number(void) { const char* input = "42"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -340,8 +340,8 @@ void test_parser_number(void) { void test_parser_string(void) { const char* input = "\"hello\""; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -353,8 +353,8 @@ void test_parser_string(void) { void test_parser_symbol(void) { const char* input = "foo"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -366,8 +366,8 @@ void test_parser_symbol(void) { void test_parser_def(void) { const char* input = "(def x 42)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -382,8 +382,8 @@ void test_parser_def(void) { void test_parser_defn_simple(void) { const char* input = "(defn foo () 42)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -392,14 +392,14 @@ void test_parser_defn_simple(void) { TEST_ASSERT(node->kind == AST_DEFN); TEST_ASSERT(strcmp(node->as.defn.name, "foo") == 0); TEST_ASSERT(node->as.defn.param_count == 0); - TEST_ASSERT(node->as.defn.body_count == 1); - TEST_ASSERT(node->as.defn.body[0]->kind == AST_NUMBER); + TEST_ASSERT(node->as.defn.body.count == 1); + TEST_ASSERT(node->as.defn.body.items[0]->kind == AST_NUMBER); } void test_parser_defn_with_params(void) { const char* input = "(defn add (x y) (+ x y))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -410,13 +410,13 @@ void test_parser_defn_with_params(void) { TEST_ASSERT(node->as.defn.param_count == 2); TEST_ASSERT(strcmp(node->as.defn.params[0], "x") == 0); TEST_ASSERT(strcmp(node->as.defn.params[1], "y") == 0); - TEST_ASSERT(node->as.defn.body_count == 1); + TEST_ASSERT(node->as.defn.body.count == 1); } void test_parser_let(void) { const char* input = "(let (x 10 y 20) (+ x y))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -428,13 +428,13 @@ void test_parser_let(void) { TEST_ASSERT(strcmp(node->as.let.vars[1], "y") == 0); TEST_ASSERT(node->as.let.vals[0]->kind == AST_NUMBER); TEST_ASSERT(node->as.let.vals[1]->kind == AST_NUMBER); - TEST_ASSERT(node->as.let.body_count == 1); + TEST_ASSERT(node->as.let.body.count == 1); } void test_parser_set(void) { const char* input = "(set x 42)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -448,8 +448,8 @@ void test_parser_set(void) { void test_parser_if(void) { const char* input = "(if (< x 10) 1 0)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -463,8 +463,8 @@ void test_parser_if(void) { void test_parser_while(void) { const char* input = "(while (< i 10) (set i (+ i 1)))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -472,26 +472,26 @@ void test_parser_while(void) { TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_WHILE); TEST_ASSERT(node->as.while_expr.cond != NULL); - TEST_ASSERT(node->as.while_expr.body_count == 1); + TEST_ASSERT(node->as.while_expr.body.count == 1); } void test_parser_do(void) { const char* input = "(do (set x 1) (set y 2) x)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_DO); - TEST_ASSERT(node->as.block.expr_count == 3); + TEST_ASSERT(node->as.block.exprs.count == 3); } void test_parser_add(void) { const char* input = "(+ 1 2)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -507,8 +507,8 @@ void test_parser_arithmetic_ops(void) { AstKind expected[] = {AST_ADD, AST_SUB, AST_INC, AST_DEC}; for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, tests[i], strlen(tests[i]), &pool); @@ -523,8 +523,8 @@ void test_parser_comparison_ops(void) { AstKind expected[] = {AST_EQ, AST_NE, AST_LT, AST_GT, AST_LE, AST_GE}; for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, tests[i], strlen(tests[i]), &pool); @@ -539,8 +539,8 @@ void test_parser_bitwise_ops(void) { AstKind expected[] = {AST_AND, AST_OR, AST_XOR, AST_NOT, AST_SHL, AST_SHR}; for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, tests[i], strlen(tests[i]), &pool); @@ -552,8 +552,8 @@ void test_parser_bitwise_ops(void) { void test_parser_nested_expressions(void) { const char* input = "(+ (* 2 3) (- 10 4))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -566,8 +566,8 @@ void test_parser_nested_expressions(void) { void test_parser_function_call(void) { const char* input = "(foo 1 2 3)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -580,8 +580,8 @@ void test_parser_function_call(void) { void test_parser_data(void) { const char* input = "(data msg (db \"hello\"))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -589,26 +589,26 @@ void test_parser_data(void) { TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_DATA); TEST_ASSERT(strcmp(node->as.data.name, "msg") == 0); - TEST_ASSERT(node->as.data.body_count == 1); + TEST_ASSERT(node->as.data.body.count == 1); } void test_parser_db(void) { const char* input = "(db 1 2 3)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_DB); - TEST_ASSERT(node->as.block.expr_count == 3); + TEST_ASSERT(node->as.block.exprs.count == 3); } void test_parser_repeat(void) { const char* input = "(repeat 10 (db 0))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -621,8 +621,8 @@ void test_parser_repeat(void) { void test_parser_addr(void) { const char* input = "(addr 0xBF 0x00)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -635,8 +635,8 @@ void test_parser_addr(void) { void test_parser_addr16(void) { const char* input = "(addr16 0xBF00)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -649,8 +649,8 @@ void test_parser_load_store(void) { // Test load { const char* input = "(load (addr 0xBF 0x00))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -663,8 +663,8 @@ void test_parser_load_store(void) { // Test store { const char* input = "(store (addr 0xBF 0x00) 42)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -680,8 +680,8 @@ void test_parser_hi_lo(void) { // Test hi { const char* input = "(hi 0x1234)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -693,8 +693,8 @@ void test_parser_hi_lo(void) { // Test lo { const char* input = "(lo 0x1234)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -706,8 +706,8 @@ void test_parser_hi_lo(void) { void test_parser_ns(void) { const char* input = "(ns mylib)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -720,8 +720,8 @@ void test_parser_ns(void) { void test_parser_ns_with_body(void) { const char* input = "(ns mylib (require (core)))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -736,8 +736,8 @@ void test_parser_ns_with_body(void) { void test_parser_ns_error_no_symbol(void) { const char* input = "(ns 123)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -748,96 +748,96 @@ void test_parser_ns_error_no_symbol(void) { void test_parser_require_single(void) { const char* input = "(require math)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_REQUIRE); - TEST_ASSERT(node->as.block.expr_count == 1); - TEST_ASSERT(node->as.block.exprs[0]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[0]->as.symbol.name, "math") == 0); + TEST_ASSERT(node->as.block.exprs.count == 1); + TEST_ASSERT(node->as.block.exprs.items[0]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[0]->as.symbol.name, "math") == 0); TEST_ASSERT(!se_parser_has_error(&parser)); } void test_parser_require_multiple(void) { const char* input = "(require math io graphics)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_REQUIRE); - TEST_ASSERT(node->as.block.expr_count == 3); - TEST_ASSERT(node->as.block.exprs[0]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[0]->as.symbol.name, "math") == 0); - TEST_ASSERT(node->as.block.exprs[1]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[1]->as.symbol.name, "io") == 0); - TEST_ASSERT(node->as.block.exprs[2]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[2]->as.symbol.name, "graphics") == 0); + TEST_ASSERT(node->as.block.exprs.count == 3); + TEST_ASSERT(node->as.block.exprs.items[0]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[0]->as.symbol.name, "math") == 0); + TEST_ASSERT(node->as.block.exprs.items[1]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[1]->as.symbol.name, "io") == 0); + TEST_ASSERT(node->as.block.exprs.items[2]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[2]->as.symbol.name, "graphics") == 0); TEST_ASSERT(!se_parser_has_error(&parser)); } void test_parser_require_list_syntax(void) { const char* input = "(require (math io graphics))"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_REQUIRE); - TEST_ASSERT(node->as.block.expr_count == 3); - TEST_ASSERT(node->as.block.exprs[0]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[0]->as.symbol.name, "math") == 0); - TEST_ASSERT(node->as.block.exprs[1]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[1]->as.symbol.name, "io") == 0); - TEST_ASSERT(node->as.block.exprs[2]->kind == AST_SYMBOL); - TEST_ASSERT(strcmp(node->as.block.exprs[2]->as.symbol.name, "graphics") == 0); + TEST_ASSERT(node->as.block.exprs.count == 3); + TEST_ASSERT(node->as.block.exprs.items[0]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[0]->as.symbol.name, "math") == 0); + TEST_ASSERT(node->as.block.exprs.items[1]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[1]->as.symbol.name, "io") == 0); + TEST_ASSERT(node->as.block.exprs.items[2]->kind == AST_SYMBOL); + TEST_ASSERT(strcmp(node->as.block.exprs.items[2]->as.symbol.name, "graphics") == 0); TEST_ASSERT(!se_parser_has_error(&parser)); } void test_parser_require_with_strings(void) { const char* input = "(require \"math.se\" \"io.se\")"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_REQUIRE); - TEST_ASSERT(node->as.block.expr_count == 2); - TEST_ASSERT(node->as.block.exprs[0]->kind == AST_STRING); - TEST_ASSERT(strcmp(node->as.block.exprs[0]->as.symbol.name, "math.se") == 0); - TEST_ASSERT(node->as.block.exprs[1]->kind == AST_STRING); - TEST_ASSERT(strcmp(node->as.block.exprs[1]->as.symbol.name, "io.se") == 0); + TEST_ASSERT(node->as.block.exprs.count == 2); + TEST_ASSERT(node->as.block.exprs.items[0]->kind == AST_STRING); + TEST_ASSERT(strcmp(node->as.block.exprs.items[0]->as.symbol.name, "math.se") == 0); + TEST_ASSERT(node->as.block.exprs.items[1]->kind == AST_STRING); + TEST_ASSERT(strcmp(node->as.block.exprs.items[1]->as.symbol.name, "io.se") == 0); TEST_ASSERT(!se_parser_has_error(&parser)); } void test_parser_require_empty(void) { const char* input = "(require)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); AstNode* node = se_parser_parse_form(&parser); TEST_ASSERT(node != NULL); TEST_ASSERT(node->kind == AST_REQUIRE); - TEST_ASSERT(node->as.block.expr_count == 0); + TEST_ASSERT(node->as.block.exprs.count == 0); TEST_ASSERT(!se_parser_has_error(&parser)); } void test_parser_error_expected_lparen(void) { const char* input = "defn foo () 42)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -849,8 +849,8 @@ void test_parser_error_expected_lparen(void) { void test_parser_error_expected_rparen(void) { const char* input = "(+ 1 2"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -861,8 +861,8 @@ void test_parser_error_expected_rparen(void) { void test_parser_error_unknown_form(void) { const char* input = "(unknown-form x y)"; - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); @@ -877,8 +877,8 @@ void test_parser_error_unknown_form(void) { // ============================================================================= static char* codegen_to_string(const char* input) { - static AstPool pool; - ast_pool_reset(&pool); + AstPool pool; + ast_pool_init(&pool); SeParser parser; se_parser_init(&parser, input, strlen(input), &pool); diff --git a/tools/midi2music.c b/tools/midi2music.c new file mode 100644 index 0000000..32802ee --- /dev/null +++ b/tools/midi2music.c @@ -0,0 +1,424 @@ +// midi2music - Convert MIDI files to tiny16 music format +// +// Usage: midi2music input.mid [options] +// +// Options: +// -o Output file (default: stdout) +// -n Data label name (default: song_data) +// -t Track number to extract (default: 0) +// -c MIDI channel to extract (default: all) +// -v Default volume 0-15 (default: 6) +// -s Time scale factor (default: 1.0) +// +// Output format (4 bytes per note): +// [0] note (0=rest, 1-36=C3-B5) +// [1] volume (0-15) +// [2] duration (frames at 60fps) +// [3] reserved + +#include +#include +#include +#include +#include + +#define MAX_NOTES 1024 +#define FRAMES_PER_SECOND 60 + +// MIDI note range: 48 (C3) to 83 (B5) -> tiny16 notes 1-36 +#define MIDI_NOTE_MIN 48 +#define MIDI_NOTE_MAX 83 + +typedef struct { + uint8_t note; // 0=rest, 1-36=notes + uint8_t volume; // 0-15 + uint8_t duration; // frames + uint8_t reserved; +} MusicNote; + +typedef struct { + MusicNote notes[MAX_NOTES]; + int count; +} MusicSequence; + +// Read big-endian values from MIDI file +static uint32_t read_u32_be(FILE* f) { + uint8_t b[4]; + fread(b, 1, 4, f); + return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3]; +} + +static uint16_t read_u16_be(FILE* f) { + uint8_t b[2]; + fread(b, 1, 2, f); + return ((uint16_t)b[0] << 8) | b[1]; +} + +// Read variable-length quantity (MIDI delta time) +static uint32_t read_vlq(FILE* f) { + uint32_t value = 0; + uint8_t byte; + do { + fread(&byte, 1, 1, f); + value = (value << 7) | (byte & 0x7F); + } while (byte & 0x80); + return value; +} + +// Convert MIDI note to tiny16 note (0=rest, 1-36=C3-B5) +static uint8_t midi_to_tiny16_note(uint8_t midi_note) { + if (midi_note < MIDI_NOTE_MIN || midi_note > MIDI_NOTE_MAX) { + return 0; // Out of range + } + return midi_note - MIDI_NOTE_MIN + 1; +} + +// Convert MIDI velocity (0-127) to tiny16 volume (0-15) +static uint8_t midi_velocity_to_volume(uint8_t velocity, uint8_t default_vol) { + if (velocity == 0) return 0; + uint8_t vol = (velocity * 15) / 127; + return vol > 0 ? vol : default_vol; +} + +// Convert MIDI ticks to frames (60fps) +static uint8_t ticks_to_frames(uint32_t ticks, uint16_t ticks_per_beat, uint32_t tempo_us, + float scale) { + // tempo_us = microseconds per beat + // frames = ticks * (tempo_us / 1000000) * 60 / ticks_per_beat + double seconds = (double)ticks * tempo_us / 1000000.0 / ticks_per_beat; + double frames = seconds * FRAMES_PER_SECOND * scale; + if (frames < 1) frames = 1; + if (frames > 255) frames = 255; + return (uint8_t)frames; +} + +typedef struct { + uint8_t note; + uint8_t velocity; + uint32_t start_tick; +} ActiveNote; + +#define MAX_ACTIVE_NOTES 16 + +static int parse_midi_track(FILE* f, uint32_t track_len, MusicSequence* seq, int target_channel, + uint16_t ticks_per_beat, uint8_t default_vol, float time_scale) { + long track_start = ftell(f); + long track_end = track_start + track_len; + + uint32_t current_tick = 0; + uint32_t tempo_us = 500000; // Default: 120 BPM + uint8_t running_status = 0; + + ActiveNote active[MAX_ACTIVE_NOTES]; + int active_count = 0; + + uint32_t last_note_end_tick = 0; + + while (ftell(f) < track_end && seq->count < MAX_NOTES) { + uint32_t delta = read_vlq(f); + current_tick += delta; + + uint8_t status; + fread(&status, 1, 1, f); + + // Handle running status + if (status < 0x80) { + fseek(f, -1, SEEK_CUR); + status = running_status; + } else { + running_status = status; + } + + uint8_t type = status & 0xF0; + uint8_t channel = status & 0x0F; + + if (type == 0x90 || type == 0x80) { + // Note On / Note Off + uint8_t note, velocity; + fread(¬e, 1, 1, f); + fread(&velocity, 1, 1, f); + + // Note On with velocity 0 = Note Off + bool is_note_on = (type == 0x90 && velocity > 0); + + if (target_channel >= 0 && channel != target_channel) { + continue; + } + + if (is_note_on) { + // Add rest if there's a gap + if (last_note_end_tick > 0 && current_tick > last_note_end_tick) { + uint32_t gap_ticks = current_tick - last_note_end_tick; + uint8_t gap_frames = + ticks_to_frames(gap_ticks, ticks_per_beat, tempo_us, time_scale); + if (gap_frames > 0 && seq->count < MAX_NOTES) { + seq->notes[seq->count].note = 0; // rest + seq->notes[seq->count].volume = 0; + seq->notes[seq->count].duration = gap_frames; + seq->notes[seq->count].reserved = 0; + seq->count++; + } + } + + // Start tracking this note + if (active_count < MAX_ACTIVE_NOTES) { + active[active_count].note = note; + active[active_count].velocity = velocity; + active[active_count].start_tick = current_tick; + active_count++; + } + } else { + // Note Off - find and complete the note + for (int i = 0; i < active_count; i++) { + if (active[i].note == note) { + uint32_t duration_ticks = current_tick - active[i].start_tick; + uint8_t tiny16_note = midi_to_tiny16_note(note); + uint8_t vol = midi_velocity_to_volume(active[i].velocity, default_vol); + uint8_t frames = + ticks_to_frames(duration_ticks, ticks_per_beat, tempo_us, time_scale); + + if (tiny16_note > 0 && seq->count < MAX_NOTES) { + seq->notes[seq->count].note = tiny16_note; + seq->notes[seq->count].volume = vol; + seq->notes[seq->count].duration = frames; + seq->notes[seq->count].reserved = 0; + seq->count++; + } + + last_note_end_tick = current_tick; + + // Remove from active list + for (int j = i; j < active_count - 1; j++) { + active[j] = active[j + 1]; + } + active_count--; + break; + } + } + } + } else if (type == 0xA0) { + // Polyphonic aftertouch + fseek(f, 2, SEEK_CUR); + } else if (type == 0xB0) { + // Control change + fseek(f, 2, SEEK_CUR); + } else if (type == 0xC0) { + // Program change + fseek(f, 1, SEEK_CUR); + } else if (type == 0xD0) { + // Channel aftertouch + fseek(f, 1, SEEK_CUR); + } else if (type == 0xE0) { + // Pitch bend + fseek(f, 2, SEEK_CUR); + } else if (status == 0xFF) { + // Meta event + uint8_t meta_type; + fread(&meta_type, 1, 1, f); + uint32_t meta_len = read_vlq(f); + + if (meta_type == 0x51 && meta_len == 3) { + // Tempo change + uint8_t t[3]; + fread(t, 1, 3, f); + tempo_us = ((uint32_t)t[0] << 16) | ((uint32_t)t[1] << 8) | t[2]; + } else { + fseek(f, meta_len, SEEK_CUR); + } + } else if (status == 0xF0 || status == 0xF7) { + // SysEx + uint32_t sysex_len = read_vlq(f); + fseek(f, sysex_len, SEEK_CUR); + } + } + + return 0; +} + +static void print_usage(const char* prog) { + fprintf(stderr, "Usage: %s input.mid [options]\n", prog); + fprintf(stderr, "\nOptions:\n"); + fprintf(stderr, " -o Output file (default: stdout)\n"); + fprintf(stderr, " -n Module/data name (default: song)\n"); + fprintf(stderr, " -t Track number to extract (default: 0)\n"); + fprintf(stderr, " -c MIDI channel 0-15 (default: all)\n"); + fprintf(stderr, " -v Default volume 0-15 (default: 6)\n"); + fprintf(stderr, " -s Time scale factor (default: 1.0)\n"); + fprintf(stderr, " -m Max notes to extract (default: unlimited)\n"); + fprintf(stderr, "\nOutput: S-expression module (.se) for tiny16\n"); + fprintf(stderr, "Use with: (require (mymodule)) then mymodule/data, mymodule/length\n"); + fprintf(stderr, "\nNote range: C3 (MIDI 48) to B5 (MIDI 83)\n"); +} + +int main(int argc, char** argv) { + const char* input_file = NULL; + const char* output_file = NULL; + const char* module_name = "song"; + int target_track = 0; + int target_channel = -1; // -1 = all channels + uint8_t default_volume = 6; + float time_scale = 1.0f; + int max_notes = 0; // 0 = no limit (SEC compiler now uses dynamic memory) + + // Parse arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + output_file = argv[++i]; + } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { + module_name = argv[++i]; + } else if (strcmp(argv[i], "-t") == 0 && i + 1 < argc) { + target_track = atoi(argv[++i]); + } else if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) { + target_channel = atoi(argv[++i]); + } else if (strcmp(argv[i], "-v") == 0 && i + 1 < argc) { + default_volume = atoi(argv[++i]) & 0x0F; + } else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) { + time_scale = atof(argv[++i]); + } else if (strcmp(argv[i], "-m") == 0 && i + 1 < argc) { + max_notes = atoi(argv[++i]); + } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return 0; + } else if (argv[i][0] != '-') { + input_file = argv[i]; + } else { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + print_usage(argv[0]); + return 1; + } + } + + if (!input_file) { + print_usage(argv[0]); + return 1; + } + + // Open input file + FILE* f = fopen(input_file, "rb"); + if (!f) { + fprintf(stderr, "Error: Cannot open %s\n", input_file); + return 1; + } + + // Read MIDI header + char header[4]; + fread(header, 1, 4, f); + if (memcmp(header, "MThd", 4) != 0) { + fprintf(stderr, "Error: Not a valid MIDI file\n"); + fclose(f); + return 1; + } + + uint32_t header_len = read_u32_be(f); + uint16_t midi_format = read_u16_be(f); + uint16_t num_tracks = read_u16_be(f); + uint16_t ticks_per_beat = read_u16_be(f); + + (void)header_len; + (void)midi_format; + + fprintf(stderr, "MIDI: format=%d, tracks=%d, ticks/beat=%d\n", midi_format, num_tracks, + ticks_per_beat); + + // Handle SMPTE timing (not supported) + if (ticks_per_beat & 0x8000) { + fprintf(stderr, "Error: SMPTE timing not supported\n"); + fclose(f); + return 1; + } + + MusicSequence seq = {.count = 0}; + + // Read tracks + int track_num = 0; + while (!feof(f)) { + char chunk_type[4]; + if (fread(chunk_type, 1, 4, f) != 4) break; + + uint32_t chunk_len = read_u32_be(f); + + if (memcmp(chunk_type, "MTrk", 4) == 0) { + if (track_num == target_track) { + fprintf(stderr, "Processing track %d (%u bytes)...\n", track_num, chunk_len); + parse_midi_track(f, chunk_len, &seq, target_channel, ticks_per_beat, default_volume, + time_scale); + } else { + fseek(f, chunk_len, SEEK_CUR); + } + track_num++; + } else { + fseek(f, chunk_len, SEEK_CUR); + } + } + + fclose(f); + + if (seq.count == 0) { + fprintf(stderr, "Warning: No notes extracted!\n"); + fprintf(stderr, "Try a different track (-t) or channel (-c)\n"); + return 1; + } + + // Apply max notes limit (if specified) + if (max_notes > 0 && seq.count > max_notes) { + fprintf(stderr, "Limiting from %d to %d notes\n", seq.count, max_notes); + seq.count = max_notes; + } + + fprintf(stderr, "Extracted %d notes\n", seq.count); + + // Output + FILE* out = stdout; + if (output_file) { + out = fopen(output_file, "w"); + if (!out) { + fprintf(stderr, "Error: Cannot create %s\n", output_file); + return 1; + } + } + + // Note name table for comments + const char* note_names[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; + + // S-expression module format + fprintf(out, "; %s - music module generated by midi2music\n", module_name); + fprintf(out, "; Source: %s (track %d", input_file, target_track); + if (target_channel >= 0) fprintf(out, ", channel %d", target_channel); + fprintf(out, ")\n"); + fprintf(out, "; %d notes, time scale %.2f\n", seq.count, time_scale); + fprintf(out, + "; Format: note (0=rest, 1-36=C3-B5), volume (0-15), duration (frames), reserved\n"); + fprintf(out, ";\n"); + fprintf(out, "; Usage:\n"); + fprintf(out, "; (require (%s))\n", module_name); + fprintf(out, "; (apu/music-play (hi %s/data) (lo %s/data) %s/length 1)\n\n", module_name, + module_name, module_name); + + fprintf(out, "(ns %s)\n\n", module_name); + fprintf(out, "(def length %d)\n", seq.count); + fprintf(out, "(def length-hi %d)\n", (seq.count >> 8) & 0xFF); + fprintf(out, "(def length-lo %d)\n\n", seq.count & 0xFF); + fprintf(out, "(data data\n"); + + for (int i = 0; i < seq.count; i++) { + MusicNote* n = &seq.notes[i]; + if (n->note == 0) { + fprintf(out, " (db 0 0 %d 0)", n->duration); + fprintf(out, " ; rest\n"); + } else { + int octave = 3 + (n->note - 1) / 12; + int note_in_octave = (n->note - 1) % 12; + fprintf(out, " (db %d %d %d 0)", n->note, n->volume, n->duration); + fprintf(out, " ; %s%d\n", note_names[note_in_octave], octave); + } + } + + fprintf(out, ")\n"); + + if (output_file) { + fclose(out); + fprintf(stderr, "Output written to %s\n", output_file); + } + + return 0; +} diff --git a/vm/apu.c b/vm/apu.c index e7cf897..f0717b7 100644 --- a/vm/apu.c +++ b/vm/apu.c @@ -24,6 +24,13 @@ static const float env_rate_seconds[] = { 0.15f, 0.20f, 0.30f, 0.40f, 0.60f, 0.80f, 1.00f, 1.50f, }; +static inline void apu_lock(Tiny16APU* apu) { + while (__sync_lock_test_and_set(&apu->lock, 1)) { + } +} + +static inline void apu_unlock(Tiny16APU* apu) { __sync_lock_release(&apu->lock); } + static inline uint32_t calc_phase_inc(uint16_t freq_value) { // freq_hz = 44100 / (2048 - freq_value) // phase_inc = freq_hz / sample_rate * 2^32 @@ -169,6 +176,7 @@ static inline void sweep_step(Tiny16APUPulseChannel* ch) { } void tiny16_apu_reset(Tiny16APU* apu) { + apu->lock = 0; apu->enabled = false; apu->master_volume = 0; apu->sample_accum = 0; @@ -260,9 +268,371 @@ void tiny16_apu_reset(Tiny16APU* apu) { apu->wave.length_left = 0; for (int i = 0; i < 32; i++) apu->wave.wave[i] = 8; + + // SFX system reset + apu->sfx.table_addr = 0; + apu->sfx.count = 0; + for (int i = 0; i < 4; i++) { + apu->sfx.ch[i].active = false; + apu->sfx.ch[i].duration_left = 0; + apu->sfx.ch[i].sfx_id = 0; + } + + // Music system reset + apu->music.enabled = false; + apu->music.looping = false; + apu->music.song_addr = 0; + apu->music.song_length = 0; + apu->music.current_note = 0; + apu->music.frames_left = 0; + apu->music.channel = 1; // default to pulse2 + + apu->frame_samples = 0; + apu->memory = NULL; +} + +// ============================================================================= +// SFX System Implementation +// ============================================================================= + +// SFX entry format (9 bytes): +// [0] channel (0-3: pulse1/pulse2/tri/noise) +// [1] freq_lo +// [2] freq_hi +// [3] period (noise only) +// [4] volume +// [5] duty (pulse only) +// [6] env_ad (attack << 4 | decay) +// [7] env_sr (sustain << 4 | release) +// [8] duration (frames) + +static void tiny16_apu_play_sfx(Tiny16APU* apu, uint8_t sfx_id) { + if (!apu->memory || sfx_id >= apu->sfx.count) return; + + Tiny16Memory* mem = (Tiny16Memory*)apu->memory; + uint16_t addr = apu->sfx.table_addr + (uint16_t)sfx_id * TINY16_APU_SFX_ENTRY_SIZE; + + // Read SFX entry from memory + uint8_t channel = mem->bytes[addr + 0] & 0x03; + uint8_t freq_lo = mem->bytes[addr + 1]; + uint8_t freq_hi = mem->bytes[addr + 2]; + uint8_t period = mem->bytes[addr + 3]; + uint8_t volume = mem->bytes[addr + 4] & 0x0F; + uint8_t duty = mem->bytes[addr + 5] & 0x03; + uint8_t env_ad = mem->bytes[addr + 6]; + uint8_t env_sr = mem->bytes[addr + 7]; + uint8_t duration = mem->bytes[addr + 8]; + + uint16_t freq = ((uint16_t)(freq_hi & 0x07) << 8) | freq_lo; + + // Set up channel based on type + switch (channel) { + case 0: // Pulse 1 + apu->pulse1.freq = freq; + apu->pulse1.phase_inc = calc_phase_inc(freq); + apu->pulse1.volume = volume; + apu->pulse1.duty = duty; + apu->pulse1.env.attack = (env_ad >> 4) & 0x0F; + apu->pulse1.env.decay = env_ad & 0x0F; + apu->pulse1.env.sustain = (env_sr >> 4) & 0x0F; + apu->pulse1.env.release = env_sr & 0x0F; + apu->pulse1.length = duration; + apu->pulse1.length_left = length_samples(duration); + apu->pulse1.phase = 0; + apu->pulse1.enabled = true; + env_start(&apu->pulse1.env); + sweep_init(&apu->pulse1); + break; + + case 1: // Pulse 2 + apu->pulse2.freq = freq; + apu->pulse2.phase_inc = calc_phase_inc(freq); + apu->pulse2.volume = volume; + apu->pulse2.duty = duty; + apu->pulse2.env.attack = (env_ad >> 4) & 0x0F; + apu->pulse2.env.decay = env_ad & 0x0F; + apu->pulse2.env.sustain = (env_sr >> 4) & 0x0F; + apu->pulse2.env.release = env_sr & 0x0F; + apu->pulse2.length = duration; + apu->pulse2.length_left = length_samples(duration); + apu->pulse2.phase = 0; + apu->pulse2.enabled = true; + env_start(&apu->pulse2.env); + sweep_init(&apu->pulse2); + break; + + case 2: // Triangle + apu->triangle.freq = freq; + apu->triangle.phase_inc = calc_phase_inc(freq); + apu->triangle.volume = volume; + apu->triangle.env.attack = (env_ad >> 4) & 0x0F; + apu->triangle.env.decay = env_ad & 0x0F; + apu->triangle.env.sustain = (env_sr >> 4) & 0x0F; + apu->triangle.env.release = env_sr & 0x0F; + apu->triangle.length = duration; + apu->triangle.length_left = length_samples(duration); + apu->triangle.phase = 0; + apu->triangle.enabled = true; + env_start(&apu->triangle.env); + break; + + case 3: // Noise + apu->noise.period = period & 0x0F; + apu->noise.volume = volume; + apu->noise.env.attack = (env_ad >> 4) & 0x0F; + apu->noise.env.decay = env_ad & 0x0F; + apu->noise.env.sustain = (env_sr >> 4) & 0x0F; + apu->noise.env.release = env_sr & 0x0F; + apu->noise.length = duration; + apu->noise.length_left = length_samples(duration); + apu->noise.lfsr = LFSR_SEED; + apu->noise.timer = 0; + apu->noise.enabled = true; + env_start(&apu->noise.env); + break; + } + + // Update SFX state + apu->sfx.ch[channel].active = true; + apu->sfx.ch[channel].duration_left = duration; + apu->sfx.ch[channel].sfx_id = sfx_id; +} + +static void tiny16_apu_stop_sfx(Tiny16APU* apu, uint8_t channel) { + if (channel > 3) return; + + apu->sfx.ch[channel].active = false; + apu->sfx.ch[channel].duration_left = 0; + + // Release the channel + switch (channel) { + case 0: + apu->pulse1.enabled = false; + env_release(&apu->pulse1.env); + break; + case 1: + apu->pulse2.enabled = false; + env_release(&apu->pulse2.env); + break; + case 2: + apu->triangle.enabled = false; + env_release(&apu->triangle.env); + break; + case 3: + apu->noise.enabled = false; + env_release(&apu->noise.env); + break; + } +} + +static uint8_t tiny16_apu_get_sfx_status(Tiny16APU* apu) { + uint8_t status = 0; + for (int i = 0; i < 4; i++) { + if (apu->sfx.ch[i].active) status |= (1 << i); + } + return status; +} + +// Called once per frame to update SFX durations +static void tiny16_apu_update_sfx(Tiny16APU* apu) { + for (int i = 0; i < 4; i++) { + if (apu->sfx.ch[i].active && apu->sfx.ch[i].duration_left > 0) { + apu->sfx.ch[i].duration_left--; + if (apu->sfx.ch[i].duration_left == 0) { + apu->sfx.ch[i].active = false; + } + } + } +} + +// ============================================================================= +// Music Sequencer Implementation +// ============================================================================= + +// Music note format (4 bytes): +// [0] note (0=rest, 1-96=notes C1-B8, using MIDI-like indexing) +// [1] volume (4-bit) +// [2] duration (frames) +// [3] reserved (for future use: instrument, effects, etc.) + +// Note frequency table (C3 to B5, 3 octaves = 36 notes) +// Frequencies are pre-calculated for the APU's freq_value format +static const uint16_t music_note_freq[] = { + // Octave 3: C3, C#3, D3, D#3, E3, F3, F#3, G3, G#3, A3, A#3, B3 + 1711, + 1730, + 1748, + 1765, + 1780, + 1795, + 1810, + 1823, + 1836, + 1848, + 1859, + 1869, + // Octave 4: C4, C#4, D4, D#4, E4, F4, F#4, G4, G#4, A4, A#4, B4 + 1879, + 1889, + 1898, + 1906, + 1914, + 1922, + 1929, + 1935, + 1942, + 1948, + 1953, + 1959, + // Octave 5: C5, C#5, D5, D#5, E5, F5, F#5, G5, G#5, A5, A#5, B5 + 1964, + 1968, + 1973, + 1977, + 1981, + 1985, + 1988, + 1992, + 1995, + 1998, + 2001, + 2003, +}; +#define MUSIC_NOTE_TABLE_SIZE (sizeof(music_note_freq) / sizeof(music_note_freq[0])) + +static void tiny16_apu_play_music_note(Tiny16APU* apu, uint8_t note, uint8_t volume) { + if (note == 0) { + // Rest - silence the music channel + if (apu->music.channel == 1) { + apu->pulse2.enabled = false; + env_release(&apu->pulse2.env); + } + return; + } + + // Convert note index (1-based) to frequency table index (0-based) + uint8_t note_idx = note - 1; + if (note_idx >= MUSIC_NOTE_TABLE_SIZE) return; + + uint16_t freq = music_note_freq[note_idx]; + + // Play on the music channel (default: pulse2) + // Envelope: instant attack, no decay, sustain at requested volume, quick release + uint8_t vol = volume & 0x0F; + + switch (apu->music.channel) { + case 0: // Pulse 1 + apu->pulse1.freq = freq; + apu->pulse1.phase_inc = calc_phase_inc(freq); + apu->pulse1.volume = vol; + apu->pulse1.duty = TINY16_APU_DUTY_25; + apu->pulse1.env.attack = 0; + apu->pulse1.env.decay = 0; + apu->pulse1.env.sustain = vol; + apu->pulse1.env.release = 1; + apu->pulse1.length = 0; + apu->pulse1.length_left = 0; + apu->pulse1.phase = 0; + apu->pulse1.enabled = true; + env_start(&apu->pulse1.env); + break; + + case 1: // Pulse 2 (default) + apu->pulse2.freq = freq; + apu->pulse2.phase_inc = calc_phase_inc(freq); + apu->pulse2.volume = vol; + apu->pulse2.duty = TINY16_APU_DUTY_25; + apu->pulse2.env.attack = 0; + apu->pulse2.env.decay = 0; + apu->pulse2.env.sustain = vol; + apu->pulse2.env.release = 1; + apu->pulse2.length = 0; + apu->pulse2.length_left = 0; + apu->pulse2.phase = 0; + apu->pulse2.enabled = true; + env_start(&apu->pulse2.env); + break; + + case 2: // Triangle + apu->triangle.freq = freq; + apu->triangle.phase_inc = calc_phase_inc(freq); + apu->triangle.volume = vol; + apu->triangle.env.attack = 0; + apu->triangle.env.decay = 0; + apu->triangle.env.sustain = vol; + apu->triangle.env.release = 1; + apu->triangle.length = 0; + apu->triangle.length_left = 0; + apu->triangle.phase = 0; + apu->triangle.enabled = true; + env_start(&apu->triangle.env); + break; + } +} + +static void tiny16_apu_music_start(Tiny16APU* apu) { + apu->music.enabled = true; + apu->music.current_note = 0; + apu->music.frames_left = 0; +} + +static void tiny16_apu_music_stop(Tiny16APU* apu) { + apu->music.enabled = false; + + // Release the music channel + switch (apu->music.channel) { + case 0: + apu->pulse1.enabled = false; + env_release(&apu->pulse1.env); + break; + case 1: + apu->pulse2.enabled = false; + env_release(&apu->pulse2.env); + break; + case 2: + apu->triangle.enabled = false; + env_release(&apu->triangle.env); + break; + } +} + +// Called once per frame to update music sequencer +static void tiny16_apu_update_music(Tiny16APU* apu) { + if (!apu->music.enabled || !apu->memory) return; + if (apu->music.song_length == 0) return; + + Tiny16Memory* mem = (Tiny16Memory*)apu->memory; + + // Check if it's time for the next note + if (apu->music.frames_left > 0) { + apu->music.frames_left--; + return; + } + + // Read current note from memory + uint16_t addr = apu->music.song_addr + (apu->music.current_note * TINY16_APU_MUSIC_NOTE_SIZE); + uint8_t note = mem->bytes[addr + 0]; + uint8_t volume = mem->bytes[addr + 1]; + uint8_t duration = mem->bytes[addr + 2]; + // byte 3 is reserved + + // Play the note + tiny16_apu_play_music_note(apu, note, volume); + apu->music.frames_left = duration; + + // Advance to next note + apu->music.current_note++; + if (apu->music.current_note >= apu->music.song_length) { + if (apu->music.looping) { + apu->music.current_note = 0; + } else { + tiny16_apu_music_stop(apu); + } + } } void tiny16_apu_mmio_write(Tiny16APU* apu, uint16_t addr, uint8_t value) { + apu_lock(apu); switch (addr) { // Master control case TINY16_MMIO_APU_CTRL: @@ -505,16 +875,55 @@ void tiny16_apu_mmio_write(Tiny16APU* apu, uint16_t addr, uint8_t value) { apu->wave.env.sustain = (value >> 4) & 0x0F; apu->wave.env.release = value & 0x0F; break; + + // SFX System + case TINY16_MMIO_APU_SFX_PLAY: tiny16_apu_play_sfx(apu, value); break; + case TINY16_MMIO_APU_SFX_STOP: tiny16_apu_stop_sfx(apu, value); break; + case TINY16_MMIO_APU_SFX_TABLE_HI: + apu->sfx.table_addr = (apu->sfx.table_addr & 0x00FF) | ((uint16_t)value << 8); + break; + case TINY16_MMIO_APU_SFX_TABLE_LO: + apu->sfx.table_addr = (apu->sfx.table_addr & 0xFF00) | value; + break; + case TINY16_MMIO_APU_SFX_COUNT: apu->sfx.count = value; break; + + // Music System + case TINY16_MMIO_APU_MUSIC_CTRL: + if (value & 0x01) { + apu->music.looping = (value & 0x04) != 0; + tiny16_apu_music_start(apu); + } + if (value & 0x02) { + tiny16_apu_music_stop(apu); + } + break; + case TINY16_MMIO_APU_MUSIC_ADDR_HI: + apu->music.song_addr = (apu->music.song_addr & 0x00FF) | ((uint16_t)value << 8); + break; + case TINY16_MMIO_APU_MUSIC_ADDR_LO: + apu->music.song_addr = (apu->music.song_addr & 0xFF00) | value; + break; + case TINY16_MMIO_APU_MUSIC_LEN_HI: + apu->music.song_length = (apu->music.song_length & 0x00FF) | ((uint16_t)value << 8); + break; + case TINY16_MMIO_APU_MUSIC_LEN_LO: + apu->music.song_length = (apu->music.song_length & 0xFF00) | value; + break; } if (addr >= TINY16_MMIO_APU_WAVE_RAM && addr <= (TINY16_MMIO_APU_WAVE_RAM + 0x1F)) { apu->wave.wave[addr - TINY16_MMIO_APU_WAVE_RAM] = value & 0x0F; } + apu_unlock(apu); } uint8_t tiny16_apu_mmio_read(Tiny16APU* apu, uint16_t addr) { + uint8_t value = 0; + apu_lock(apu); switch (addr) { - case TINY16_MMIO_APU_CTRL: return (apu->master_volume << 4) | (apu->enabled ? 0x01 : 0x00); + case TINY16_MMIO_APU_CTRL: + value = (apu->master_volume << 4) | (apu->enabled ? 0x01 : 0x00); + break; case TINY16_MMIO_APU_STATUS: { uint8_t status = 0; @@ -523,75 +932,123 @@ uint8_t tiny16_apu_mmio_read(Tiny16APU* apu, uint16_t addr) { if (apu->triangle.enabled) status |= 0x04; if (apu->noise.enabled) status |= 0x08; if (apu->wave.enabled) status |= 0x10; - return status; + value = status; + break; } - case TINY16_MMIO_APU_CH0_VOL: return (apu->pulse1.duty << 4) | apu->pulse1.volume; - case TINY16_MMIO_APU_CH0_CTRL: return apu->pulse1.enabled ? 0x01 : 0x00; + case TINY16_MMIO_APU_CH0_VOL: value = (apu->pulse1.duty << 4) | apu->pulse1.volume; break; + case TINY16_MMIO_APU_CH0_CTRL: value = apu->pulse1.enabled ? 0x01 : 0x00; break; - case TINY16_MMIO_APU_CH1_VOL: return (apu->pulse2.duty << 4) | apu->pulse2.volume; - case TINY16_MMIO_APU_CH1_CTRL: return apu->pulse2.enabled ? 0x01 : 0x00; + case TINY16_MMIO_APU_CH1_VOL: value = (apu->pulse2.duty << 4) | apu->pulse2.volume; break; + case TINY16_MMIO_APU_CH1_CTRL: value = apu->pulse2.enabled ? 0x01 : 0x00; break; - case TINY16_MMIO_APU_CH2_VOL: return apu->triangle.volume; - case TINY16_MMIO_APU_CH2_CTRL: return apu->triangle.enabled ? 0x01 : 0x00; + case TINY16_MMIO_APU_CH2_VOL: value = apu->triangle.volume; break; + case TINY16_MMIO_APU_CH2_CTRL: value = apu->triangle.enabled ? 0x01 : 0x00; break; - case TINY16_MMIO_APU_CH3_PERIOD: return apu->noise.period; - case TINY16_MMIO_APU_CH3_VOL: return apu->noise.volume; + case TINY16_MMIO_APU_CH3_PERIOD: value = apu->noise.period; break; + case TINY16_MMIO_APU_CH3_VOL: value = apu->noise.volume; break; case TINY16_MMIO_APU_CH3_CTRL: - return (apu->noise.short_mode ? 0x04 : 0x00) | (apu->noise.enabled ? 0x01 : 0x00); + value = (apu->noise.short_mode ? 0x04 : 0x00) | (apu->noise.enabled ? 0x01 : 0x00); + break; - case TINY16_MMIO_APU_CH0_ENV_AD: return (apu->pulse1.env.attack << 4) | apu->pulse1.env.decay; + case TINY16_MMIO_APU_CH0_ENV_AD: + value = (apu->pulse1.env.attack << 4) | apu->pulse1.env.decay; + break; case TINY16_MMIO_APU_CH0_ENV_SR: - return (apu->pulse1.env.sustain << 4) | apu->pulse1.env.release; - case TINY16_MMIO_APU_CH1_ENV_AD: return (apu->pulse2.env.attack << 4) | apu->pulse2.env.decay; + value = (apu->pulse1.env.sustain << 4) | apu->pulse1.env.release; + break; + case TINY16_MMIO_APU_CH1_ENV_AD: + value = (apu->pulse2.env.attack << 4) | apu->pulse2.env.decay; + break; case TINY16_MMIO_APU_CH1_ENV_SR: - return (apu->pulse2.env.sustain << 4) | apu->pulse2.env.release; + value = (apu->pulse2.env.sustain << 4) | apu->pulse2.env.release; + break; case TINY16_MMIO_APU_CH2_ENV_AD: - return (apu->triangle.env.attack << 4) | apu->triangle.env.decay; + value = (apu->triangle.env.attack << 4) | apu->triangle.env.decay; + break; case TINY16_MMIO_APU_CH2_ENV_SR: - return (apu->triangle.env.sustain << 4) | apu->triangle.env.release; - case TINY16_MMIO_APU_CH3_ENV_AD: return (apu->noise.env.attack << 4) | apu->noise.env.decay; - case TINY16_MMIO_APU_CH3_ENV_SR: return (apu->noise.env.sustain << 4) | apu->noise.env.release; + value = (apu->triangle.env.sustain << 4) | apu->triangle.env.release; + break; + case TINY16_MMIO_APU_CH3_ENV_AD: + value = (apu->noise.env.attack << 4) | apu->noise.env.decay; + break; + case TINY16_MMIO_APU_CH3_ENV_SR: + value = (apu->noise.env.sustain << 4) | apu->noise.env.release; + break; case TINY16_MMIO_APU_CH0_SWEEP: - return (apu->pulse1.sweep_rate << 4) | (apu->pulse1.sweep_down ? 0x08 : 0x00) | - (apu->pulse1.sweep_shift & 0x07); + value = (apu->pulse1.sweep_rate << 4) | (apu->pulse1.sweep_down ? 0x08 : 0x00) | + (apu->pulse1.sweep_shift & 0x07); + break; case TINY16_MMIO_APU_CH1_SWEEP: - return (apu->pulse2.sweep_rate << 4) | (apu->pulse2.sweep_down ? 0x08 : 0x00) | - (apu->pulse2.sweep_shift & 0x07); - case TINY16_MMIO_APU_CH0_LEN: return apu->pulse1.length; - case TINY16_MMIO_APU_CH1_LEN: return apu->pulse2.length; - case TINY16_MMIO_APU_CH2_LEN: return apu->triangle.length; - case TINY16_MMIO_APU_CH3_LEN: return apu->noise.length; - - case TINY16_MMIO_APU_WAVE_FREQ_LO: return apu->wave.freq & 0xFF; - case TINY16_MMIO_APU_WAVE_FREQ_HI: return (apu->wave.freq >> 8) & 0x07; - case TINY16_MMIO_APU_WAVE_VOL: return apu->wave.volume; - case TINY16_MMIO_APU_WAVE_CTRL: return apu->wave.enabled ? 0x01 : 0x00; - case TINY16_MMIO_APU_WAVE_LEN: return apu->wave.length; - case TINY16_MMIO_APU_WAVE_ENV_AD: return (apu->wave.env.attack << 4) | apu->wave.env.decay; - case TINY16_MMIO_APU_WAVE_ENV_SR: return (apu->wave.env.sustain << 4) | apu->wave.env.release; + value = (apu->pulse2.sweep_rate << 4) | (apu->pulse2.sweep_down ? 0x08 : 0x00) | + (apu->pulse2.sweep_shift & 0x07); + break; + case TINY16_MMIO_APU_CH0_LEN: value = apu->pulse1.length; break; + case TINY16_MMIO_APU_CH1_LEN: value = apu->pulse2.length; break; + case TINY16_MMIO_APU_CH2_LEN: value = apu->triangle.length; break; + case TINY16_MMIO_APU_CH3_LEN: value = apu->noise.length; break; + + case TINY16_MMIO_APU_WAVE_FREQ_LO: value = apu->wave.freq & 0xFF; break; + case TINY16_MMIO_APU_WAVE_FREQ_HI: value = (apu->wave.freq >> 8) & 0x07; break; + case TINY16_MMIO_APU_WAVE_VOL: value = apu->wave.volume; break; + case TINY16_MMIO_APU_WAVE_CTRL: value = apu->wave.enabled ? 0x01 : 0x00; break; + case TINY16_MMIO_APU_WAVE_LEN: value = apu->wave.length; break; + case TINY16_MMIO_APU_WAVE_ENV_AD: + value = (apu->wave.env.attack << 4) | apu->wave.env.decay; + break; + case TINY16_MMIO_APU_WAVE_ENV_SR: + value = (apu->wave.env.sustain << 4) | apu->wave.env.release; + break; + + // SFX System + case TINY16_MMIO_APU_SFX_STATUS: value = tiny16_apu_get_sfx_status(apu); break; + case TINY16_MMIO_APU_SFX_TABLE_HI: value = (apu->sfx.table_addr >> 8) & 0xFF; break; + case TINY16_MMIO_APU_SFX_TABLE_LO: value = apu->sfx.table_addr & 0xFF; break; + case TINY16_MMIO_APU_SFX_COUNT: value = apu->sfx.count; break; + + // Music System + case TINY16_MMIO_APU_MUSIC_STATUS: value = apu->music.enabled ? 0x01 : 0x00; break; + case TINY16_MMIO_APU_MUSIC_ADDR_HI: value = (apu->music.song_addr >> 8) & 0xFF; break; + case TINY16_MMIO_APU_MUSIC_ADDR_LO: value = apu->music.song_addr & 0xFF; break; + case TINY16_MMIO_APU_MUSIC_LEN_HI: value = (apu->music.song_length >> 8) & 0xFF; break; + case TINY16_MMIO_APU_MUSIC_LEN_LO: value = apu->music.song_length & 0xFF; break; } if (addr >= TINY16_MMIO_APU_WAVE_RAM && addr <= (TINY16_MMIO_APU_WAVE_RAM + 0x1F)) { - return apu->wave.wave[addr - TINY16_MMIO_APU_WAVE_RAM] & 0x0F; + value = apu->wave.wave[addr - TINY16_MMIO_APU_WAVE_RAM] & 0x0F; } - return 0; + apu_unlock(apu); + return value; } uint32_t tiny16_apu_samples_for_cycles(Tiny16APU* apu, uint32_t cpu_cycles, uint32_t cpu_hz) { - if (cpu_hz == 0) return 0; + apu_lock(apu); + if (cpu_hz == 0) { + apu_unlock(apu); + return 0; + } uint64_t add = (uint64_t)cpu_cycles * (uint64_t)TINY16_APU_SAMPLE_RATE; uint64_t accum = apu->sample_accum + add; uint32_t frames = (uint32_t)(accum / cpu_hz); apu->sample_accum = accum - (uint64_t)frames * cpu_hz; + apu_unlock(apu); return frames; } void tiny16_apu_generate_samples(Tiny16APU* apu, float* buffer, unsigned int frames) { + apu_lock(apu); for (unsigned int i = 0; i < frames; i++) { float mix = 0.0f; int active_channels = 0; + // Update SFX/music once per frame (every samples_per_frame samples) + apu->frame_samples++; + if (apu->frame_samples >= samples_per_frame) { + apu->frame_samples = 0; + tiny16_apu_update_sfx(apu); + tiny16_apu_update_music(apu); + } + if (!apu->enabled) { buffer[i] = 0.0f; continue; @@ -714,4 +1171,5 @@ void tiny16_apu_generate_samples(Tiny16APU* apu, float* buffer, unsigned int fra buffer[i] = mix; } + apu_unlock(apu); } diff --git a/vm/apu.h b/vm/apu.h index 0ef0c2b..fdf490d 100644 --- a/vm/apu.h +++ b/vm/apu.h @@ -3,8 +3,13 @@ #include #include -#define TINY16_APU_SAMPLE_RATE 44100 -#define TINY16_APU_CHANNELS 5 +#define TINY16_APU_SAMPLE_RATE 44100 +#define TINY16_APU_CHANNELS 5 +#define TINY16_APU_SFX_ENTRY_SIZE 9 // bytes per SFX entry +#define TINY16_APU_MUSIC_NOTE_SIZE 4 // bytes per music note + +// Forward declaration for memory access +struct Tiny16Memory; typedef enum { TINY16_APU_CH_PULSE1 = 0, @@ -93,7 +98,33 @@ typedef struct { uint8_t wave[32]; // 4-bit samples } Tiny16APUWaveChannel; +// SFX player state (per channel) +typedef struct { + bool active; // SFX currently playing on this channel + uint8_t duration_left; // frames remaining + uint8_t sfx_id; // current SFX ID playing +} Tiny16APUSfxState; + +// SFX system state +typedef struct { + uint16_t table_addr; // memory address of SFX table + uint8_t count; // number of SFX entries + Tiny16APUSfxState ch[4]; // per-channel SFX state (pulse1/pulse2/tri/noise) +} Tiny16APUSfxSystem; + +// Music player state typedef struct { + bool enabled; // music is playing + bool looping; // loop when song ends + uint16_t song_addr; // memory address of song data + uint16_t song_length; // number of notes in song + uint16_t current_note; // current note index + uint8_t frames_left; // frames until next note + uint8_t channel; // channel reserved for music (default: pulse2) +} Tiny16MusicPlayer; + +typedef struct { + volatile int lock; bool enabled; // APU master enable uint8_t master_volume; // 4-bit master volume (0-15) uint64_t sample_accum; // fractional samples in cpu_hz units @@ -103,6 +134,14 @@ typedef struct { Tiny16APUTriangleChannel triangle; // CH2 Tiny16APUNoiseChannel noise; // CH3 Tiny16APUWaveChannel wave; // CH4 + + // SFX and Music systems + Tiny16APUSfxSystem sfx; // SFX bank system + Tiny16MusicPlayer music; // Music sequencer + uint32_t frame_samples; // samples per frame counter + + // Memory pointer for reading SFX/music data + struct Tiny16Memory* memory; } Tiny16APU; void tiny16_apu_reset(Tiny16APU* apu); diff --git a/vm/memory.h b/vm/memory.h index d772d8a..9dcb5bd 100644 --- a/vm/memory.h +++ b/vm/memory.h @@ -126,6 +126,22 @@ typedef struct { // Wave RAM (32 x 4-bit samples) #define TINY16_MMIO_APU_WAVE_RAM 0xBF70 // 0xBF70 - 0xBF8F +// APU SFX System (0xBF90-0xBF9F) +#define TINY16_MMIO_APU_SFX_PLAY 0xBF90 // W: trigger SFX by ID +#define TINY16_MMIO_APU_SFX_STOP 0xBF91 // W: stop SFX on channel +#define TINY16_MMIO_APU_SFX_STATUS 0xBF92 // R: active SFX channels (bits 0-3) +#define TINY16_MMIO_APU_SFX_TABLE_HI 0xBF93 // W: SFX table address high byte +#define TINY16_MMIO_APU_SFX_TABLE_LO 0xBF94 // W: SFX table address low byte +#define TINY16_MMIO_APU_SFX_COUNT 0xBF95 // W: number of SFX entries + +// APU Music System (0xBFA0-0xBFAF) +#define TINY16_MMIO_APU_MUSIC_CTRL 0xBFA0 // W: bit0=play, bit1=stop, bit2=loop +#define TINY16_MMIO_APU_MUSIC_STATUS 0xBFA1 // R: bit0=playing +#define TINY16_MMIO_APU_MUSIC_ADDR_HI 0xBFA2 // W: song data address high byte +#define TINY16_MMIO_APU_MUSIC_ADDR_LO 0xBFA3 // W: song data address low byte +#define TINY16_MMIO_APU_MUSIC_LEN_HI 0xBFA4 // W: song length high byte +#define TINY16_MMIO_APU_MUSIC_LEN_LO 0xBFA5 // W: song length low byte + // RESERVED (0xBFF0 - 0xBFFF) // diff --git a/vm/vm.c b/vm/vm.c index 3e70e98..e063d78 100644 --- a/vm/vm.c +++ b/vm/vm.c @@ -14,6 +14,7 @@ Tiny16VM* tiny16_vm_create(void) { tiny16_cpu_reset(&vm->cpu); tiny16_ppu_reset(&vm->ppu); tiny16_apu_reset(&vm->apu); + vm->apu.memory = (struct Tiny16Memory*)&vm->memory; vm->ticks = 0; return vm; } @@ -23,6 +24,7 @@ void tiny16_vm_reset(Tiny16VM* vm) { tiny16_memory_reset(&vm->memory); tiny16_ppu_reset(&vm->ppu); tiny16_apu_reset(&vm->apu); + vm->apu.memory = (struct Tiny16Memory*)&vm->memory; vm->ticks = 0; } @@ -39,7 +41,8 @@ static inline bool tiny16_is_ppu_mmio(uint16_t addr) { } static inline bool tiny16_is_apu_mmio(uint16_t addr) { - return addr >= TINY16_MMIO_APU_CTRL && addr <= (TINY16_MMIO_APU_WAVE_RAM + 0x1F); + // APU range: 0xBF40-0xBF8F (base), 0xBF90-0xBF9F (SFX), 0xBFA0-0xBFA5 (music) + return addr >= TINY16_MMIO_APU_CTRL && addr <= TINY16_MMIO_APU_MUSIC_LEN_LO; } uint8_t tiny16_vm_mem_read(void* ctx, uint16_t addr) {