SPC music playback tools for real snes apu

Dependencies:   mbed

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;
+}