akkera 102
/
apuplay
SPC music playback tools for real snes apu
Diff: apu2.cpp
- Revision:
- 2:62e6e22f8be2
- Parent:
- 0:5bd52e196edb
- Child:
- 3:b845c0cf715a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/apu2.cpp Wed Jan 11 16:00:29 2017 +0000 @@ -0,0 +1,411 @@ +#include "apu.h" +#include "apu2.h" +#include "cmd.h" + +// cmd.cpp +extern int g_debug; +extern int g_verbose; + +static unsigned char bootcode[77] = { + 0x8f, 0x00, 0x00, 0x8f, 0x00, 0x01, 0x8f, 0xff, + 0xfc, 0x8f, 0xff, 0xfb, 0x8f, 0x4f, 0xfa, 0x8f, + 0x31, 0xf1, 0xcd, 0x53, 0xd8, 0xf4, 0xe4, 0xf4, + 0x68, 0x00, 0xd0, 0xfa, 0xe4, 0xf5, 0x68, 0x00, + 0xd0, 0xfa, 0xe4, 0xf6, 0x68, 0x00, 0xd0, 0xfa, + 0xe4, 0xf7, 0x68, 0x00, 0xd0, 0xfa, 0xe4, 0xfd, + 0xe4, 0xfe, 0xe4, 0xff, 0x8f, 0x6c, 0xf2, 0x8f, + 0x00, 0xf3, 0x8f, 0x4c, 0xf2, 0x8f, 0x00, 0xf3, + 0x8f, 0x7f, 0xf2, 0xcd, 0xf5, 0xbd, 0xe8, 0xff, + 0x8d, 0x00, 0xcd, 0x00, 0x7f +}; + +// code loaded at 0xf2 +static unsigned char dsploader[16] = { + 0xc4, 0xf2, // start: MOV f2, A + 0x64, 0xf4, // loop: CMP A, f4 + 0xd0, 0xfc, // BNE loop + 0xfa, 0xf5, 0xf3, // MOV f3 , f5 + 0xc4, 0xf4, // MOV f4, A + 0xbc, // INC A + 0x10, 0xf2, // BPL start + 0x2f, 0xb7 // BRA 0xb7 ; jump inside rom +}; + +int LoadAPU_embedded(FILE *fp) +{ + int i=0, j=0, count=0, val=0; + + unsigned char spc_pcl; + unsigned char spc_pch; + unsigned char spc_a; + unsigned char spc_x; + unsigned char spc_y; + unsigned char spc_sw; + unsigned char spc_sp; + + unsigned char dsp_kon=0; + unsigned char dsp_flg=0; + unsigned char dsp_esa=0; + unsigned char dsp_edl=0; + + unsigned char workbuf[64]; + + int echosize, echoregion, bootptr, readcount=0; + + fseek(fp, 0x25, SEEK_SET); + + fread(&spc_pcl, 1, 1, fp); + fread(&spc_pch, 1, 1, fp); + fread(&spc_a, 1, 1, fp); + fread(&spc_x, 1, 1, fp); + fread(&spc_y, 1, 1, fp); + fread(&spc_sw, 1, 1, fp); + fread(&spc_sp, 1, 1, fp); + + if(g_debug) + { + printf("PC: %02x%02x\n", spc_pch, spc_pcl); + printf("A: %02X\n", spc_a); + printf("X: %02X\n", spc_x); + printf("Y: %02X\n", spc_y); + printf("SW: %02X\n", spc_sw); + printf("SP: %02X\n", spc_sp); + } + + apu_init(); + apu_reset(); + apu_initTransfer(0x0002); + + if(g_verbose) + { + printf("Restoring dsp registers...\n"); + } + + // first, we send a small program called the dsploader which we will + // use to restore the DSP registers (with our modified KON and FLG to + // keep it silent) + if(apu_writeBytes(dsploader, 16)) + { + fprintf(stderr, "Timeout sending dsploader\n"); + return -1; + } + apu_endTransfer(0x0002); + + // restore the 128 dsp registers one by one with the help of the dsp loader. + fseek(fp, OFFSET_DSPDATA, SEEK_SET); + for(i=0; i<128; i+=64) + { + fread(workbuf, 64, 1, fp); + for(j=0; j<64; j++) + { + // mute all voices and stop all notes + if(i+j == DSP_FLG) + { + dsp_flg = workbuf[j]; // save it for later + workbuf[j] = DSP_FLG_MUTE | DSP_FLG_ECEN; + } + if(i+j == DSP_KON) + { + dsp_kon = workbuf[j]; // save it for later + workbuf[j] = 0x00; + } + + // take note of some values while we upload... + if(i+j == DSP_ESA) dsp_esa = workbuf[j]; + if(i+j == DSP_EDL) dsp_edl = workbuf[j]; + + apu_write(1, workbuf[j]); + apu_write(0, i+j); + if(!apu_waitInport(0, i+j, 500)) + { + if(apu_read(0) == 0xaa) + { + if(g_verbose) + { + printf("ingored %d\n", i+j); + } + } + else + { + fprintf(stderr, "timeout 3\n"); + return -1; + } + } + cmd_pspin_update(); + } + } + + // after receiving 128 registers, the dsp loaded will jump + // inside the rom at address $ffc9. Once 0xAA appears in + // port0, the apu is ready for a new transfer. */ + if(!apu_waitInport(0, 0xaa, 500)) + { + fprintf(stderr, "timeout 4\n"); + return -1; + } + + // save a bunch of registers to be restored + // later by the "bootcode" + bootcode[BOOT_DSP_FLG] = dsp_flg; + bootcode[BOOT_DSP_KON] = dsp_kon; + bootcode[BOOT_A] = spc_a; + bootcode[BOOT_Y] = spc_y; + bootcode[BOOT_X] = spc_x; + bootcode[BOOT_SP] = spc_sp - 3; // save new stack pointer + + // save address $0000 and $0001 to be restored by "bootcode" + fseek(fp, OFFSET_SPCDATA, SEEK_SET); + fread(workbuf, 2, 1, fp); + bootcode[0x01] = workbuf[0]; + bootcode[0x04] = workbuf[1]; + + // save most spc registers (0xf0 to 0xff) into bootcode to be restored + // later + fseek(fp, OFFSET_SPCDATA+0xf0, SEEK_SET); + fread(workbuf, 0x10, 1, fp); + for(i=0xf0; i<=0xff; i++) + { + switch(i) + { + case SPC_PORT0: bootcode[BOOT_SPC_PORT0] = workbuf[i-0xf0]; break; + case SPC_PORT1: bootcode[BOOT_SPC_PORT1] = workbuf[i-0xf0]; break; + case SPC_PORT2: bootcode[BOOT_SPC_PORT2] = workbuf[i-0xf0]; break; + case SPC_PORT3: bootcode[BOOT_SPC_PORT3] = workbuf[i-0xf0]; break; + case SPC_TIMER0: bootcode[BOOT_SPC_TIMER0] = workbuf[i-0xf0]; break; + case SPC_TIMER1: bootcode[BOOT_SPC_TIMER1] = workbuf[i-0xf0]; break; + case SPC_TIMER2: bootcode[BOOT_SPC_TIMER2] = workbuf[i-0xf0]; break; + case SPC_CONTROL: bootcode[BOOT_SPC_CONTROL] = workbuf[i-0xf0]; break; + case SPC_REGADD: bootcode[BOOT_SPC_REGADD] = workbuf[i-0xf0]; break; + } + } + + // to produce an echo effect, the dsp uses a memory region. + // ESA: Esa * 100h becomes the lead-off address of the echo + // region. Calculate this address... + echoregion = dsp_esa * 256; + + // echo delay. The bigger the delay is, more memory is needed. + // calculate how much memory used... + echosize = dsp_edl * 2048; + if(echosize == 0) + { + echosize = 4; + } + + if(g_debug) + { + printf("debug: echoregion: $%04x, size %d\n", echoregion, echosize); + } + + apu_initTransfer(0x0002); + if(g_verbose) + { + printf("Restoring spc700 memory...\n"); + } + + if(g_debug) + { + printf("debug: Sending spc memory from 0x02 to 0xef\n"); + } + + // send the first part of the memory (0x02 to 0xef) + // After 0xef comes spc700 registers (0xf0 to 0xff). Those + // are taken care of by the bootcode. 0x00 and 0x01 are + // retored by the bootcode too. + fseek(fp, OFFSET_SPCDATA, SEEK_SET); + for(j=0; j<256; j+=64) + { + fread(workbuf, 64, 1, fp); + + for(i=0; i<0x40; i++) + { + if(j+i >= 0xf0) + { + break; + } + + // skip $0000 and $0001 + if(j==0 && i<2) + { + continue; + } + + apu_write(1, workbuf[i]); + apu_write(0, j+i-2); + + if(!apu_waitInport(0, j+i-2, 500)) + { + fprintf(stderr, "timeout 5\n"); + return -1; + } + + cmd_pspin_update(); + } + + if (j+i>=0xf0) + { + break; + } + } + + if(apu_newTransfer(0x100)) + { + apu_reset(); + return -1; + } + + if(g_debug) + { + printf("debug: Sending spc memory from 0x100 to 0xffc0\n"); + } + + // upload the external memory region data (0x100 (page 1) to 0xffbf (rom), + // and look for an area with the same consecutive value repeated 77 times + fseek(fp, OFFSET_SPCDATA+0x100, SEEK_SET); + bootptr = -1; + for(i=0x100; i <= 65471; i+= 16) + { + fread(workbuf, 16, 1, fp); + + for(j=0; j<16; j++) + { + // push program counter and status ward on stack + if((i+j) == (0x100 +spc_sp - 0)) + { + workbuf[j] = spc_pch; + } + + if((i+j) == (0x100 +spc_sp - 1)) + { + workbuf[j] = spc_pcl; + } + + if((i+j) == (0x100 +spc_sp - 2)) + { + workbuf[j] = spc_sw; + } + + if((i > echoregion + echosize) || (i < echoregion)) + { + if(val==workbuf[j]) + { + count++; + if(count >= 77) + { + bootptr = i+j-77; + } + } + else + { + val = workbuf[j]; + count = 0; + } + } + else + { + count = 0; + } + } + + if(apu_writeBytes(workbuf, 16)) + { + fprintf(stderr, "Transfer error\n"); + return -1; + } + + if(i % 256 == 0) + { + readcount += 256; + cmd_pspin_update(); + } + } + + if(g_debug) + { + printf("debug: area for bootcode: $%04x (%02X)\n", bootptr, val); + } + + // we did not find an area of 77 consecutive identical byte values. + if(bootptr == -1) + { + // We will have to use the echo region. The region will need to be + // at least 77 bytes... + if(echosize < 77) + { + fprintf(stderr, "This spc file does not have sufficient ram to be loaded"); + return -1; + } + else + { + // we will use the echo region + bootptr = echoregion; + } + } + + if(g_debug) + { + printf("debug: Sending spc memory from 0xffc0 to 0xffff\n"); + } + // upload the external memory area overlapping with the rom... I guess + // if we write to those address from the SPC it really writes to this + // memory area, but if you read you'll probably get the ROM code. Maybe + // it's really Read/Write from the DSP point of view... TODO: Check this + // + // Maybe also setting SPC_CONTROL msb bit enables this region? It's not + // documented my manual... + if(bootcode[BOOT_SPC_CONTROL] & 0x80) + { + fseek(fp, OFFSET_SPCRAM, SEEK_SET); + fread(workbuf, 64, 1, fp); + } + else + { + fseek(fp, OFFSET_SPCDATA + 65472, SEEK_SET); + fread(workbuf, 64, 1, fp); + } + + if(apu_writeBytes(workbuf, 64)) + { + return -1; + } + + if(apu_newTransfer(bootptr)) + { + apu_reset(); + return -1; + } + + // Copy our bootcode into the area we found + if(apu_writeBytes(bootcode, 77)) + { + fprintf(stderr, "Bootcode transfer error\n"); + return -1; + } + + apu_endTransfer(bootptr); + + //i = 0; + if(!apu_waitInport(0, 0x53, 500)) + { + fprintf(stderr, "timeout 7\n"); + return -1; + } + + if(g_debug) + { + printf("Setting final port values $%02X $%02X $%02X $%02X\n", + bootcode[BOOT_SPC_PORT0], bootcode[BOOT_SPC_PORT1], + bootcode[BOOT_SPC_PORT2], bootcode[BOOT_SPC_PORT3]); + } + + // Restore the ports to the value they + // had in the .spc (this is not done by the bootcode because + // Port0-3 have 2 different values (The value set internally is + // seen externaly and the value seen internally is set externally) + apu_write(0, bootcode[BOOT_SPC_PORT0]); + apu_write(1, bootcode[BOOT_SPC_PORT1]); + apu_write(2, bootcode[BOOT_SPC_PORT2]); + apu_write(3, bootcode[BOOT_SPC_PORT3]); + + return 0; +}