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