SPC music playback tools for real snes apu

Dependencies:   mbed

Revision:
0:5bd52e196edb
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apuplay_embedded.cpp	Mon Jan 09 13:54:39 2017 +0000
@@ -0,0 +1,378 @@
+/* hwapu - SPC music playback tools for real snes apu
+ * Copyright (C) 2004-2005  Raphael Assenat <raph@raphnet.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <stdio.h>
+#include "apuplay.h"
+#include "apu.h"
+#include "dsploader.h"
+#include "bootcode.h"
+#ifdef PROGRESS_SPINNER
+#include "pspin.h"
+#endif
+
+extern int g_debug;
+extern int g_verbose;
+extern int g_exit_now;
+extern int g_playing;
+extern int g_progress;
+
+int LoadAPU_embedded(FILE *fptr)
+{
+    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 spcdata[65536];
+//  unsigned char spcram[64];
+
+    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(fptr, 0x25, SEEK_SET);
+
+    fread(&spc_pcl, 1, 1, fptr);
+    fread(&spc_pch, 1, 1, fptr);
+    fread(&spc_a, 1, 1, fptr);
+    fread(&spc_x, 1, 1, fptr);
+    fread(&spc_y, 1, 1, fptr);
+    fread(&spc_sw, 1, 1, fptr);
+    fread(&spc_sp, 1, 1, fptr);
+
+    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_reset();
+    apu_initTransfer(0x0002);
+
+    if (g_verbose) 
+        printf("Restoring dsp registers...\n");
+
+    if (g_exit_now || !g_playing) { apu_reset(); return 0; }
+    
+    /* 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);    
+    
+    if (g_exit_now || !g_playing) { apu_reset(); return 0; }
+
+    /* restore the 128 dsp registers one by one with the help of the dsp loader. */
+    fseek(fptr, OFFSET_DSPDATA, SEEK_SET);
+    for (i=0; i<128; i+=64)
+    {
+        fread(workbuf, 64, 1, fptr);
+        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) {
+//              fprintf(stderr, "ingored\n");
+                } else {
+                    fprintf(stderr, "timeout 3\n"); return -1; 
+                }
+            }
+
+            if (g_exit_now || !g_playing) { apu_reset(); return 0; }
+#ifdef PROGRESS_SPINNER
+            if (g_progress) {
+                pspin_update();
+            }
+#endif
+        }
+
+    }
+//  if (g_verbose) 
+//      printf("\n");
+
+    /* 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; 
+    }
+
+//  fseek(fptr, OFFSET_SPCRAM, SEEK_SET);
+//  fread(spcram, 64, 1, fptr);
+
+    /* 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(fptr, OFFSET_SPCDATA, SEEK_SET);
+    fread(workbuf, 2, 1, fptr);
+    bootcode[0x01] = workbuf[0];
+    bootcode[0x04] = workbuf[1];
+    
+    /* save most spc registers (0xf0 to 0xff) into bootcode to be restored
+     * later */
+    fseek(fptr, OFFSET_SPCDATA+0xf0, SEEK_SET);
+    fread(workbuf, 0x10, 1, fptr);
+    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(fptr, OFFSET_SPCDATA, SEEK_SET);
+    for (j=0; j<256; j+=64)
+    {
+        fread(workbuf, 64, 1, fptr);
+        for (i=0; i<0x40; i++) {
+            if (j+i>=0xf0) { break; }
+            if (j==0 && i<2) { continue; } // skip $0000 and $0001
+            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; 
+            }
+#ifdef PROGRESS_SPINNER
+            if (g_progress) 
+                pspin_update();
+#endif
+            if (g_exit_now || !g_playing) { apu_reset(); return 0; }
+        }
+        if (j+i>=0xf0) { break; }
+    }
+
+//  if (g_verbose) 
+//      printf("\n");
+
+    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(fptr, OFFSET_SPCDATA+0x100, SEEK_SET);
+    bootptr = -1;
+    for (i=0x100; i <= 65471; i+= 16)
+    {       
+        fread(workbuf, 16, 1, fptr);
+        
+        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;
+//                      printf("nbptr: %d\n", 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;
+#ifdef PROGRESS_SPINNER
+            if (g_progress) {
+                pspin_update();
+            }
+#endif
+        }
+        if (g_exit_now || !g_playing) { apu_reset(); return 0; }
+    }
+    
+//  bootptr = 0x2e47;
+    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(fptr, OFFSET_SPCRAM, SEEK_SET);
+        fread(workbuf, 64, 1, fptr);
+    }
+    else {
+        fseek(fptr, OFFSET_SPCDATA + 65472, SEEK_SET);
+        fread(workbuf, 64, 1, fptr);
+    }
+    
+    if (apu_writeBytes(workbuf, 64)) {
+        return -1;
+    }
+
+//  if (g_verbose) 
+//      printf("\n");
+
+    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]);
+
+    
+    if (g_exit_now || !g_playing) { apu_reset(); return 0; }
+    return 0;
+}
+
+