snes_spc 0.9.0: SNES SPC-700 APU Emulator ----------------------------------------- Author : Shay Green Website: http://www.slack.net/~ant/ Forum : http://groups.google.com/group/blargg-sound-libs License: GNU Lesser General Public License (LGPL) Contents -------- * C and C++ Interfaces * Overview * Full SPC Emulation * DSP Emulation * SPC Music Playback * State Copying * Library Compilation * Error handling * Solving Problems * Accurate S-DSP Limitations * Fast S-DSP Limitations * S-SMP Limitations * To Do * Thanks C and C++ Interfaces -------------------- The library includes a C interface in spc.h and dsp.h, which can be used from C and C++. This C interface is referred to in the following documentation. If you're building this as a shared library (rather than linking statically), you should use the C interface since it will change less in future versions. The native C++ interface is in the header files SNES_SPC.h, SPC_DSP.h, and SPC_Filter.h, and the two interfaces can be freely mixed in C++ code. Conversion between the two interfaces generally follows a pattern: C interface C++ interface - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SNES_SPC* spc; SNES_SPC* spc; spc = spc_new(); spc = new SNES_SPC; spc_play( spc, count, buf ); spc->play( count, buf ); spc_sample_rate SNES_SPC::sample_rate spc_delete( spc ); delete spc; Overview -------- There are three main roles for this library: * Full SPC emulation in a SNES emulator * DSP emulation in a SNES emulator (where you emulate the SMP CPU) * SPC playback in an SPC music file player Each of these uses are described separately below. Full SPC Emulation ------------------ See spc.h for full function reference (SNES_SPC.h if using C++). * Create SPC emulator with spc_new() and check for NULL. * Call spc_init_rom() with a pointer to the 64-byte IPL ROM dump (not included with library). * When your emulated SNES is powered on, call spc_reset(). When the reset switch is pressed, call spc_soft_reset(). * Call spc_set_output() with your output buffer, then do emulation. * When the SNES CPU accesses APU ports, call spc_read_port() and spc_write_port(). * When your emulator's timebase is going back to 0, call spc_end_frame(), usually at the end of a video frame or scanline. * Periodically play samples from your buffer. Use spc_sample_count() to find out how many samples have been written, then spc_set_output() after you've made more space in your buffer. * Save/load full emulator state with spc_copy_state(). * You can save as an SPC music file with spc_save_spc(). * When done, use spc_delete() to free memory. DSP Emulation ------------- See dsp.h for full function reference (SPC_DSP.h if using C++). * Create DSP emulator with spc_dsp_new() and check for NULL. * Let the DSP know where your 64K RAM is with spc_dsp_init(). * When your emulated SNES is powered on, call spc_dsp_reset(). When the reset switch is pressed, call spc_dsp_soft_reset(). * Call spc_dsp_set_output() with your output buffer, then do emulation. * Use spc_dsp_run() to run DSP for specified number of clocks (1024000 per second). Every 32 clocks a pair of samples is added to your output buffer. * Use spc_dsp_read() and spc_dsp_write() to handle DSP reads/writes from the S-SMP. Before calling these always catch the DSP up to present time with spc_dsp_run(). * Periodically play samples from your buffer. Use spc_dsp_sample_count() to find out how many samples have been written, then spc_dsp_set_output() after you've made more space in your buffer. * Use spc_dsp_copy_state() to save/load full DSP state. * When done, use spc_delete() to free memory. SPC Music Playback ------------------ See spc.h for full function reference (SNES_SPC.h if using C++). * Create SPC emulator with spc_new() and check for NULL. * Load SPC with spc_load_spc() and check for error. * Optionally cear echo buffer with spc_clear_echo(). Many SPCs have garbage in echo buffer, which causes noise at the beginning. * Generate samples as needed with spc_play(). * When done, use spc_delete() to free memory. * For a more complete game music playback library, use Game_Music_Emu State Copying ------------- The SPC and DSP modules include state save/load functions. They take a pointer to a pointer to a buffer to store state, and a copy function. This copy function can either copy data to the buffer or from it, allowing state save and restore with the same library function. The internal save state format allows for future expansion without making previous save states unusable. The SPC save state format puts the most important parts first to make it easier to manually examine. It's organized as follows: Offset Size Data - - - - - - - - - - - - - - - - - - 0 $10000 SPC RAM $10000 $10 SMP $F0-$FF registers $10010 4 SMP $F4-$F8 output registers $10014 2 PC $10016 1 A $10017 1 X $10018 1 Y $10019 1 PSW $1001A 1 SP $1001B 5 internal $10020 $80 DSP registers $100A0 ... internal Library Compilation ------------------- While this library is in C++, it has been written to easily link in a C program *without* needing the standard C++ library. It doesn't use exception handling or run-time type information (RTTI), so you can disable these in your C++ compiler to increase efficiency. If you're building a shared library (DLL), I recommend only exporting the C interfaces in spc.h and dsp.h, as the C++ interfaces expose implementation details that will break link compatibility across versions. If you're using C and compiling with GCC, I recommend the following command-line options when compiling the library source, otherwise GCC will insert calls to the standard C++ library and require that it be linked in: -fno-rtti -fno-exceptions For maximum optimization, see the NDEBUG and BLARGG_NONPORTABLE options in blargg_config. If using GCC, you can enable these by adding the following command-line options when you invoke gcc. If you encounter problems, try without -DBLARGG_NONPORTABLE; if that works, contact me so I can figure out why BLARGG_NONPORTABLE was causing it to fail. -O3 -DNDEBUG -DBLARGG_NONPORTABLE -fno-rtti -fno-exceptions Error handling -------------- Functions which can fail have a return type of spc_err_t (blargg_err_t in the C++ interfaces), which is a pointer to an error string (const char*). If a function is successful it returns NULL. Errors that you can easily avoid are checked with debug assertions; spc_err_t return values are only used for genuine run-time errors that can't be easily predicted in advance (out of memory, I/O errors, incompatible file data). Your code should check all error values. To improve usability for C programmers, C++ programmers unfamiliar with exceptions, and compatibility with older C++ compilers, the library does *not* throw any C++ exceptions and uses malloc() instead of the standard operator new. This means that you *must* check for NULL when creating a library object with the new operator. Solving Problems ---------------- If you're having problems, try the following: * If you're getting garbled sound, try this simple siren generator in place of your call to play(). This will quickly tell whether the problem is in the library or in your code. static void play_siren( long count, short* out ) { static double a, a2; while ( count-- ) *out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) ); } * Enable debugging support in your environment. This enables assertions and other run-time checks. * Turn the compiler's optimizer is off. Sometimes an optimizer generates bad code. * If multiple threads are being used, ensure that only one at a time is accessing a given set of objects from the library. This library is not in general thread-safe, though independent objects can be used in separate threads. * If all else fails, see if the demos work. Accurate S-DSP Limitations -------------------------- * Power-up and soft reset behavior might have slight inaccuracies. * Muting (FLG bit 6) behavior when toggling bit very rapidly is not emulated properly. * No other known inaccuracies. Has passed 100+ strenuous tests. Fast S-DSP Limitations ---------------------- * Uses faster sample calculations except in cases where exact value is actually important (BRR decoding, and gaussian interpolation combined with pitch modulation). * Stops decoding BRR data when a voice's envelope has released to silence. * Emulates 32 clocks at a time, so DSP register and memory accesses are all done in a bunch rather than spread out. Even though, some clever code makes register accesses separated by 40 or so clocks occur with cycle-accurate timing. S-SMP Limitations ----------------- * Opcode fetches and indirect pointers are always read directly from memory, even for the $F0-$FF region, and the DSP is not caught up for these fetches. * Attempts to perversely execute data in registers or an area being modified by echo will not be emulated properly. * Has not been thoroughly tested. * Test register ($F0) is not implemented. * Echo buffer can overwrite IPL ROM area, and does not correctly update extra RAM there. To Do ----- * I'd like feedback on the interface and any ways to improve it. In particular, the differing features between the accurate and fast DSP emulators might make it harder to cleanly switch between them without modifying source code. * Finish thorough tests on SMP memory access times. * Finish thorough tests on SMP instruction behavior (flags, registers). * Finish thorough tests on SMP timers. * Finish power-up and reset behavior testing. * Come up with best starting conditions to play an SPC and implement in hardware SNES SPC player for verification. Thanks ------ Thanks to Anti-Resonance's SPC2ROM and help getting SPCs playing on my SNES in the first place, then Brad Martin's openspc and Chris Moeller's openspc++ C++ adaptation, giving me a good SPC emulator to start with several years ago. Thanks to Richard Bannister, Mahendra Tallur, Shazz, nenolod, theHobbit, Johan Samuelsson, nes6502, and Micket for helping test my Game_Music_Emu library. Thanks to hcs for help in converting to C for the Rockbox port. Thanks to byuu (bsnes author) and pagefault and Nach (zsnes team) for testing and using my new rewritten DSP in their emulators. Thanks to anomie for his good SNES documentation and discussions with me to keep it up to date with my latest findings. -- Shay Green