diff --git a/cartridge/Makefile b/cartridge/Makefile index 692c0d0..9d67e98 100644 --- a/cartridge/Makefile +++ b/cartridge/Makefile @@ -8,7 +8,7 @@ CARCH = -m2 -mb ### general flags ### -OPT ?= -O3 +OPT ?= -O2 DEBUG = -g -gdwarf-4 @@ -63,6 +63,12 @@ OBJDUMP = $(TARGET)objdump --rename-section .data=.rom.$(basename $@) \ $< $@ +%.adpcm.o: %.adpcm + $(OBJCOPY) \ + -I binary -O elf32-sh -B sh2 \ + --rename-section .data=.rom.$(basename $@) \ + $< $@ + ### object files ### SYS_IP_OBJ += $(LIB)/ip/sys_id.o @@ -83,6 +89,10 @@ CARTRIDGE_OBJ = main.o wc3.pcm.o cartridge.elf: LDSCRIPT = $(LIB)/cartridge.lds cartridge.elf: $(SYS_IP_OBJ) $(COMMON_OBJ) $(CARTRIDGE_OBJ) +ADPCM_OBJ = adpcm.o ecclesia.adpcm.o +adpcm.elf: LDSCRIPT = $(LIB)/cartridge.lds +adpcm.elf: $(SYS_IP_OBJ) $(COMMON_OBJ) $(ADPCM_OBJ) + .SUFFIXES: .INTERMEDIATE: .SECONDARY: diff --git a/cartridge/adpcm.cpp b/cartridge/adpcm.cpp new file mode 100644 index 0000000..19e7985 --- /dev/null +++ b/cartridge/adpcm.cpp @@ -0,0 +1,220 @@ +#include + +#include "vdp1.h" +#include "vdp2.h" +#include "scsp.h" +#include "smpc.h" + +#include "../common/copy.hpp" + +extern void * _ecclesia_adpcm_start __asm("_binary_ecclesia_adpcm_start"); +extern void * _ecclesia_adpcm_size __asm("_binary_ecclesia_adpcm_size"); + +// table of index changes +static const int8_t index_table[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 +}; + +// quantizer lookup table +static const int16_t step_size_table[89] = { + 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , + 16 , 17 , 19 , 21 , 23 , 25 , 28 , 31 , + 34 , 37 , 41 , 45 , 50 , 55 , 60 , 66 , + 73 , 80 , 88 , 97 , 107 , 118 , 130 , 143 , + 157 , 173 , 190 , 209 , 230 , 253 , 279 , 307 , + 337 , 371 , 408 , 449 , 494 , 544 , 598 , 658 , + 724 , 796 , 876 , 963 , 1060 , 1166 , 1282 , 1411 , + 1552 , 1707 , 1878 , 2066 , 2272 , 2499 , 2749 , 3024 , + 3327 , 3660 , 4026 , 4428 , 4871 , 5358 , 5894 , 6484 , + 7132 , 7845 , 8630 , 9493 , 10442 , 11487 , 12635 , 13899 , + 15289 , 16818 , 18500 , 20350 , 22385 , 24623 , 27086 , 29794 , + 32767 +}; + + +struct adpcm_state { + int32_t predicted_sample; + int32_t index; + int32_t step_size; +}; + +static struct adpcm_state state; + +void __attribute__ ((noinline)) init_state() +{ + state.predicted_sample = 0; + state.index = 0; + state.step_size = 7; +} + +int16_t decode_sample(int sample) +{ + int difference = 0; + if (sample & 0b100) + difference += state.step_size; + if (sample & 0b010) + difference += state.step_size >> 1; + if (sample & 0b001) + difference += state.step_size >> 2; + difference += state.step_size >> 3; + + if (sample & 0b1000) { + difference = -difference; + } + + state.predicted_sample += difference; + if (state.predicted_sample > 32767) + state.predicted_sample = 32767; + if (state.predicted_sample < -32768) + state.predicted_sample = -32768; + + state.index += index_table[sample]; + if (state.index < 0) + state.index = 0; + if (state.index > 88) + state.index = 88; + state.step_size = step_size_table[state.index]; + + return state.predicted_sample; +} + +void snd_init() +{ + while ((smpc.reg.SF & 1) != 0); + smpc.reg.SF = 1; + smpc.reg.COMREG = COMREG__SNDOFF; + while (smpc.reg.OREG[31].val != OREG31__SNDOFF); + + scsp.reg.ctrl.MIXER = MIXER__MEM4MB; + + volatile uint32_t * dsp_steps = reinterpret_cast(&(scsp.reg.dsp.STEP[0].MPRO[0])); + fill(dsp_steps, 0, (sizeof (scsp.reg.dsp.STEP))); + + for (int i = 0; i < 32; i++) { + scsp.reg.slot[i].SA = 0; + scsp.reg.slot[i].MIXER = 0; + } + + scsp.reg.ctrl.MIXER = MIXER__MEM4MB | MIXER__MVOL(0xf); +} + +void __attribute__ ((noinline)) decode_chunk(volatile uint16_t * dst, const uint8_t * src, int samples) +{ + for (int i = 0; i < samples / 2; i++) { + uint8_t b = src[i]; + int nib0 = (b >> 0) & 0xf; + int nib1 = (b >> 4) & 0xf; + dst[i * 2 + 0] = decode_sample(nib0); + dst[i * 2 + 1] = decode_sample(nib1); + } +} + +void snd_step() +{ + const uint8_t * buf = reinterpret_cast(&_ecclesia_adpcm_start); + const uint32_t size = reinterpret_cast(&_ecclesia_adpcm_size); + constexpr uint32_t chunk_samples = 16384 / 2; + decode_chunk(&scsp.ram.u16[0], buf, chunk_samples); + + scsp_slot& slot = scsp.reg.slot[0]; + // start address (bytes) + slot.SA = SA__KYONB | SA__LPCTL__NORMAL | SA__SA(0); // kx kb sbctl[1:0] ssctl[1:0] lpctl[1:0] 8b sa[19:0] + slot.LSA = 0; // loop start address (samples) + slot.LEA = chunk_samples * 2; // loop end address (samples) + //slot.EG = EG__EGHOLD; // d2r d1r ho ar krs dl rr + slot.EG = EG__AR(0x1F) | EG__D1R(0x00) | EG__D2R(0x00) | EG__RR(0x1F); + slot.FM = 0; // stwinh sdir tl mdl mdxsl mdysl + slot.PITCH = PITCH__OCT(-1) | PITCH__FNS(0); // oct fns + slot.LFO = 0; // lfof plfows + slot.MIXER = MIXER__DISDL(0b110); // disdl dipan efsdl efpan + + slot.LOOP |= LOOP__KYONEX; + + uint32_t offset = 1; + uint32_t chunk = 1; + + constexpr uint32_t timer_a_interrupt = (1 << 6); + scsp.reg.ctrl.TIMA = TIMA__TACTL(7) + | TIMA__TIMA(128); + scsp.reg.ctrl.SCIRE = timer_a_interrupt; + + while (1) { + decode_chunk(&scsp.ram.u16[chunk_samples * chunk], + &buf[(chunk_samples * offset) / 2], + chunk_samples); + chunk = !chunk; + offset = offset + 1; + if (chunk_samples * offset > size * 2) { + offset = 0; + } + + /* + uint32_t sample = 0; + const uint32_t target = chunk_samples * 2; + constexpr uint32_t sample_interval = (1 << 10); + while (sample < target) { + scsp.reg.ctrl.SCIRE = sample_interval; + while (!(scsp.reg.ctrl.SCIPD & sample_interval)); + sample++; + } + */ + while (!(scsp.reg.ctrl.SCIPD & timer_a_interrupt)); + scsp.reg.ctrl.TIMA = TIMA__TACTL(7) + | TIMA__TIMA(128); + scsp.reg.ctrl.SCIRE = timer_a_interrupt; + } +} + +extern "C" +void main() __attribute__((section(".text.main"))); +void main() +{ + // DISP: Please make sure to change this bit from 0 to 1 during V blank. + vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE + | TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320); + + // VDP2 User's Manual: + // "When sprite data is in an RGB format, sprite register 0 is selected" + // "When the value of a priority number is 0h, it is read as transparent" + // + // The power-on value of PRISA is zero. Set the priority for sprite register 0 + // to some number greater than zero, so that the color data is not interpreted + // as "transparent". + vdp2.reg.PRISA = PRISA__S0PRIN(1); // Sprite register 0 Priority Number + + /* TVM settings must be performed from the second H-blank IN interrupt after the + V-blank IN interrupt to the H-blank IN interrupt immediately after the V-blank + OUT interrupt. */ + // "normal" display resolution, 16 bits per pixel, 512x256 framebuffer + vdp1.reg.TVMR = TVMR__TVM__NORMAL; + + // swap framebuffers every 1 cycle; non-interlace + vdp1.reg.FBCR = 0; + + // erase upper-left coordinate + vdp1.reg.EWLR = EWLR__16BPP_X1(0) | EWLR__Y1(0); + + // erase lower-right coordinate + vdp1.reg.EWRR = EWRR__16BPP_X3(319) | EWRR__Y3(239); + + // during a framebuffer erase cycle, write the color "black" to each pixel + const uint16_t black = 1 << 15 | 0x00ff; + vdp1.reg.EWDR = black; + + vdp1.vram.cmd[0].CTRL = CTRL__END; + + init_state(); + snd_init(); + snd_step(); + + const uint16_t blue = 1 << 15 | 0x7f00; + vdp1.reg.EWDR = blue; + + vdp2.reg.BGON = 0; + + // start drawing (execute the command list) on every frame + vdp1.reg.PTMR = PTMR__PTM__FRAME_CHANGE; + + while (1); +} diff --git a/cartridge/adpcm.py b/cartridge/adpcm.py new file mode 100644 index 0000000..3fddbae --- /dev/null +++ b/cartridge/adpcm.py @@ -0,0 +1,179 @@ +from dataclasses import dataclass +import struct + +# Table of index changes +index_table = [ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 +] +assert len(index_table) == 16 + +# quantizer lookup table +step_size_table = [ + 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , + 16 , 17 , 19 , 21 , 23 , 25 , 28 , 31 , + 34 , 37 , 41 , 45 , 50 , 55 , 60 , 66 , + 73 , 80 , 88 , 97 , 107 , 118 , 130 , 143 , + 157 , 173 , 190 , 209 , 230 , 253 , 279 , 307 , + 337 , 371 , 408 , 449 , 494 , 544 , 598 , 658 , + 724 , 796 , 876 , 963 , 1060 , 1166 , 1282 , 1411 , + 1552 , 1707 , 1878 , 2066 , 2272 , 2499 , 2749 , 3024 , + 3327 , 3660 , 4026 , 4428 , 4871 , 5358 , 5894 , 6484 , + 7132 , 7845 , 8630 , 9493 , 10442 , 11487 , 12635 , 13899 , + 15289 , 16818 , 18500 , 20350 , 22385 , 24623 , 27086 , 29794 , + 32767 +] +assert len(step_size_table) == 89 + +@dataclass +class ADPCMState: + predicted_sample: int + index: int + step_size: int + +def encode_sample(state, sample): + assert sample >= -32768 and sample <= 32767 + difference = sample - state.predicted_sample + if difference >= 0: + new_sample = 0b0000 + else: + new_sample = 0b1000 + difference = -difference + mask = 0b100 + + temp_step_size = state.step_size + # division through repeated subtraction + for i in range(3): + if difference >= temp_step_size: + new_sample |= mask + difference -= temp_step_size + temp_step_size >>= 1 + mask >>= 1 + + difference = 0 + if new_sample & 0b100: + difference += state.step_size + if new_sample & 0b010: + difference += state.step_size >> 1 + if new_sample & 0b001: + difference += state.step_size >> 2 + difference += state.step_size >> 3 + + if new_sample & 0b1000: + difference = -difference + + state.predicted_sample += difference + if state.predicted_sample > 32767: + state.predicted_sample = 32767 + elif state.predicted_sample < -32768: + state.predicted_sample = -32768 + + state.index += index_table[new_sample] + if state.index < 0: + state.index = 0 + elif state.index > 88: + state.index = 88 + + state.step_size = step_size_table[state.index] + + return new_sample + +def sign_extend(value, bits): + sign_bit = 1 << (bits - 1) + return (value & (sign_bit - 1)) - (value & sign_bit) + +def test_encode_sample(): + sample = sign_extend(0x873f, 16) + state = ADPCMState( + predicted_sample = sign_extend(0x8700, 16), + index = 24, + step_size = 73 + ) + + new_sample = encode_sample(state, sample) + assert new_sample == 3 + assert (state.predicted_sample & 0xffff) == 0x873f, hex(state.predicted_sample) + assert state.index == 23 + assert state.step_size == step_size_table[23] + +test_encode_sample() + +def decode_sample(state, sample): + difference = 0 + if sample & 0b100: + difference += state.step_size + if sample & 0b010: + difference += state.step_size >> 1 + if sample & 0b001: + difference += state.step_size >> 2 + difference += state.step_size >> 3 + + if sample & 0b1000: + difference = -difference + + state.predicted_sample += difference + if state.predicted_sample > 32767: + state.predicted_sample = 32767 + elif state.predicted_sample < -32768: + state.predicted_sample = -32768 + + state.index += index_table[sample] + if state.index < 0: + state.index = 0 + elif state.index > 88: + state.index = 88 + state.step_size = step_size_table[state.index] + + return state.predicted_sample + +def test_decode_sample(): + sample = 3 + state = ADPCMState( + predicted_sample = sign_extend(0x8700, 16), + index = 24, + step_size = 73 + ) + + new_sample = decode_sample(state, sample) + assert new_sample == sign_extend(0x873f, 16) + assert state.index == 23 + assert state.step_size == step_size_table[23] + +test_decode_sample() + +with open('ecclesia.pcm', 'rb') as f: + pcm = f.read() + +def nibbles(buf): + state = ADPCMState( + 0, 0, 7 + ) + byte = 0 + for i in range(len(buf) // 2): + sample, = struct.unpack('>h', buf[i * 2:i * 2+2]) + new_sample = encode_sample(state, sample) + assert new_sample >= 0 and new_sample <= 15 + if i % 2 == 0: + byte = new_sample + else: + byte |= (new_sample << 4) + yield byte + +with open('ecclesia.adpcm', 'wb') as f: + f.write(bytes(nibbles(pcm))) + +with open('ecclesia.adpcm', 'rb') as f: + adpcm = f.read() + +def unnibbles(buf): + state = ADPCMState( + 0, 0, 7 + ) + for i in range(len(buf) * 2): + nib = (buf[i // 2] >> (4 * (i % 2))) & 0xf + new_sample = decode_sample(state, nib) + yield struct.pack('>h', new_sample) + +with open('ecclesia.adpcm.pcm', 'wb') as f: + for b in unnibbles(adpcm): + f.write(b) diff --git a/cartridge/ecclesia.adpcm b/cartridge/ecclesia.adpcm new file mode 100644 index 0000000..40bc2c0 Binary files /dev/null and b/cartridge/ecclesia.adpcm differ