diff --git a/CMakeLists.txt b/CMakeLists.txt index 4de5dbc92..2f08e5338 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,7 +105,7 @@ set( LZMA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lzma/C" ) add_subdirectory( lzma ) add_subdirectory( tools ) -add_subdirectory( snes_spc ) +add_subdirectory( game-music-emu ) add_subdirectory( dumb ) add_subdirectory( gdtoa ) add_subdirectory( wadsrc ) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 00505a707..381269a9b 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,7 @@ -June 2, 2009 (Changes by Graf Zahl) +June 2, 2009 +- Swapped snes_spc out for the full Game Music Emu library. + +June 2, 2009 (Changes by Graf Zahl) - Fixed: The Hexen status bar still uses MAX_MANA for some calculations instead of MaxAmount. diff --git a/game-music-emu/CMakeLists.txt b/game-music-emu/CMakeLists.txt new file mode 100644 index 000000000..673db5445 --- /dev/null +++ b/game-music-emu/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required( VERSION 2.4 ) +include( CheckCXXCompilerFlag ) + +# I don't plan on debugging this, so make it a release build. +set( CMAKE_BUILD_TYPE "RelWithDebInfo" ) + +if( CMAKE_COMPILER_IS_GNUCXX ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) + if( NOT PROFILE ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer" ) + endif( NOT PROFILE ) + check_cxx_compiler_flag( -Wno-array-bounds HAVE_NO_ARRAY_BOUNDS ) + if( HAVE_NO_ARRAY_BOUNDS ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds" ) + endif( HAVE_NO_ARRAY_BOUNDS ) +endif( CMAKE_COMPILER_IS_GNUCXX ) + +add_library( gme + gme/Blip_Buffer.cpp + gme/Classic_Emu.cpp + gme/Data_Reader.cpp + gme/Dual_Resampler.cpp + gme/Effects_Buffer.cpp + gme/Fir_Resampler.cpp + gme/gme.cpp + gme/Gme_File.cpp + gme/M3u_Playlist.cpp + gme/Multi_Buffer.cpp + gme/Music_Emu.cpp + + gme/Ay_Apu.cpp + gme/Ay_Cpu.cpp + gme/Ay_Emu.cpp + gme/Gb_Apu.cpp + gme/Gb_Cpu.cpp + gme/Gb_Oscs.cpp + gme/Gbs_Emu.cpp + gme/Gym_Emu.cpp + gme/Hes_Apu.cpp + gme/Hes_Cpu.cpp + gme/Hes_Emu.cpp + gme/Kss_Cpu.cpp + gme/Kss_Emu.cpp + gme/Kss_Scc_Apu.cpp + gme/Nes_Apu.cpp + gme/Nes_Cpu.cpp + gme/Nes_Fme7_Apu.cpp + gme/Nes_Namco_Apu.cpp + gme/Nes_Oscs.cpp + gme/Nes_Vrc6_Apu.cpp + gme/Nsf_Emu.cpp + gme/Nsfe_Emu.cpp + gme/Sap_Apu.cpp + gme/Sap_Cpu.cpp + gme/Sap_Emu.cpp + gme/Sms_Apu.cpp + gme/Snes_Spc.cpp + gme/Spc_Cpu.cpp + gme/Spc_Dsp.cpp + gme/Spc_Emu.cpp + gme/Vgm_Emu.cpp + gme/Vgm_Emu_Impl.cpp + gme/Ym2413_Emu.cpp + gme/Ym2612_Emu.cpp ) +target_link_libraries( gme ) diff --git a/game-music-emu/changes.txt b/game-music-emu/changes.txt new file mode 100644 index 000000000..0405b5904 --- /dev/null +++ b/game-music-emu/changes.txt @@ -0,0 +1,218 @@ +Game_Music_Emu Change Log +------------------------- + +Game_Music_Emu 0.5.2 +-------------------- +- *TONS* of changes and improvements. You should re-read the new header +files and documentation as the changes will allow you to simplify your +code a lot (it might even be simpler to just rewrite it). Existing code +should continue to work without changes in most cases (see Deprecated +features in gme.txt). + +- New file formats: AY, HES, KSS, SAP, NSFE + +- All-new comprehensive C interface (also usable from C++). Simplifies +many things, especially file loading, and brings everything together in +one header file (gme.h). + +- Information tags and track names and times can be accessed for all +game music formats + +- New features supported by all emulators: end of track fading, +automatic silence detection, adjustable song tempo, seek to new time in +track + +- Updated mini player example to support track names and times, echo, +tempo, and channel muting, and added visual waveform display + +- Improved configuration to use blargg_config.h, which you can modify +and keep when you update to a newer libary version. Includes flag for +library to automatically handle gzipped files using zlib (so you don't +need to use Gzip_File_Reader anymore). + +- GBS: Fixed wave channel to not reset waveform when APU is powered off +(affected Garfield). Also improved invalid bank selection (affected Game +& Watch and others). + +- VGM: Added support for alternate noise shifter register +configurations, used by other systems like the BBC Micro. + +- SPC: Removed IPL ROM dump from emulator, as none of the SPC files I +scanned needed it, and an SPC file can include a copy if necessary. Also +re-enabled supposed clamping in gaussian interpolation between the third +and fourth lookups, though I don't know whether it matters + +- Added Music_Emu::load_mem() to use music data already in memory +(without copying it) + +- Added Music_Emu::warning(), which reports minor problems when loading +and playing a music file + +- Added Music_Emu::set_gain() for uniform adjustment of gain. Can only +be set during initialization, so not useful as a general volume control. + +- Added custom operator new to ensure that no exceptions are thrown in +the library (I'd use std::nothrow if it were part of pre-ISO (ARM) C++) + +- Added BLIP_BUFFER_FAST flag to blargg_config.h to use a lower quality +bandlimited synthesis in "classic" emulators, which might help +performance on ancient processors (measure first!). Don't use this +unless absolutely necessary, as quality suffers. + +- Improved performance a bit for x86 platforms + +- Text files now in DOS newline format so they will open in Notepad +properly + +- Removed requirement that file header structures not have any padding +added to the end + +- Fixed common bug in all CPU emulators where negative program counter +could crash emulator (occurred during a negative branch from the +beginning of memory). Also fixed related bug in Z80 emulator for +IX/IY+displacement mode. + +- Eliminated all warnings when compiling on gcc 4.0. The following +generates no diagnostics: + + gcc -S gme/*.cpp -o /dev/null -ansi -fno-gnu-keywords + -fno-nonansi-builtins -pedantic -W -Wabi -Wall -Wcast-align + -Wcast-qual -Wchar-subscripts -Wdisabled-optimization -Werror + -Winline -Wlong-long -Wmultichar -Winvalid-offsetof + -Wnon-virtual-dtor -Woverloaded-virtual -Wparentheses + -Wpointer-arith -Wredundant-decls -Wreorder -Wsign-compare + -Wsign-promo -Wunknown-pragmas -Wwrite-strings + + +Game_Music_Emu 0.3.0 +-------------------- +- Added more demos, including music player using the SDL multimedia +library for sound, and improved documentation + +- All: Improved interface to emulators to allow simpler setup and +loading. Instead of various init() functions, all now support +set_sample_rate( long rate ) and load( const char* file_path ). + +- All: Removed error return from start_track() and play(), and added +error_count() to get the total number of emulation errors since the +track was last started. See demos for examples of new usage. + +- All: Fixed mute_voices() muting to be preserved after loading files +and starting tracks, instead of being cleared as it was whenever a track +was started + +- VGM: Rewrote Vgm_Emu to support Sega Genesis/Mega Drive FM sound at +any sample rate with optional FM oversampling, support for alternate +YM2612 sound cores, and support for optional YM2413 + +- VGM: Added tempo control, useful for slowing 60Hz NTSC Sega Genesis +music to 50Hz PAL + +- VGM: Removed Vgm_Emu::track_data(), since I realized that this +information is already present in the VGM header (oops!) + +- GYM: Changed Gym_Emu::track_length() operation (see Gym_Emu.h) + +- NSF: Added support for Sunsoft FME-7 sound chip used by Gimmick +soundtrack + +- NSF: Fixed Namco 106 problems with Final Lap and others + +- Moved library sources to gme/ directory to reduce clutter, and merged +boost/ functionality into blargg_common.h + +- Added Gzip_File_Reader for transparently using gzipped files + + +Game_Music_Emu 0.2.4 +-------------------- +- Created a discussion forum for problems and feedback: +http://groups-beta.google.com/group/blargg-sound-libs + +- Changed error return value of Blip_Buffer::sample_rate() (also for +Stereo_Buffer, Effects_Buffer, etc.) to blargg_err_t (defined in +blargg_common.h), to make error reporting consistent with other +functions. This means the "no error" return value is the opposite of +what it was before, which will break current code which checks the error +return value: + + // current code (broken) + if ( !buf.sample_rate( samples_per_sec ) ) + out_of_memory(); + + // quick-and-dirty fix (just remove the ! operation) + if ( buf.sample_rate( samples_per_sec ) ) + out_of_memory(); + + // proper fix + blargg_err_t error = buf.sample_rate( samples_per_sec ); + if ( error ) + report_error( error ); + +- Implemented workaround for MSVC++ 6 compiler limitations, allowing it +to work on that compiler again + +- Added sample clamping to avoid wrap-around at high volumes, allowing +higher volume with little distortion + +- Added to-do list and design notes + +- Added Music_Emu::skip( long sample_count ) to skip ahead in current +track + +- Added Gym_Emu::track_length() and Vgm_Emu::track_length() for +determining the length of non-looped GYM and VGM files + +- Partially implemented DMC non-linearity when its value is directly set +using $4011, which reduces previously over-emphasized "popping" of +percussion on some games (TMNT II in particular) + +- Fixed Fir_Resampler, used for SPC and GYM playback (was incorrectly +using abs() instead of fabs()...argh) + +- Fixed SPC emulation bugs: eliminated clicks in Plok! soundtrack and +now stops sample slightly earlier than the end, as the SNES does. Fixed +a totally broken CPU addressing mode. + +- Fixed Konami VRC6 saw wave (was very broken before). Now VRC6 music +sounds decent + +- Fixed a minor GBS emulation bug + +- Fixed GYM loop point bug when track was restarted before loop point +had been reached + +- Made default GBS frequency equalization less muffled + +- Added pseudo-surround effect removal for SPC files + +- Added Music_Emu::voice_names() which returns names for each voice. + +- Added BLARGG_SOURCE_BEGIN which allows custom compiler options to be +easily set for library sources + +- Changed assignment of expansion sound chips in Nsf_Emu to be spread +more evenly when using Effects_Buffer + +- Changed 'size_t' values in Blip_Buffer interface to 'long' + +- Changed demo to generate a WAVE sound file rather than an AIFF file + + +Game_Music_Emu 0.2.0 +-------------------- +- Redid framework and rewrote/cleaned up emulators + +- Changed licensing to GNU Lesser General Public License (LGPL) + +- Added Sega Genesis GYM and Super Nintendo SPC emulators + +- Added Namco-106 and Konami VRC6 sound chip support to NSF emulator + +- Eliminated use of static mutable data in emulators, allowing +multi-instance safety + + +Game_Music_Emu 0.1.0 +-------------------- +- First release diff --git a/game-music-emu/design.txt b/game-music-emu/design.txt new file mode 100644 index 000000000..8c8c65b14 --- /dev/null +++ b/game-music-emu/design.txt @@ -0,0 +1,194 @@ +Game_Music_Emu 0.5.2 Design +--------------------------- +This might be slightly out-of-date at times, but will be a big help in +understanding the library implementation. + + +Architecture +------------ +The library is essentially a bunch of independent game music file +emulators unified with a common interface. + +Gme_File and Music_Emu provide a common interface to the emulators. The +virtual functions are protected rather than public to allow pre- and +post-processing of arguments and data in one place. This allows the +emulator classes to assume that everything is set up properly when +starting a track and playing samples. + +All file input is done with the Data_Reader interface. Many derived +classes are present, for the usual disk-based file and block of memory, +to specialized adaptors for things like reading a subset of data or +combining a block of memory with a Data_Reader to the remaining data. +This makes the library much more flexible with regard to the source of +game music file data. I still added a specialized load_mem() function to +have the emulator keep a pointer to data already read in memory, for +those formats whose files can be absolutely huge (GYM, some VGMs). This +is important if for some reason the caller must load the data ahead of +time, but doesn't want the emulator needlessly making a copy. + +Since silence checking and fading are relatively complex, they are kept +separate from basic file loading and track information, which are +handled in the base class Gme_File. My original intent was to use +Gme_File as the common base class for full emulators and track +information-only readers, but implementing the C interface was much +simpler if both derived from Music_Emu. User C++ code can still benefit +from static checking by using Gme_File where only track information will +be accessed. + +Each emulator generally has three components: main emulator, CPU +emulator, and sound chip emulator(s). Each component has minimal +coupling, so use in a full emulator or stand alone is fairly easy. This +modularity really helps reduce complexity. Blip_Buffer helps a lot with +simplifying the APU interfaces and implementation. + +The "classic" emulators derive from Classic_Emu, which handles +Blip_Buffer filling and multiple channels. It uses Multi_Buffer for +output, allowing you to derive a custom buffer that could output each +voice to a separate sound channel and do different processing on each. +At some point I'm going to implement a better Effects_Buffer that allows +individual control of every channel. + +In implementing the C interface, I wanted a way to specify an emulator +type that didn't require linking in all the emulators. For each emulator +type there is a global object with pointers to functions to create the +emulator or a track information reader. The emulator type is thus a +pointer to this, which conveniently allows for a NULL value. The user +referencing this emulator type object is what ultimately links the +emulator in (unless new Foo_Emu is used in C++, of course). This type +also serves as a useful substitute for RTTI on older C++ compilers. + +Addendum: I have since added gme_type_list(), which causes all listed +emulators to be linked in. To avoid this, I make the list itself +editable in blargg_config.h. Having a built-in list allows +gme_load_file() to take a path and give back an emulator with the file +loaded, which is extremely useful for new users. + + +Interface conventions +---------------------- +If a function retains a pointer to or replaces the value of an object +passed, it takes a pointer so that it will be clear in the caller's +source code that care is required. + +Multi-word names have an underscore '_' separator between individual +words. + +Functions are named with lowercase words. Functions which perform an +action with side-effects are named with a verb phrase (i.e. load, move, +run). Functions which return the value of a piece of state are named +using a noun phrase (i.e. loaded, moved, running). + +Classes are named with capitalized words. Only the first letter of an +acronym is capitalized. Class names are nouns, sometimes suggestive of +what they do (i.e. File_Scanner). + +Structure, enumeration, and typedefs to these and built-in types are +named using lowercase words with a _t suffix. + +Macros are named with all-uppercase words. + +Internal names which can't be hidden due to technical reasons have an +underscore '_' suffix. + + +Managing Complexity +------------------- +Complexity has been a factor in most library decisions. Many features +have been passed by due to the complexity they would add. Once +complexity goes past a certain level, it mentally grasping the library +in its entirety, at which point more defects will occur and be hard to +find. + +I chose 16-bit signed samples because it seems to be the most common +format. Supporting multiple formats would add too much complexity to be +worth it. Other formats can be obtained via conversion. + +I've kept interfaces fairly lean, leaving many possible features +untapped but easy to add if necessary. For example the classic emulators +could have volume and frequency equalization adjusted separately for +each channel, since they each have an associated Blip_Synth. + +Source files of 400 lines or less seem to be the best size to limit +complexity. In a few cases there is no reasonable way to split longer +files, or there is benefit from having the source together in one file. + + +Preventing Bugs +--------------- +I've done many things to reduce the opportunity for defects. A general +principle is to write code so that defects will be as visible as +possible. I've used several techniques to achieve this. + +I put assertions at key points where defects seem likely or where +corruption due to a defect is likely to be visible. I've also put +assertions where violations of the interface are likely. In emulators +where I am unsure of exact hardware operation in a particular case, I +output a debug-only message noting that this has occurred; many times I +haven't implemented a hardware feature because nothing uses it. I've +made code brittle where there is no clear reason flexibility; code +written to handle every possibility sacrifices quality and reliability +to handle vaguely defined situations. + + +Flexibility through indirection +------------------------------- +I've tried to allow the most flexibility of modules by using indirection +to allow extension by the user. This keeps each module simpler and more +focused on its unique task. + +The classic emulators use Multi_Buffer, which potentially allows a +separate Blip_Buffer for each channel. This keeps emulators free of +typical code to allow output in mono, stereo, panning, etc. + +All emulators use a reader object to access file data, allowing it to be +stored in a regular file, compressed archive, memory, or generated +on-the-fly. Again, the library can be kept free of the particulars of +file access and changes required to support new formats. + + +Emulators in general +-------------------- +When I wrote the first NES sound emulator, I stored most of the state in +an emulator-specific format, with significant redundancy. In the +register write function I decoded everything into named variables. I +became tired of the verbosity and wanted to more closely model the +hardware, so I moved to a style of storing the last written value to +each register, along with as little other state as possible, mostly the +internal hardware registers. While this involves slightly more +recalculation, in most cases the emulation code is of comparable size. +It also makes state save/restore (for use in a full emulator) much +simpler. Finally, it makes debugging easier since the hardware registers +used in emulation are obvious. + + +CPU Cores +--------- +I've spent lots of time coming up with techniques to optimize the CPU +cores. Some of the most important: execute multiple instructions during +an emulation call, keep state in local variables to allow register +assignment, optimize state representation for most common instructions, +defer status flag calculation until actually needed, read program code +directly without a call to the memory read function, always pre-fetch +the operand byte before decoding instruction, and emulate instructions +using common blocks of code. + +I've successfully used Nes_Cpu in a fairly complete NES emulator, and +I'd like to make all the CPU emulators suitable for use in emulators. It +seems a waste for them to be used only for the small amount of emulation +necessary for game music files. + +I debugged the CPU cores by writing a test shell that ran them in +parallel with other CPU cores and compared all memory accesses and +processor states at each step. This provided good value at little cost. + +The CPU mapping page size is adjustable to allow the best tradeoff +between memory/cache usage and handler granularity. The interface allows +code to be somewhat independent of the page size. + +I optimize program memory accesses to direct reads rather than calls to +the memory read function. My assumption is that it would be difficult to +get useful code out of hardware I/O addresses, so no software will +intentionally execute out of I/O space. Since the page size can be +changed easily, most program memory mapping schemes can be accommodated. +This greatly reduces memory access function calls. + diff --git a/snes_spc/snes_spc.vcproj b/game-music-emu/game-music-emu.vcproj similarity index 54% rename from snes_spc/snes_spc.vcproj rename to game-music-emu/game-music-emu.vcproj index ea0e62bf4..00b05d5fd 100644 --- a/snes_spc/snes_spc.vcproj +++ b/game-music-emu/game-music-emu.vcproj @@ -2,9 +2,9 @@ @@ -46,7 +46,7 @@ PreprocessorDefinitions="WIN32;_DEBUG;_LIB" MinimalRebuild="true" BasicRuntimeChecks="3" - RuntimeLibrary="3" + RuntimeLibrary="1" UsePrecompiledHeader="0" WarningLevel="3" Detect64BitPortabilityProblems="true" @@ -80,69 +80,6 @@ Name="VCPostBuildEventTool" /> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -588,31 +438,183 @@ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -622,43 +624,243 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -666,14 +868,6 @@ RelativePath=".\CMakeLists.txt" > - - - - diff --git a/game-music-emu/gme.txt b/game-music-emu/gme.txt new file mode 100644 index 000000000..2c963d8e3 --- /dev/null +++ b/game-music-emu/gme.txt @@ -0,0 +1,464 @@ +Game_Music_Emu 0.5.2 +-------------------- +Author : Shay Green +Website: http://www.slack.net/~ant/libs/ +Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + +Contents +-------- +* Overview +* C and C++ interfaces +* Function reference +* Error handling +* Emulator types +* M3U playlist support +* Information fields +* Track length +* Loading file data +* Sound parameters +* VGM/GYM YM2413 & YM2612 FM sound +* Modular construction +* Obscure features +* Solving problems +* Deprecated features +* Thanks + + +Overview +-------- +This library can open game music files, play tracks, and read game and +track information tags. To play a game music file, do the following: + +* Open the file with gme_open_file() +* Start a track with gme_start_track(); +* Generate samples as needed with gme_play() +* Play samples through speaker using your operating system +* Delete emulator when done with gme_delete() + +Your code must arrange for the generated samples to be played through +the computer's speaker using whatever method your operating system +requires. + +There are many additional features available; you can: + +* Determine of the type of a music file without opening it with +gme_identify_*() +* Load just the file's information tags with gme_info_only +* Load from a block of memory rather than a file with gme_load_data() +* Arrange for a fade-out at a particular time with gme_set_fade +* Find when a track has ended with gme_track_ended() +* Seek to a new time in the track with gme_seek() +* Load an extended m3u playlist with gme_load_m3u() +* Get a list of the voices (channels) and mute them individually with +gme_voice_names() and gme_mute_voice() +* Change the playback tempo without affecting pitch with gme_set_tempo() +* Adjust treble/bass equalization with gme_set_equalizer() +* Associate your own data with an emulator and later get it back with +gme_set_user_data() +* Register a function of yours to be called back when the emulator is +deleted with gme_set_user_cleanup() + +Refer to gme.h for a comprehensive summary of features. + + +C and C++ interfaces +-------------------- +While the library is written in C++, an extensive C interface is +provided in gme.h. This C interface will be referred to throughout this +documentation unless a feature is only available in the full C++ +interface. All C interface functions and other names have the gme_ +prefix, so you can recognize a C++-only feature by the lack of gme_ in +the names used (contact me if you'd like a feature added to the C +interface). If you're building a shared library, I highly recommend +sticking to the C interface only, because it will be more stable between +releases of the library than the C++ interface. Finally, the C and C++ +interfaces can be freely mixed without problems. Compare demo/basics.c +with demo/cpp_basics.cpp to see how the C and C++ interfaces translate +between each other. + + +Function reference +------------------ +Read the following header files for a complete reference to functions +and features. The second group of header files can only be used in C++. + +blargg_config.h Library configuration +gme.h C interface (also usable from C++) + +Gme_File.h File loading and track information +Music_Emu.h Track playback and adjustments +Data_Reader.h Custom data readers +Effects_Buffer.h Sound buffer with adjustable stereo echo and panning +M3u_Playlist.h M3U playlist support +Gbs_Emu.h GBS equalizer settings +Nsf_Emu.h NSF equalizer settings +Spc_Emu.h SPC surround disable +Vgm_Emu.h VGM oversampling disable and custom buffer query + + +Error handling +-------------- +Functions which can fail have a return type of gme_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; gme_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. + +When loading a music file in the wrong emulator or trying to load a +non-music file, gme_wrong_file_type is returned. You can check for this +error in C++ like this: + + gme_err_t err = gme_open_file( path, &emu ); + if ( err == gme_wrong_file_type ) + ... + +To check for minor problems, call gme_warning() to get a string +describing the last warning. Your player should allow the user some way +of knowing when this is the case, since these minor errors could affect +playback. Without this information the user can't solve problems as +well. When playing a track, gme_warning() returns minor playback-related +problems (major playback problems end the track immediately and set the +warning string). + + +Emulator types +-------------- +The library includes several game music emulators that each support a +different file type. Each is identified by a gme_type_t constant defined +in gme.h, for example gme_nsf_emu is for the NSF emulator. If you use +gme_open_file() or gme_open_data(), the library does the work of +determining the file type and creating an appropriate emulator. If you +want more control over this process, read on. + +There are two basic ways to identify a game music file's type: look at +its file extension, or read the header data. The library includes +functions to help with both methods. The first is preferable because it +is fast and the most common way to identify files. Sometimes the +extension is lost or wrong, so the header must be read. + +Use gme_identify_extension() to find the correct game music type based +on a filename. To identify a file based on its extension and header +contents, use gme_identify_file(). If you read the header data yourself, +use gme_identify_header(). + +If you want to remove support for some music types to reduce your +executable size, edit GME_TYPE_LIST in blargg_config.h. For example, to +support just NSF and GBS, use this: + + #define GME_TYPE_LIST gme_nsf_type, gme_gbs_type + + +M3U playlist support +-------------------- +The library supports playlists in an extended m3u format with +gme_load_m3u() to give track names and times to multi-song formats: AY, +GBS, HES, KSS, NSF, NSFE, and SAP. Some aspects of the file format +itself is not well-defined so some m3u files won't work properly +(particularly those provided with KSS files). Only m3u files referencing +a single file are supported; your code must handle m3u files covering +more than one game music file, though it can use the built-in m3u +parsing provided by the library. + + +Information fields +------------------ +Support is provided for the various text fields and length information +in a file with gme_track_info(). If you just need track information for +a file (for example, building a playlist), use gme_new_info() in place +of gme_new_emu(), load the file normally, then you can access the track +count and info, but nothing else. + + M3U VGM GYM SPC SAP NSFE NSF AY GBS HES KSS + ------------------------------------------------------- +Track Count | * * * * * * * * * + | +System | * * * * * * * * * * + | +Game | * * * * * * * + | +Song | * * * * * * * + | +Author | * * * * * * * * + | +Copyright | * * * * * * * * + | +Comment | * * * * + | +Dumper | * * * * + | +Length | * * * * * * + | +Intro Length| * * * + | +Loop Length | * * * + +As listed above, the HES and KSS file formats don't include a track +count, and tracks are often scattered over the 0-255 range, so an m3u +playlist for these is a must. + +Unavailable text fields are set to an empty string and times to -1. Your +code should be prepared for any combination of available and unavailable +fields, as a particular music file might not use all of the supported +fields listed above. + +Currently text fields are truncated to 255 characters. Obscure fields of +some formats are not currently decoded; contact me if you want one +added. + + +Track length +------------ +The library leaves it up to you as to when to stop playing a track. You +can ask for available length information and then tell the library what +time it should start fading the track with gme_set_fade(). By default it +also continually checks for 6 or more seconds of silence to mark the end +of a track. Here is a reasonable algorithm you can use to decide how +long to play a track: + +* If the track length is > 0, use it +* If the loop length > 0, play for intro + loop * 2 +* Otherwise, default to 2.5 minutes (150000 msec) + +If you want to play a track longer than normal, be sure the loop length +isn't zero. See Music_Player.cpp around line 145 for example code. + +By default, the library skips silence at the beginning of a track. It +also continually checks for the end of a non-looping track by watching +for 6 seconds of unbroken silence. When doing this is scans *ahead* by +several seconds so it can report the end of the track after only one +second of silence has actually played. This feature can be disabled with +gme_ignore_silence(). + + +Loading file data +----------------- +The library allows file data to be loaded in many different ways. All +load functions return an error which you should check. The following +examples assume these variables: + + Music_Emu* emu; + gme_err_t error; + +If you're letting the library determine a file's type, you can use +either gme_open_file() or gme_open_data(): + + error = gme_open_file( pathname, &emu ); + error = gme_open_data( pointer, size, &emu ); + +If you're manually determining file type and using used gme_new_emu() to +create an emulator, you can use the following methods of loading: + +* From a block of memory: + + error = gme_load_data( emu, pointer, size ); + +* Have library call your function to read data: + + gme_err_t my_read( void* my_data, void* out, long count ) + { + // code that reads 'count' bytes into 'out' buffer + // and return 0 if no error + } + + error = gme_load_custom( emu, my_read, file_size, my_data ); + +* If you must load the file data into memory yourself, you can have the +library use your data directly *without* making a copy. If you do this, +you must not free the data until you're done playing the file. + + error = emu->load_mem( pointer, size ); + +* If you've already read the first bytes of a file (perhaps to determine +the file type) and want to avoid seeking back to the beginning for +performance reasons, use Remaining_Reader: + + Std_File_Reader in; + error = in.open( file_path ); + + char header [4]; + error = in.read( &header, sizeof header ); + ... + + Remaining_Reader rem( &header, sizeof header, &in ); + error = emu->load( rem ); + +If you merely need access to a file's header after loading, use the +emulator-specific header() functions, after casting the Music_Emu +pointer to the specific emulator's type. This example examines the +chip_flags field of the header if it's an NSF file: + + if ( music_emu->type() == gme_nsf_type ) + { + Nsf_Emu* nsf_emu = (Nsf_Emu*) music_emu; + if ( nsf_emu->header().chip_flags & 0x01 ) + ... + } + +Contact me if you want more information about loading files. + + +Sound parameters +---------------- +All emulators support an arbitrary output sampling rate. A rate of 44100 +Hz should work well on most systems. Since band-limited synthesis is +used, a sampling rate above 48000 Hz is not necessary and will actually +reduce sound quality and performance. + +All emulators also support adjustable gain, mainly for the purpose of +getting consistent volume between different music formats and avoiding +excessive modulation. The gain can only be set *before* setting the +emulator's sampling rate, so it's not useful as a general volume +control. The default gains of emulators are set so that they give +generally similar volumes, though some soundtracks are significantly +louder or quieter than normal. + +Some emulators support adjustable treble and bass frequency equalization +(AY, GBS, HES, KSS, NSF, NSFE, SAP, VGM) using set_equalizer(). +Parameters are specified using gme_equalizer_t eq = { treble_dB, +bass_freq }. Treble_dB sets the treble level (in dB), where 0.0 dB gives +normal treble; -200.0 dB is quite muffled, and 5.0 dB emphasizes treble +for an extra crisp sound. Bass_freq sets the frequency where bass +response starts to diminish; 15 Hz is normal, 0 Hz gives maximum bass, +and 15000 Hz removes all bass. For example, the following makes the +sound extra-crisp but lacking bass: + + gme_equalizer_t eq = { 5.0, 1000 }; + gme_set_equalizer( music_emu, &eq ); + +Each emulator's equalization defaults to approximate the particular +console's sound quality; this default can be determined by calling +equalizer() just after creating the emulator. The Music_Emu::tv_eq +profile gives sound as if coming from a TV speaker, and some emulators +include other profiles for different versions of the system. For +example, to use Famicom sound equalization with the NSF emulator, do the +following: + + music_emu->set_equalizer( Nsf_Emu::famicom_eq ); + + +VGM/GYM YM2413 & YM2612 FM sound +-------------------------------- +The library plays Sega Genesis/Mega Drive music using a YM2612 FM sound +chip emulator based on the Gens project. Because this has some +inaccuracies, other YM2612 emulators can be used in its place by +re-implementing the interface in YM2612_Emu.h. Available on my website +is a modified version of MAME's YM2612 emulator, which sounds better in +some ways and whose author is still making improvements. + +VGM music files using the YM2413 FM sound chip are also supported, but a +YM2413 emulator isn't included with the library due to technical +reasons. I have put one of the available YM2413 emulators on my website +that can be used directly. + + +Modular construction +-------------------- +The library is made of many fairly independent modules. If you're using +only one music file emulator, you can eliminate many of the library +sources from your program. Refer to the files list in readme.txt to get +a general idea of what can be removed, and be sure to edit GME_TYPE_LIST +(see "Emulator types" above). Post to the forum if you'd like me to put +together a smaller version for a particular use, as this only takes me a +few minutes to do. + +If you want to use one of the individual sound chip emulators (or CPU +cores) in your own console emulator, first check the libraries page on +my website since I have released several of them as stand alone +libraries with included documentation and examples on their use. If you +don't find it as a standalone library, contact me and I'll consider +separating it. + +The "classic" sound chips use my Blip_Buffer library, which greatly +simplifies their implementation and efficiently handles band-limited +synthesis. It is also available as a stand alone library with +documentation and many examples. + + +Obscure features +---------------- +The library's flexibility allows many possibilities. Contact me if you +want help implementing ideas or removing limitations. + +* Uses no global/static variables, allowing multiple instances of any +emulator. This is useful in a music player if you want to allow +simultaneous recording or scanning of other tracks while one is already +playing. This will also be useful if your platform disallows global +data. + +* Emulators that support a custom sound buffer can have *every* voice +routed to a different Blip_Buffer, allowing custom processing on each +voice. For example you could record a Game Boy track as a 4-channel +sound file. + +* Defining BLIP_BUFFER_FAST uses lower quality, less-multiply-intensive +synthesis on "classic" emulators, which might help on some really old +processors. This significantly lowers sound quality and prevents treble +equalization. Try this if your platform's processor isn't fast enough +for normal quality. Even on my ten-year-old 400 MHz Mac, this reduces +processor usage at most by about 0.6% (from 4% to 3.4%), hardly worth +the quality loss. + + +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. + + +Deprecated features +------------------- +The following functions and other features have been deprecated and will +be removed in a future release of the library. Alternatives to the +deprecated features are listed to the right. + +Music_Emu::error_count() warning() +load( header, reader ) see "Loading file data" above +Spc_Emu::trailer() track_info() +Spc_Emu::trailer_size() +Gym_Emu::track_length() track_info() +Vgm_Emu::gd3_data() track_info() +Nsfe_Emu::disable_playlist() clear_playlist() + + +Thanks +------ +Big thanks to Chris Moeller (kode54) for help with library testing and +feedback, for maintaining the Foobar2000 plugin foo_gep based on it, and +for original work on openspc++ that was used when developing Spc_Emu. +Brad Martin's excellent OpenSPC SNES DSP emulator worked well from the +start. Also thanks to Richard Bannister, Mahendra Tallur, Shazz, +nenolod, theHobbit, Johan Samuelsson, and nes6502 for testing, using, +and giving feedback for the library in their respective game music +players. diff --git a/game-music-emu/gme/Ay_Apu.cpp b/game-music-emu/gme/Ay_Apu.cpp new file mode 100644 index 000000000..8c1c43a0f --- /dev/null +++ b/game-music-emu/gme/Ay_Apu.cpp @@ -0,0 +1,395 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Ay_Apu.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Emulation inaccuracies: +// * Noise isn't run when not in use +// * Changes to envelope and noise periods are delayed until next reload +// * Super-sonic tone should attenuate output to about 60%, not 50% + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +unsigned const inaudible_freq = 16384; + +int const period_factor = 16; + +static byte const amp_table [16] = +{ +#define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5) + // With channels tied together and 1K resistor to ground (as datasheet recommends), + // output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step. + ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625), + ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500), + ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000), + ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000), + + /* + // Measured from an AY-3-8910A chip with date code 8611. + + // Direct voltages without any load (very linear) + ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785), + ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032), + ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043), + ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000), + // With only some load + ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876), + ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388), + ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945), + ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000), + */ +#undef ENTRY +}; + +static byte const modes [8] = +{ +#define MODE( a0,a1, b0,b1, c0,c1 ) \ + (a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5) + MODE( 1,0, 1,0, 1,0 ), + MODE( 1,0, 0,0, 0,0 ), + MODE( 1,0, 0,1, 1,0 ), + MODE( 1,0, 1,1, 1,1 ), + MODE( 0,1, 0,1, 0,1 ), + MODE( 0,1, 1,1, 1,1 ), + MODE( 0,1, 1,0, 0,1 ), + MODE( 0,1, 0,0, 0,0 ), +}; + +Ay_Apu::Ay_Apu() +{ + // build full table of the upper 8 envelope waveforms + for ( int m = 8; m--; ) + { + byte* out = env.modes [m]; + int flags = modes [m]; + for ( int x = 3; --x >= 0; ) + { + int amp = flags & 1; + int end = flags >> 1 & 1; + int step = end - amp; + amp *= 15; + for ( int y = 16; --y >= 0; ) + { + *out++ = amp_table [amp]; + amp += step; + } + flags >>= 2; + } + } + + output( 0 ); + volume( 1.0 ); + reset(); +} + +void Ay_Apu::reset() +{ + last_time = 0; + noise.delay = 0; + noise.lfsr = 1; + + osc_t* osc = &oscs [osc_count]; + do + { + osc--; + osc->period = period_factor; + osc->delay = 0; + osc->last_amp = 0; + osc->phase = 0; + } + while ( osc != oscs ); + + for ( int i = sizeof regs; --i >= 0; ) + regs [i] = 0; + regs [7] = 0xFF; + write_data_( 13, 0 ); +} + +void Ay_Apu::write_data_( int addr, int data ) +{ + assert( (unsigned) addr < reg_count ); + + if ( (unsigned) addr >= 14 ) + { + #ifdef dprintf + dprintf( "Wrote to I/O port %02X\n", (int) addr ); + #endif + } + + // envelope mode + if ( addr == 13 ) + { + if ( !(data & 8) ) // convert modes 0-7 to proper equivalents + data = (data & 4) ? 15 : 9; + env.wave = env.modes [data - 7]; + env.pos = -48; + env.delay = 0; // will get set to envelope period in run_until() + } + regs [addr] = data; + + // handle period changes accurately + int i = addr >> 1; + if ( i < osc_count ) + { + blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100L * period_factor) + + regs [i * 2] * period_factor; + if ( !period ) + period = period_factor; + + // adjust time of next timer expiration based on change in period + osc_t& osc = oscs [i]; + if ( (osc.delay += period - osc.period) < 0 ) + osc.delay = 0; + osc.period = period; + } + + // TODO: same as above for envelope timer, and it also has a divide by two after it +} + +int const noise_off = 0x08; +int const tone_off = 0x01; + +void Ay_Apu::run_until( blip_time_t final_end_time ) +{ + require( final_end_time >= last_time ); + + // noise period and initial values + blip_time_t const noise_period_factor = period_factor * 2; // verified + blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor; + if ( !noise_period ) + noise_period = noise_period_factor; + blip_time_t const old_noise_delay = noise.delay; + blargg_ulong const old_noise_lfsr = noise.lfsr; + + // envelope period + blip_time_t const env_period_factor = period_factor * 2; // verified + blip_time_t env_period = (regs [12] * 0x100L + regs [11]) * env_period_factor; + if ( !env_period ) + env_period = env_period_factor; // same as period 1 on my AY chip + if ( !env.delay ) + env.delay = env_period; + + // run each osc separately + for ( int index = 0; index < osc_count; index++ ) + { + osc_t* const osc = &oscs [index]; + int osc_mode = regs [7] >> index; + + // output + Blip_Buffer* const osc_output = osc->output; + if ( !osc_output ) + continue; + osc_output->set_modified(); + + // period + int half_vol = 0; + blip_time_t inaudible_period = (blargg_ulong) (osc_output->clock_rate() + + inaudible_freq) / (inaudible_freq * 2); + if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) + { + half_vol = 1; // Actually around 60%, but 50% is close enough + osc_mode |= tone_off; + } + + // envelope + blip_time_t start_time = last_time; + blip_time_t end_time = final_end_time; + int const vol_mode = regs [0x08 + index]; + int volume = amp_table [vol_mode & 0x0F] >> half_vol; + int osc_env_pos = env.pos; + if ( vol_mode & 0x10 ) + { + volume = env.wave [osc_env_pos] >> half_vol; + // use envelope only if it's a repeating wave or a ramp that hasn't finished + if ( !(regs [13] & 1) || osc_env_pos < -32 ) + { + end_time = start_time + env.delay; + if ( end_time >= final_end_time ) + end_time = final_end_time; + + //if ( !(regs [12] | regs [11]) ) + // dprintf( "Used envelope period 0\n" ); + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + } + else if ( !volume ) + { + osc_mode = noise_off | tone_off; + } + + // tone time + blip_time_t const period = osc->period; + blip_time_t time = start_time + osc->delay; + if ( osc_mode & tone_off ) // maintain tone's phase when off + { + blargg_long count = (final_end_time - time + period - 1) / period; + time += count * period; + osc->phase ^= count & 1; + } + + // noise time + blip_time_t ntime = final_end_time; + blargg_ulong noise_lfsr = 1; + if ( !(osc_mode & noise_off) ) + { + ntime = start_time + old_noise_delay; + noise_lfsr = old_noise_lfsr; + //if ( (regs [6] & 0x1F) == 0 ) + // dprintf( "Used noise period 0\n" ); + } + + // The following efficiently handles several cases (least demanding first): + // * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC + // * Just tone or just noise, envelope disabled + // * Envelope controlling tone and/or noise + // * Tone and noise disabled, envelope enabled with high frequency + // * Tone and noise together + // * Tone and noise together with envelope + + // This loop only runs one iteration if envelope is disabled. If envelope + // is being used as a waveform (tone and noise disabled), this loop will + // still be reasonably efficient since the bulk of it will be skipped. + while ( 1 ) + { + // current amplitude + int amp = 0; + if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) ) + amp = volume; + { + int delta = amp - osc->last_amp; + if ( delta ) + { + osc->last_amp = amp; + synth_.offset( start_time, delta, osc_output ); + } + } + + // Run wave and noise interleved with each catching up to the other. + // If one or both are disabled, their "current time" will be past end time, + // so there will be no significant performance hit. + if ( ntime < end_time || time < end_time ) + { + // Since amplitude was updated above, delta will always be +/- volume, + // so we can avoid using last_amp every time to calculate the delta. + int delta = amp * 2 - volume; + int delta_non_zero = delta != 0; + int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 ); + do + { + // run noise + blip_time_t end = end_time; + if ( end_time > time ) end = time; + if ( phase & delta_non_zero ) + { + while ( ntime <= end ) // must advance *past* time to avoid hang + { + int changed = noise_lfsr + 1; + noise_lfsr = ((blargg_ulong)-(blargg_long)(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); + if ( changed & 2 ) + { + delta = -delta; + synth_.offset( ntime, delta, osc_output ); + } + ntime += noise_period; + } + } + else + { + // 20 or more noise periods on average for some music + blargg_long remain = end - ntime; + blargg_long count = remain / noise_period; + if ( remain >= 0 ) + ntime += noise_period + count * noise_period; + } + + // run tone + end = end_time; + if ( end_time > ntime ) end = ntime; + if ( noise_lfsr & delta_non_zero ) + { + while ( time < end ) + { + delta = -delta; + synth_.offset( time, delta, osc_output ); + time += period; + //phase ^= 1; + } + //assert( phase == (delta > 0) ); + phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1); + // (delta > 0) + } + else + { + // loop usually runs less than once + //SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period ); + + while ( time < end ) + { + time += period; + phase ^= 1; + } + } + } + while ( time < end_time || ntime < end_time ); + + osc->last_amp = (delta + volume) >> 1; + if ( !(osc_mode & tone_off) ) + osc->phase = phase; + } + + if ( end_time >= final_end_time ) + break; // breaks first time when envelope is disabled + + // next envelope step + if ( ++osc_env_pos >= 0 ) + osc_env_pos -= 32; + volume = env.wave [osc_env_pos] >> half_vol; + + start_time = end_time; + end_time += env_period; + if ( end_time > final_end_time ) + end_time = final_end_time; + } + osc->delay = time - final_end_time; + + if ( !(osc_mode & noise_off) ) + { + noise.delay = ntime - final_end_time; + noise.lfsr = noise_lfsr; + } + } + + // TODO: optimized saw wave envelope? + + // maintain envelope phase + blip_time_t remain = final_end_time - last_time - env.delay; + if ( remain >= 0 ) + { + blargg_long count = (remain + env_period) / env_period; + env.pos += count; + if ( env.pos >= 0 ) + env.pos = (env.pos & 31) - 32; + remain -= count * env_period; + assert( -remain <= env_period ); + } + env.delay = -remain; + assert( env.delay > 0 ); + assert( env.pos < 0 ); + + last_time = final_end_time; +} diff --git a/game-music-emu/gme/Ay_Apu.h b/game-music-emu/gme/Ay_Apu.h new file mode 100644 index 000000000..31956939e --- /dev/null +++ b/game-music-emu/gme/Ay_Apu.h @@ -0,0 +1,107 @@ +// AY-3-8910 sound chip emulator + +// Game_Music_Emu 0.5.2 +#ifndef AY_APU_H +#define AY_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Ay_Apu { +public: + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Reset sound chip + void reset(); + + // Write to register at specified time + enum { reg_count = 16 }; + void write( blip_time_t time, int addr, int data ); + + // Run sound to specified time, end current time frame, then start a new + // time frame at time 0. Time frames have no effect on emulation and each + // can be whatever length is convenient. + void end_frame( blip_time_t length ); + +// Additional features + + // Set sound output of specific oscillator to buffer, where index is + // 0, 1, or 2. If buffer is NULL, the specified oscillator is muted. + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see documentation) + void treble_eq( blip_eq_t const& ); + +public: + Ay_Apu(); + typedef unsigned char byte; +private: + struct osc_t + { + blip_time_t period; + blip_time_t delay; + short last_amp; + short phase; + Blip_Buffer* output; + } oscs [osc_count]; + blip_time_t last_time; + byte latch; + byte regs [reg_count]; + + struct { + blip_time_t delay; + blargg_ulong lfsr; + } noise; + + struct { + blip_time_t delay; + byte const* wave; + int pos; + byte modes [8] [48]; // values already passed through volume table + } env; + + void run_until( blip_time_t ); + void write_data_( int addr, int data ); +public: + enum { amp_range = 255 }; + Blip_Synth synth_; +}; + +inline void Ay_Apu::volume( double v ) { synth_.volume( 0.7 / osc_count / amp_range * v ); } + +inline void Ay_Apu::treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); } + +inline void Ay_Apu::write( blip_time_t time, int addr, int data ) +{ + run_until( time ); + write_data_( addr, data ); +} + +inline void Ay_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Ay_Apu::output( Blip_Buffer* buf ) +{ + osc_output( 0, buf ); + osc_output( 1, buf ); + osc_output( 2, buf ); +} + +inline void Ay_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +#endif diff --git a/game-music-emu/gme/Ay_Cpu.cpp b/game-music-emu/gme/Ay_Cpu.cpp new file mode 100644 index 000000000..b4487d9c9 --- /dev/null +++ b/game-music-emu/gme/Ay_Cpu.cpp @@ -0,0 +1,1665 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +/* +Last validated with zexall 2006.11.21 5:26 PM +* Doesn't implement the R register or immediate interrupt after EI. +* Address wrap-around isn't completely correct, but is prevented from crashing emulator. +*/ + +#include "Ay_Cpu.h" + +#include "blargg_endian.h" +#include + +//#include "z80_cpu_log.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define SYNC_TIME() (void) (s.time = s_time) +#define RELOAD_TIME() (void) (s_time = s.time) + +// Callbacks to emulator + +#define CPU_OUT( cpu, addr, data, TIME )\ + ay_cpu_out( cpu, TIME, addr, data ) + +#define CPU_IN( cpu, addr, TIME )\ + ay_cpu_in( cpu, addr ) + +#include "blargg_source.h" + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +#define SZ28P( n ) szpc [n] +#define SZ28PC( n ) szpc [n] +#define SZ28C( n ) (szpc [n] & ~P04) +#define SZ28( n ) SZ28C( n ) + +#define SET_R( n ) (void) (r.r = n) +#define GET_R() (r.r) + +Ay_Cpu::Ay_Cpu() +{ + state = &state_; + for ( int i = 0x100; --i >= 0; ) + { + int even = 1; + for ( int p = i; p; p >>= 1 ) + even ^= p; + int n = (i & (S80 | F20 | F08)) | ((even & 1) * P04); + szpc [i] = n; + szpc [i + 0x100] = n | C01; + } + szpc [0x000] |= Z40; + szpc [0x100] |= Z40; +} + +void Ay_Cpu::reset( void* m ) +{ + mem = (uint8_t*) m; + + check( state == &state_ ); + state = &state_; + state_.time = 0; + state_.base = 0; + end_time_ = 0; + + memset( &r, 0, sizeof r ); +} + +#define TIME (s_time + s.base) +#define READ_PROG( addr ) (mem [addr]) +#define INSTR( offset ) READ_PROG( pc + (offset) ) +#define GET_ADDR() GET_LE16( &READ_PROG( pc ) ) +#define READ( addr ) READ_PROG( addr ) +#define WRITE( addr, data ) (void) (READ_PROG( addr ) = data) +#define READ_WORD( addr ) GET_LE16( &READ_PROG( addr ) ) +#define WRITE_WORD( addr, data ) SET_LE16( &READ_PROG( addr ), data ) +#define IN( addr ) CPU_IN( this, addr, TIME ) +#define OUT( addr, data ) CPU_OUT( this, addr, data, TIME ) + +#if BLARGG_BIG_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + +//#define R16( n, shift, offset ) (r16_ [((n) >> shift) - (offset >> shift)]) + +// help compiler see that it can just adjust stack offset, saving an extra instruction +#define R16( n, shift, offset )\ + (*(uint16_t*) ((char*) r16_ - (offset >> (shift - 1)) + ((n) >> (shift - 1)))) + +#define CASE5( a, b, c, d, e ) case 0x##a:case 0x##b:case 0x##c:case 0x##d:case 0x##e +#define CASE6( a, b, c, d, e, f ) CASE5( a, b, c, d, e ): case 0x##f +#define CASE7( a, b, c, d, e, f, g ) CASE6( a, b, c, d, e, f ): case 0x##g +#define CASE8( a, b, c, d, e, f, g, h ) CASE7( a, b, c, d, e, f, g ): case 0x##h + +// high four bits are $ED time - 8, low four bits are $DD/$FD time - 8 +static byte const ed_dd_timing [0x100] = { +//0 1 2 3 4 5 6 7 8 9 A B C D E F +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x0C,0x02,0x00,0x00,0x03,0x00,0x00,0x07,0x0C,0x02,0x00,0x00,0x03,0x00, +0x00,0x00,0x00,0x00,0x0F,0x0F,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0, +0x4B,0x4B,0x7B,0xCB,0x0B,0x6B,0x00,0x0B,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00, +0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00,0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x00,0x0F,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; + +bool Ay_Cpu::run( cpu_time_t end_time ) +{ + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + bool warning = false; + + typedef BOOST::int8_t int8_t; + + union { + regs_t rg; + pairs_t rp; + uint8_t r8_ [8]; // indexed + uint16_t r16_ [4]; + }; + rg = this->r.b; + + cpu_time_t s_time = s.time; + uint8_t* const mem = this->mem; // cache + fuint16 pc = r.pc; + fuint16 sp = r.sp; + fuint16 ix = r.ix; // TODO: keep in memory for direct access? + fuint16 iy = r.iy; + int flags = r.b.flags; + + goto loop; +jr_not_taken: + s_time -= 5; + goto loop; +call_not_taken: + s_time -= 7; +jp_not_taken: + pc += 2; +loop: + + check( (unsigned long) pc < 0x10000 ); + check( (unsigned long) sp < 0x10000 ); + check( (unsigned) flags < 0x100 ); + check( (unsigned) ix < 0x10000 ); + check( (unsigned) iy < 0x10000 ); + + fuint8 opcode; + opcode = READ_PROG( pc ); + pc++; + + static byte const base_timing [0x100] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,10, 7, 6, 4, 4, 7, 4, 4,11, 7, 6, 4, 4, 7, 4, // 0 + 13,10, 7, 6, 4, 4, 7, 4,12,11, 7, 6, 4, 4, 7, 4, // 1 + 12,10,16, 6, 4, 4, 7, 4,12,11,16, 6, 4, 4, 7, 4, // 2 + 12,10,13, 6,11,11,10, 4,12,11,13, 6, 4, 4, 7, 4, // 3 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 4 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 5 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 6 + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, // 7 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 8 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 9 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // A + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // B + 11,10,10,10,17,11, 7,11,11,10,10, 8,17,17, 7,11, // C + 11,10,10,11,17,11, 7,11,11, 4,10,11,17, 8, 7,11, // D + 11,10,10,19,17,11, 7,11,11, 4,10, 4,17, 8, 7,11, // E + 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F + }; + + fuint16 data; + data = base_timing [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = READ_PROG( pc ); + + #ifdef Z80_CPU_LOG_H + //log_opcode( opcode, READ_PROG( pc ) ); + z80_log_regs( rg.a, rp.bc, rp.de, rp.hl, sp, ix, iy ); + z80_cpu_log( "new", pc - 1, opcode, READ_PROG( pc ), + READ_PROG( pc + 1 ), READ_PROG( pc + 2 ) ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Common + + case 0x00: // NOP + CASE7( 40, 49, 52, 5B, 64, 6D, 7F ): // LD B,B etc. + goto loop; + + case 0x08:{// EX AF,AF' + int temp = r.alt.b.a; + r.alt.b.a = rg.a; + rg.a = temp; + + temp = r.alt.b.flags; + r.alt.b.flags = flags; + flags = temp; + goto loop; + } + + case 0xD3: // OUT (imm),A + pc++; + OUT( data + rg.a * 0x100, rg.a ); + goto loop; + + case 0x2E: // LD L,imm + pc++; + rg.l = data; + goto loop; + + case 0x3E: // LD A,imm + pc++; + rg.a = data; + goto loop; + + case 0x3A:{// LD A,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rg.a = READ( addr ); + goto loop; + } + +// Conditional + +#define ZERO (flags & Z40) +#define CARRY (flags & C01) +#define EVEN (flags & P04) +#define MINUS (flags & S80) + +// JR +#define JR( cond ) {\ + int disp = (BOOST::int8_t) data;\ + pc++;\ + if ( !(cond) )\ + goto jr_not_taken;\ + pc += disp;\ + goto loop;\ +} + + case 0x20: JR( !ZERO ) // JR NZ,disp + case 0x28: JR( ZERO ) // JR Z,disp + case 0x30: JR( !CARRY ) // JR NC,disp + case 0x38: JR( CARRY ) // JR C,disp + case 0x18: JR( true ) // JR disp + + case 0x10:{// DJNZ disp + int temp = rg.b - 1; + rg.b = temp; + JR( temp ) + } + +// JP +#define JP( cond ) if ( !(cond) ) goto jp_not_taken; pc = GET_ADDR(); goto loop; + + case 0xC2: JP( !ZERO ) // JP NZ,addr + case 0xCA: JP( ZERO ) // JP Z,addr + case 0xD2: JP( !CARRY ) // JP NC,addr + case 0xDA: JP( CARRY ) // JP C,addr + case 0xE2: JP( !EVEN ) // JP PO,addr + case 0xEA: JP( EVEN ) // JP PE,addr + case 0xF2: JP( !MINUS ) // JP P,addr + case 0xFA: JP( MINUS ) // JP M,addr + + case 0xC3: // JP addr + pc = GET_ADDR(); + goto loop; + + case 0xE9: // JP HL + pc = rp.hl; + goto loop; + +// RET +#define RET( cond ) if ( cond ) goto ret_taken; s_time -= 6; goto loop; + + case 0xC0: RET( !ZERO ) // RET NZ + case 0xC8: RET( ZERO ) // RET Z + case 0xD0: RET( !CARRY ) // RET NC + case 0xD8: RET( CARRY ) // RET C + case 0xE0: RET( !EVEN ) // RET PO + case 0xE8: RET( EVEN ) // RET PE + case 0xF0: RET( !MINUS ) // RET P + case 0xF8: RET( MINUS ) // RET M + + case 0xC9: // RET + ret_taken: + pc = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// CALL +#define CALL( cond ) if ( cond ) goto call_taken; goto call_not_taken; + + case 0xC4: CALL( !ZERO ) // CALL NZ,addr + case 0xCC: CALL( ZERO ) // CALL Z,addr + case 0xD4: CALL( !CARRY ) // CALL NC,addr + case 0xDC: CALL( CARRY ) // CALL C,addr + case 0xE4: CALL( !EVEN ) // CALL PO,addr + case 0xEC: CALL( EVEN ) // CALL PE,addr + case 0xF4: CALL( !MINUS ) // CALL P,addr + case 0xFC: CALL( MINUS ) // CALL M,addr + + case 0xCD:{// CALL addr + call_taken: + fuint16 addr = pc + 2; + pc = GET_ADDR(); + sp = uint16_t (sp - 2); + WRITE_WORD( sp, addr ); + goto loop; + } + + case 0xFF: // RST + if ( (pc - 1) > 0xFFFF ) + { + pc = uint16_t (pc - 1); + s_time -= 11; + goto loop; + } + CASE7( C7, CF, D7, DF, E7, EF, F7 ): + data = pc; + pc = opcode & 0x38; + goto push_data; + +// PUSH/POP + case 0xF5: // PUSH AF + data = rg.a * 0x100u + flags; + goto push_data; + + case 0xC5: // PUSH BC + case 0xD5: // PUSH DE + case 0xE5: // PUSH HL + data = R16( opcode, 4, 0xC5 ); + push_data: + sp = uint16_t (sp - 2); + WRITE_WORD( sp, data ); + goto loop; + + case 0xF1: // POP AF + flags = READ( sp ); + rg.a = READ( sp + 1 ); + sp = uint16_t (sp + 2); + goto loop; + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL + R16( opcode, 4, 0xC1 ) = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// ADC/ADD/SBC/SUB + case 0x96: // SUB (HL) + case 0x86: // ADD (HL) + flags &= ~C01; + case 0x9E: // SBC (HL) + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_data; + + case 0xD6: // SUB A,imm + case 0xC6: // ADD imm + flags &= ~C01; + case 0xDE: // SBC A,imm + case 0xCE: // ADC imm + pc++; + goto adc_data; + + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // SUB r + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // ADD r + flags &= ~C01; + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // SBC r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // ADC r + data = R8( opcode & 7, 0 ); + adc_data: { + int result = data + (flags & C01); + data ^= rg.a; + flags = opcode >> 3 & N02; // bit 4 is set in subtract opcodes + if ( flags ) + result = -result; + result += rg.a; + data ^= result; + flags |=(data & H10) | + ((data - -0x80) >> 6 & V04) | + SZ28C( result & 0x1FF ); + rg.a = result; + goto loop; + } + +// CP + case 0xBE: // CP (HL) + data = READ( rp.hl ); + goto cp_data; + + case 0xFE: // CP imm + pc++; + goto cp_data; + + CASE7( B8, B9, BA, BB, BC, BD, BF ): // CP r + data = R8( opcode, 0xB8 ); + cp_data: { + int result = rg.a - data; + flags = N02 | (data & (F20 | F08)) | (result >> 8 & C01); + data ^= rg.a; + flags |=(((result ^ rg.a) & data) >> 5 & V04) | + (((data & H10) ^ result) & (S80 | H10)); + if ( (uint8_t) result ) + goto loop; + flags |= Z40; + goto loop; + } + +// ADD HL,rp + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_data; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( opcode, 4, 0x09 ); + add_hl_data: { + blargg_ulong sum = rp.hl + data; + data ^= rp.hl; + rp.hl = sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((data ^ sum) >> 8 & H10); + goto loop; + } + + case 0x27:{// DAA + int a = rg.a; + if ( a > 0x99 ) + flags |= C01; + + int adjust = 0x60 & -(flags & C01); + + if ( flags & H10 || (a & 0x0F) > 9 ) + adjust |= 0x06; + + if ( flags & N02 ) + adjust = -adjust; + a += adjust; + + flags = (flags & (C01 | N02)) | + ((rg.a ^ a) & H10) | + SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + /* + case 0x27:{// DAA + // more optimized, but probably not worth the obscurity + int f = (rg.a + (0xFF - 0x99)) >> 8 | flags; // (a > 0x99 ? C01 : 0) | flags + int adjust = 0x60 & -(f & C01); // f & C01 ? 0x60 : 0 + + if ( (((rg.a + (0x0F - 9)) ^ rg.a) | f) & H10 ) // flags & H10 || (rg.a & 0x0F) > 9 + adjust |= 0x06; + + if ( f & N02 ) + adjust = -adjust; + int a = rg.a + adjust; + + flags = (f & (N02 | C01)) | ((rg.a ^ a) & H10) | SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + */ + +// INC/DEC + case 0x34: // INC (HL) + data = READ( rp.hl ) + 1; + WRITE( rp.hl, data ); + goto inc_set_flags; + + CASE7( 04, 0C, 14, 1C, 24, 2C, 3C ): // INC r + data = ++R8( opcode >> 3, 0 ); + inc_set_flags: + flags = (flags & C01) | + (((data & 0x0F) - 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x80 ) + goto loop; + flags |= V04; + goto loop; + + case 0x35: // DEC (HL) + data = READ( rp.hl ) - 1; + WRITE( rp.hl, data ); + goto dec_set_flags; + + CASE7( 05, 0D, 15, 1D, 25, 2D, 3D ): // DEC r + data = --R8( opcode >> 3, 0 ); + dec_set_flags: + flags = (flags & C01) | N02 | + (((data & 0x0F) + 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x7F ) + goto loop; + flags |= V04; + goto loop; + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( opcode, 4, 0x03 )++; + goto loop; + + case 0x33: // INC SP + sp = uint16_t (sp + 1); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( opcode, 4, 0x0B )--; + goto loop; + + case 0x3B: // DEC SP + sp = uint16_t (sp - 1); + goto loop; + +// AND + case 0xA6: // AND (HL) + data = READ( rp.hl ); + goto and_data; + + case 0xE6: // AND imm + pc++; + goto and_data; + + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // AND r + data = R8( opcode, 0xA0 ); + and_data: + rg.a &= data; + flags = SZ28P( rg.a ) | H10; + goto loop; + +// OR + case 0xB6: // OR (HL) + data = READ( rp.hl ); + goto or_data; + + case 0xF6: // OR imm + pc++; + goto or_data; + + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // OR r + data = R8( opcode, 0xB0 ); + or_data: + rg.a |= data; + flags = SZ28P( rg.a ); + goto loop; + +// XOR + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + goto xor_data; + + case 0xEE: // XOR imm + pc++; + goto xor_data; + + CASE7( A8, A9, AA, AB, AC, AD, AF ): // XOR r + data = R8( opcode, 0xA8 ); + xor_data: + rg.a ^= data; + flags = SZ28P( rg.a ); + goto loop; + +// LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (HL),r + WRITE( rp.hl, R8( opcode, 0x70 ) ); + goto loop; + + CASE6( 41, 42, 43, 44, 45, 47 ): // LD B,r + CASE6( 48, 4A, 4B, 4C, 4D, 4F ): // LD C,r + CASE6( 50, 51, 53, 54, 55, 57 ): // LD D,r + CASE6( 58, 59, 5A, 5C, 5D, 5F ): // LD E,r + CASE6( 60, 61, 62, 63, 65, 67 ): // LD H,r + CASE6( 68, 69, 6A, 6B, 6C, 6F ): // LD L,r + CASE6( 78, 79, 7A, 7B, 7C, 7D ): // LD A,r + R8( opcode >> 3 & 7, 0 ) = R8( opcode & 7, 0 ); + goto loop; + + CASE5( 06, 0E, 16, 1E, 26 ): // LD r,imm + R8( opcode >> 3, 0 ) = data; + pc++; + goto loop; + + case 0x36: // LD (HL),imm + pc++; + WRITE( rp.hl, data ); + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(HL) + R8( opcode >> 3, 8 ) = READ( rp.hl ); + goto loop; + + case 0x01: // LD rp,imm + case 0x11: + case 0x21: + R16( opcode, 4, 0x01 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0x31: // LD sp,imm + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x2A:{// LD HL,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rp.hl = READ_WORD( addr ); + goto loop; + } + + case 0x32:{// LD (addr),A + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE( addr, rg.a ); + goto loop; + } + + case 0x22:{// LD (addr),HL + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, rp.hl ); + goto loop; + } + + case 0x02: // LD (BC),A + case 0x12: // LD (DE),A + WRITE( R16( opcode, 4, 0x02 ), rg.a ); + goto loop; + + case 0x0A: // LD A,(BC) + case 0x1A: // LD A,(DE) + rg.a = READ( R16( opcode, 4, 0x0A ) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + +// Rotate + + case 0x07:{// RLCA + fuint16 temp = rg.a; + temp = (temp << 1) | (temp >> 7); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08 | C01)); + rg.a = temp; + goto loop; + } + + case 0x0F:{// RRCA + fuint16 temp = rg.a; + flags = (flags & (S80 | Z40 | P04)) | + (temp & C01); + temp = (temp << 7) | (temp >> 1); + flags |= temp & (F20 | F08); + rg.a = temp; + goto loop; + } + + case 0x17:{// RLA + blargg_ulong temp = (rg.a << 1) | (flags & C01); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (temp >> 8); + rg.a = temp; + goto loop; + } + + case 0x1F:{// RRA + fuint16 temp = (flags << 7) | (rg.a >> 1); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (rg.a & C01); + rg.a = temp; + goto loop; + } + +// Misc + case 0x2F:{// CPL + fuint16 temp = ~rg.a; + flags = (flags & (S80 | Z40 | P04 | C01)) | + (temp & (F20 | F08)) | + (H10 | N02); + rg.a = temp; + goto loop; + } + + case 0x3F:{// CCF + flags = ((flags & (S80 | Z40 | P04 | C01)) ^ C01) | + (flags << 4 & H10) | + (rg.a & (F20 | F08)); + goto loop; + } + + case 0x37: // SCF + flags = (flags & (S80 | Z40 | P04)) | C01 | + (rg.a & (F20 | F08)); + goto loop; + + case 0xDB: // IN A,(imm) + pc++; + rg.a = IN( data + rg.a * 0x100 ); + goto loop; + + case 0xE3:{// EX (SP),HL + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, rp.hl ); + rp.hl = temp; + goto loop; + } + + case 0xEB:{// EX DE,HL + fuint16 temp = rp.hl; + rp.hl = rp.de; + rp.de = temp; + goto loop; + } + + case 0xD9:{// EXX DE,HL + fuint16 temp = r.alt.w.bc; + r.alt.w.bc = rp.bc; + rp.bc = temp; + + temp = r.alt.w.de; + r.alt.w.de = rp.de; + rp.de = temp; + + temp = r.alt.w.hl; + r.alt.w.hl = rp.hl; + rp.hl = temp; + goto loop; + } + + case 0xF3: // DI + r.iff1 = 0; + r.iff2 = 0; + goto loop; + + case 0xFB: // EI + r.iff1 = 1; + r.iff2 = 1; + // TODO: delayed effect + goto loop; + + case 0x76: // HALT + goto halt; + +//////////////////////////////////////// CB prefix + { + case 0xCB: + unsigned data2; + data2 = INSTR( 1 ); + pc++; + switch ( data ) + { + + // Rotate left + + #define RLC( read, write ) {\ + fuint8 result = read;\ + result = uint8_t (result << 1) | (result >> 7);\ + flags = SZ28P( result ) | (result & C01);\ + write;\ + goto loop;\ + } + + case 0x06: // RLC (HL) + s_time += 7; + data = rp.hl; + rlc_data_addr: + RLC( READ( data ), WRITE( data, result ) ) + + CASE7( 00, 01, 02, 03, 04, 05, 07 ):{// RLC r + uint8_t& reg = R8( data, 0 ); + RLC( reg, reg = result ) + } + + #define RL( read, write ) {\ + fuint16 result = (read << 1) | (flags & C01);\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x16: // RL (HL) + s_time += 7; + data = rp.hl; + rl_data_addr: + RL( READ( data ), WRITE( data, result ) ) + + CASE7( 10, 11, 12, 13, 14, 15, 17 ):{// RL r + uint8_t& reg = R8( data, 0x10 ); + RL( reg, reg = result ) + } + + #define SLA( read, add, write ) {\ + fuint16 result = (read << 1) | add;\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x26: // SLA (HL) + s_time += 7; + data = rp.hl; + sla_data_addr: + SLA( READ( data ), 0, WRITE( data, result ) ) + + CASE7( 20, 21, 22, 23, 24, 25, 27 ):{// SLA r + uint8_t& reg = R8( data, 0x20 ); + SLA( reg, 0, reg = result ) + } + + case 0x36: // SLL (HL) + s_time += 7; + data = rp.hl; + sll_data_addr: + SLA( READ( data ), 1, WRITE( data, result ) ) + + CASE7( 30, 31, 32, 33, 34, 35, 37 ):{// SLL r + uint8_t& reg = R8( data, 0x30 ); + SLA( reg, 1, reg = result ) + } + + // Rotate right + + #define RRC( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = uint8_t (result << 7) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x0E: // RRC (HL) + s_time += 7; + data = rp.hl; + rrc_data_addr: + RRC( READ( data ), WRITE( data, result ) ) + + CASE7( 08, 09, 0A, 0B, 0C, 0D, 0F ):{// RRC r + uint8_t& reg = R8( data, 0x08 ); + RRC( reg, reg = result ) + } + + #define RR( read, write ) {\ + fuint8 result = read;\ + fuint8 temp = result & C01;\ + result = uint8_t (flags << 7) | (result >> 1);\ + flags = SZ28P( result ) | temp;\ + write;\ + goto loop;\ + } + + case 0x1E: // RR (HL) + s_time += 7; + data = rp.hl; + rr_data_addr: + RR( READ( data ), WRITE( data, result ) ) + + CASE7( 18, 19, 1A, 1B, 1C, 1D, 1F ):{// RR r + uint8_t& reg = R8( data, 0x18 ); + RR( reg, reg = result ) + } + + #define SRA( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = (result & 0x80) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x2E: // SRA (HL) + data = rp.hl; + s_time += 7; + sra_data_addr: + SRA( READ( data ), WRITE( data, result ) ) + + CASE7( 28, 29, 2A, 2B, 2C, 2D, 2F ):{// SRA r + uint8_t& reg = R8( data, 0x28 ); + SRA( reg, reg = result ) + } + + #define SRL( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result >>= 1;\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x3E: // SRL (HL) + s_time += 7; + data = rp.hl; + srl_data_addr: + SRL( READ( data ), WRITE( data, result ) ) + + CASE7( 38, 39, 3A, 3B, 3C, 3D, 3F ):{// SRL r + uint8_t& reg = R8( data, 0x38 ); + SRL( reg, reg = result ) + } + + // BIT + { + unsigned temp; + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ): // BIT b,(HL) + s_time += 4; + temp = READ( rp.hl ); + flags &= C01; + goto bit_temp; + CASE7( 40, 41, 42, 43, 44, 45, 47 ): // BIT 0,r + CASE7( 48, 49, 4A, 4B, 4C, 4D, 4F ): // BIT 1,r + CASE7( 50, 51, 52, 53, 54, 55, 57 ): // BIT 2,r + CASE7( 58, 59, 5A, 5B, 5C, 5D, 5F ): // BIT 3,r + CASE7( 60, 61, 62, 63, 64, 65, 67 ): // BIT 4,r + CASE7( 68, 69, 6A, 6B, 6C, 6D, 6F ): // BIT 5,r + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // BIT 6,r + CASE7( 78, 79, 7A, 7B, 7C, 7D, 7F ): // BIT 7,r + temp = R8( data & 7, 0 ); + flags = (flags & C01) | (temp & (F20 | F08)); + bit_temp: + int masked = temp & 1 << (data >> 3 & 7); + flags |=(masked & S80) | H10 | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + // SET/RES + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(HL) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(HL) + s_time += 7; + int temp = READ( rp.hl ); + int bit = 1 << (data >> 3 & 7); + temp |= bit; // SET + if ( !(data & 0x40) ) + temp ^= bit; // RES + WRITE( rp.hl, temp ); + goto loop; + } + + CASE7( C0, C1, C2, C3, C4, C5, C7 ): // SET 0,r + CASE7( C8, C9, CA, CB, CC, CD, CF ): // SET 1,r + CASE7( D0, D1, D2, D3, D4, D5, D7 ): // SET 2,r + CASE7( D8, D9, DA, DB, DC, DD, DF ): // SET 3,r + CASE7( E0, E1, E2, E3, E4, E5, E7 ): // SET 4,r + CASE7( E8, E9, EA, EB, EC, ED, EF ): // SET 5,r + CASE7( F0, F1, F2, F3, F4, F5, F7 ): // SET 6,r + CASE7( F8, F9, FA, FB, FC, FD, FF ): // SET 7,r + R8( data & 7, 0 ) |= 1 << (data >> 3 & 7); + goto loop; + + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // RES 0,r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // RES 1,r + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // RES 2,r + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // RES 3,r + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // RES 4,r + CASE7( A8, A9, AA, AB, AC, AD, AF ): // RES 5,r + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // RES 6,r + CASE7( B8, B9, BA, BB, BC, BD, BF ): // RES 7,r + R8( data & 7, 0 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// ED prefix + { + case 0xED: + pc++; + s_time += ed_dd_timing [data] >> 4; + switch ( data ) + { + { + blargg_ulong temp; + case 0x72: // SBC HL,SP + case 0x7A: // ADC HL,SP + temp = sp; + if ( 0 ) + case 0x42: // SBC HL,BC + case 0x52: // SBC HL,DE + case 0x62: // SBC HL,HL + case 0x4A: // ADC HL,BC + case 0x5A: // ADC HL,DE + case 0x6A: // ADC HL,HL + temp = R16( data >> 3 & 6, 1, 0 ); + blargg_ulong sum = temp + (flags & C01); + flags = ~data >> 2 & N02; + if ( flags ) + sum = (blargg_ulong)-(blargg_long)sum; + sum += rp.hl; + temp ^= rp.hl; + temp ^= sum; + flags |=(sum >> 16 & C01) | + (temp >> 8 & H10) | + (sum >> 8 & (S80 | F20 | F08)) | + ((temp - -0x8000) >> 14 & V04); + rp.hl = sum; + if ( (uint16_t) sum ) + goto loop; + flags |= Z40; + goto loop; + } + + CASE8( 40, 48, 50, 58, 60, 68, 70, 78 ):{// IN r,(C) + int temp = IN( rp.bc ); + R8( data >> 3, 8 ) = temp; + flags = (flags & C01) | SZ28P( temp ); + goto loop; + } + + case 0x71: // OUT (C),0 + rg.flags = 0; + CASE7( 41, 49, 51, 59, 61, 69, 79 ): // OUT (C),r + OUT( rp.bc, R8( data >> 3, 8 ) ); + goto loop; + + { + unsigned temp; + case 0x73: // LD (ADDR),SP + temp = sp; + if ( 0 ) + case 0x43: // LD (ADDR),BC + case 0x53: // LD (ADDR),DE + temp = R16( data, 4, 0x43 ); + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, temp ); + goto loop; + } + + case 0x4B: // LD BC,(ADDR) + case 0x5B:{// LD DE,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + R16( data, 4, 0x4B ) = READ_WORD( addr ); + goto loop; + } + + case 0x7B:{// LD SP,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + sp = READ_WORD( addr ); + goto loop; + } + + case 0x67:{// RRD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (rg.a << 4) | (temp >> 4) ); + temp = (rg.a & 0xF0) | (temp & 0x0F); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + case 0x6F:{// RLD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (temp << 4) | (rg.a & 0x0F) ); + temp = (rg.a & 0xF0) | (temp >> 4); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + CASE8( 44, 4C, 54, 5C, 64, 6C, 74, 7C ): // NEG + opcode = 0x10; // flag to do SBC instead of ADC + flags &= ~C01; + data = rg.a; + rg.a = 0; + goto adc_data; + + { + int inc; + case 0xA9: // CPD + case 0xB9: // CPDR + inc = -1; + if ( 0 ) + case 0xA1: // CPI + case 0xB1: // CPIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int result = rg.a - temp; + flags = (flags & C01) | N02 | + ((((temp ^ rg.a) & H10) ^ result) & (S80 | H10)); + + if ( !(uint8_t) result ) flags |= Z40; + result -= (flags & H10) >> 4; + flags |= result & F08; + flags |= result << 4 & F20; + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( flags & Z40 || data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xA8: // LDD + case 0xB8: // LDDR + inc = -1; + if ( 0 ) + case 0xA0: // LDI + case 0xB0: // LDIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + addr = rp.de; + rp.de = addr + inc; + WRITE( addr, temp ); + + temp += rg.a; + flags = (flags & (S80 | Z40 | C01)) | + (temp & F08) | (temp << 4 & F20); + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xAB: // OUTD + case 0xBB: // OTDR + inc = -1; + if ( 0 ) + case 0xA3: // OUTI + case 0xB3: // OTIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + OUT( rp.bc, temp ); + goto loop; + } + + { + int inc; + case 0xAA: // IND + case 0xBA: // INDR + inc = -1; + if ( 0 ) + case 0xA2: // INI + case 0xB2: // INIR + inc = +1; + + fuint16 addr = rp.hl; + rp.hl = addr + inc; + + int temp = IN( rp.bc ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + WRITE( addr, temp ); + goto loop; + } + + case 0x47: // LD I,A + r.i = rg.a; + goto loop; + + case 0x4F: // LD R,A + SET_R( rg.a ); + dprintf( "LD R,A not supported\n" ); + warning = true; + goto loop; + + case 0x57: // LD A,I + rg.a = r.i; + goto ld_ai_common; + + case 0x5F: // LD A,R + rg.a = GET_R(); + dprintf( "LD A,R not supported\n" ); + warning = true; + ld_ai_common: + flags = (flags & C01) | SZ28( rg.a ) | (r.iff2 << 2 & V04); + goto loop; + + CASE8( 45, 4D, 55, 5D, 65, 6D, 75, 7D ): // RETI/RETN + r.iff1 = r.iff2; + goto ret_taken; + + case 0x46: case 0x4E: case 0x66: case 0x6E: // IM 0 + r.im = 0; + goto loop; + + case 0x56: case 0x76: // IM 1 + r.im = 1; + goto loop; + + case 0x5E: case 0x7E: // IM 2 + r.im = 2; + goto loop; + + default: + dprintf( "Opcode $ED $%02X not supported\n", data ); + warning = true; + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// DD/FD prefix + { + fuint16 ixy; + case 0xDD: + ixy = ix; + goto ix_prefix; + case 0xFD: + ixy = iy; + ix_prefix: + pc++; + unsigned data2 = READ_PROG( pc ); + s_time += ed_dd_timing [data] & 0x0F; + switch ( data ) + { + // TODO: more efficient way of avoid negative address + #define IXY_DISP( ixy, disp ) uint16_t ((ixy) + (disp)) + + #define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in; + + // ADD/ADC/SUB/SBC + + case 0x96: // SUB (IXY+disp) + case 0x86: // ADD (IXY+disp) + flags &= ~C01; + case 0x9E: // SBC (IXY+disp) + case 0x8E: // ADC (IXY+disp) + pc++; + opcode = data; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto adc_data; + + case 0x94: // SUB HXY + case 0x84: // ADD HXY + flags &= ~C01; + case 0x9C: // SBC HXY + case 0x8C: // ADC HXY + opcode = data; + data = ixy >> 8; + goto adc_data; + + case 0x95: // SUB LXY + case 0x85: // ADD LXY + flags &= ~C01; + case 0x9D: // SBC LXY + case 0x8D: // ADC LXY + opcode = data; + data = (uint8_t) ixy; + goto adc_data; + + { + unsigned temp; + case 0x39: // ADD IXY,SP + temp = sp; + goto add_ixy_data; + + case 0x29: // ADD IXY,HL + temp = ixy; + goto add_ixy_data; + + case 0x09: // ADD IXY,BC + case 0x19: // ADD IXY,DE + temp = R16( data, 4, 0x09 ); + add_ixy_data: { + blargg_ulong sum = ixy + temp; + temp ^= ixy; + ixy = (uint16_t) sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((temp ^ sum) >> 8 & H10); + goto set_ixy; + } + } + + // AND + case 0xA6: // AND (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto and_data; + + case 0xA4: // AND HXY + data = ixy >> 8; + goto and_data; + + case 0xA5: // AND LXY + data = (uint8_t) ixy; + goto and_data; + + // OR + case 0xB6: // OR (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto or_data; + + case 0xB4: // OR HXY + data = ixy >> 8; + goto or_data; + + case 0xB5: // OR LXY + data = (uint8_t) ixy; + goto or_data; + + // XOR + case 0xAE: // XOR (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto xor_data; + + case 0xAC: // XOR HXY + data = ixy >> 8; + goto xor_data; + + case 0xAD: // XOR LXY + data = (uint8_t) ixy; + goto xor_data; + + // CP + case 0xBE: // CP (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto cp_data; + + case 0xBC: // CP HXY + data = ixy >> 8; + goto cp_data; + + case 0xBD: // CP LXY + data = (uint8_t) ixy; + goto cp_data; + + // LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (IXY+disp),r + data = R8( data, 0x70 ); + if ( 0 ) + case 0x36: // LD (IXY+disp),imm + pc++, data = READ_PROG( pc ); + pc++; + WRITE( IXY_DISP( ixy, (int8_t) data2 ), data ); + goto loop; + + CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY + R8( data >> 3, 8 ) = ixy >> 8; + goto loop; + + case 0x64: // LD HXY,HXY + case 0x6D: // LD LXY,LXY + goto loop; + + CASE5( 45, 4D, 55, 5D, 7D ): // LD r,LXY + R8( data >> 3, 8 ) = ixy; + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp) + pc++; + R8( data >> 3, 8 ) = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto loop; + + case 0x26: // LD HXY,imm + pc++; + goto ld_hxy_data; + + case 0x65: // LD HXY,LXY + data2 = (uint8_t) ixy; + goto ld_hxy_data; + + CASE5( 60, 61, 62, 63, 67 ): // LD HXY,r + data2 = R8( data, 0x60 ); + ld_hxy_data: + ixy = (uint8_t) ixy | (data2 << 8); + goto set_ixy; + + case 0x2E: // LD LXY,imm + pc++; + goto ld_lxy_data; + + case 0x6C: // LD LXY,HXY + data2 = ixy >> 8; + goto ld_lxy_data; + + CASE5( 68, 69, 6A, 6B, 6F ): // LD LXY,r + data2 = R8( data, 0x68 ); + ld_lxy_data: + ixy = (ixy & 0xFF00) | data2; + set_ixy: + if ( opcode == 0xDD ) + { + ix = ixy; + goto loop; + } + iy = ixy; + goto loop; + + case 0xF9: // LD SP,IXY + sp = ixy; + goto loop; + + case 0x22:{// LD (ADDR),IXY + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, ixy ); + goto loop; + } + + case 0x21: // LD IXY,imm + ixy = GET_ADDR(); + pc += 2; + goto set_ixy; + + case 0x2A:{// LD IXY,(addr) + fuint16 addr = GET_ADDR(); + ixy = READ_WORD( addr ); + pc += 2; + goto set_ixy; + } + + // DD/FD CB prefix + case 0xCB: { + data = IXY_DISP( ixy, (int8_t) data2 ); + pc++; + data2 = READ_PROG( pc ); + pc++; + switch ( data2 ) + { + case 0x06: goto rlc_data_addr; // RLC (IXY) + case 0x16: goto rl_data_addr; // RL (IXY) + case 0x26: goto sla_data_addr; // SLA (IXY) + case 0x36: goto sll_data_addr; // SLL (IXY) + case 0x0E: goto rrc_data_addr; // RRC (IXY) + case 0x1E: goto rr_data_addr; // RR (IXY) + case 0x2E: goto sra_data_addr; // SRA (IXY) + case 0x3E: goto srl_data_addr; // SRL (IXY) + + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) + fuint8 temp = READ( data ); + int masked = temp & 1 << (data2 >> 3 & 7); + flags = (flags & C01) | H10 | + (masked & S80) | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(IXY+disp) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(IXY+disp) + int temp = READ( data ); + int bit = 1 << (data2 >> 3 & 7); + temp |= bit; // SET + if ( !(data2 & 0x40) ) + temp ^= bit; // RES + WRITE( data, temp ); + goto loop; + } + + default: + dprintf( "Opcode $%02X $CB $%02X not supported\n", opcode, data2 ); + warning = true; + goto loop; + } + assert( false ); + } + + // INC/DEC + case 0x23: // INC IXY + ixy = uint16_t (ixy + 1); + goto set_ixy; + + case 0x2B: // DEC IXY + ixy = uint16_t (ixy - 1); + goto set_ixy; + + case 0x34: // INC (IXY+disp) + ixy = IXY_DISP( ixy, (int8_t) data2 ); + pc++; + data = READ( ixy ) + 1; + WRITE( ixy, data ); + goto inc_set_flags; + + case 0x35: // DEC (IXY+disp) + ixy = IXY_DISP( ixy, (int8_t) data2 ); + pc++; + data = READ( ixy ) - 1; + WRITE( ixy, data ); + goto dec_set_flags; + + case 0x24: // INC HXY + ixy = uint16_t (ixy + 0x100); + data = ixy >> 8; + goto inc_xy_common; + + case 0x2C: // INC LXY + data = uint8_t (ixy + 1); + ixy = (ixy & 0xFF00) | data; + inc_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto inc_set_flags; + } + iy = ixy; + goto inc_set_flags; + + case 0x25: // DEC HXY + ixy = uint16_t (ixy - 0x100); + data = ixy >> 8; + goto dec_xy_common; + + case 0x2D: // DEC LXY + data = uint8_t (ixy - 1); + ixy = (ixy & 0xFF00) | data; + dec_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto dec_set_flags; + } + iy = ixy; + goto dec_set_flags; + + // PUSH/POP + case 0xE5: // PUSH IXY + data = ixy; + goto push_data; + + case 0xE1:{// POP IXY + ixy = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto set_ixy; + } + + // Misc + + case 0xE9: // JP (IXY) + pc = ixy; + goto loop; + + case 0xE3:{// EX (SP),IXY + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, ixy ); + ixy = temp; + goto set_ixy; + } + + default: + dprintf( "Unnecessary DD/FD prefix encountered\n" ); + warning = true; + pc--; + goto loop; + } + assert( false ); + } + + } + dprintf( "Unhandled main opcode: $%02X\n", opcode ); + assert( false ); + +halt: + s_time &= 3; // increment by multiple of 4 +out_of_time: + pc--; + + s.time = s_time; + rg.flags = flags; + r.ix = ix; + r.iy = iy; + r.sp = sp; + r.pc = pc; + this->r.b = rg; + this->state_ = s; + this->state = &this->state_; + + return warning; +} diff --git a/game-music-emu/gme/Ay_Cpu.h b/game-music-emu/gme/Ay_Cpu.h new file mode 100644 index 000000000..07241d5e6 --- /dev/null +++ b/game-music-emu/gme/Ay_Cpu.h @@ -0,0 +1,92 @@ +// Z80 CPU emulator + +// Game_Music_Emu 0.5.2 +#ifndef AY_CPU_H +#define AY_CPU_H + +#include "blargg_endian.h" + +typedef blargg_long cpu_time_t; + +// must be defined by caller +void ay_cpu_out( class Ay_Cpu*, cpu_time_t, unsigned addr, int data ); +int ay_cpu_in( class Ay_Cpu*, unsigned addr ); + +class Ay_Cpu { +public: + // Clear all registers and keep pointer to 64K memory passed in + void reset( void* mem_64k ); + + // Run until specified time is reached. Returns true if suspicious/unsupported + // instruction was encountered at any point during run. + bool run( cpu_time_t end_time ); + + // Time of beginning of next instruction + cpu_time_t time() const { return state->time + state->base; } + + // Alter current time. Not supported during run() call. + void set_time( cpu_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + typedef BOOST::uint8_t uint8_t; + typedef BOOST::uint16_t uint16_t; + + #if BLARGG_BIG_ENDIAN + struct regs_t { uint8_t b, c, d, e, h, l, flags, a; }; + #else + struct regs_t { uint8_t c, b, e, d, l, h, a, flags; }; + #endif + BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 ); + + struct pairs_t { uint16_t bc, de, hl, fa; }; + + // Registers are not updated until run() returns + struct registers_t { + uint16_t pc; + uint16_t sp; + uint16_t ix; + uint16_t iy; + union { + regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a + pairs_t w; // w.bc, w.de, w.hl. w.fa + }; + union { + regs_t b; + pairs_t w; + } alt; + uint8_t iff1; + uint8_t iff2; + uint8_t r; + uint8_t i; + uint8_t im; + }; + //registers_t r; (below for efficiency) + + // can read this far past end of memory + enum { cpu_padding = 0x100 }; + +public: + Ay_Cpu(); +private: + uint8_t szpc [0x200]; + uint8_t* mem; + cpu_time_t end_time_; + struct state_t { + cpu_time_t base; + cpu_time_t time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + void set_end_time( cpu_time_t t ); +public: + registers_t r; +}; + +inline void Ay_Cpu::set_end_time( cpu_time_t t ) +{ + cpu_time_t delta = state->base - t; + state->base = t; + state->time += delta; +} + +#endif diff --git a/game-music-emu/gme/Ay_Emu.cpp b/game-music-emu/gme/Ay_Emu.cpp new file mode 100644 index 000000000..090b5ef82 --- /dev/null +++ b/game-music-emu/gme/Ay_Emu.cpp @@ -0,0 +1,405 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Ay_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +long const spectrum_clock = 3546900; +long const cpc_clock = 2000000; + +unsigned const ram_start = 0x4000; +int const osc_count = Ay_Apu::osc_count + 1; + +Ay_Emu::Ay_Emu() +{ + beeper_output = 0; + set_type( gme_ay_type ); + + static const char* const names [osc_count] = { + "Wave 1", "Wave 2", "Wave 3", "Beeper" + }; + set_voice_names( names ); + + static int const types [osc_count] = { + wave_type | 0, wave_type | 1, wave_type | 2, mixed_type | 0 + }; + set_voice_types( types ); + set_silence_lookahead( 6 ); +} + +Ay_Emu::~Ay_Emu() { } + +// Track info + +static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size ) +{ + long pos = long(ptr - (byte const*) file.header); + long file_size = long(file.end - (byte const*) file.header); + assert( (unsigned long) pos <= (unsigned long) file_size - 2 ); + int offset = (BOOST::int16_t) get_be16( ptr ); + if ( !offset || blargg_ulong (pos + offset) > blargg_ulong (file_size - min_size) ) + return 0; + return ptr + offset; +} + +static blargg_err_t parse_header( byte const* in, long size, Ay_Emu::file_t* out ) +{ + typedef Ay_Emu::header_t header_t; + out->header = (header_t const*) in; + out->end = in + size; + + if ( size < Ay_Emu::header_size ) + return gme_wrong_file_type; + + header_t const& h = *(header_t const*) in; + if ( memcmp( h.tag, "ZXAYEMUL", 8 ) ) + return gme_wrong_file_type; + + out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 ); + if ( !out->tracks ) + return "Missing track data"; + + return 0; +} + +static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track ) +{ + Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) ); + byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 ); + if ( track_info ) + out->length = get_be16( track_info + 4 ) * (1000L / 50); // frames to msec + + Gme_File::copy_field_( out->author, (char const*) get_data( file, file.header->author, 1 ) ); + Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) ); +} + +blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const +{ + copy_ay_fields( file, out, track ); + return 0; +} + +struct Ay_File : Gme_Info_ +{ + Ay_Emu::file_t file; + + Ay_File() { set_type( gme_ay_type ); } + + blargg_err_t load_mem_( byte const* begin, long size ) + { + RETURN_ERR( parse_header( begin, size, &file ) ); + set_track_count( file.header->max_track + 1 ); + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int track ) const + { + copy_ay_fields( file, out, track ); + return 0; + } +}; + +static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; } +static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; } + +static gme_type_t_ const gme_ay_type_ = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 }; +gme_type_t const gme_ay_type = &gme_ay_type_; + +// Setup + +blargg_err_t Ay_Emu::load_mem_( byte const* in, long size ) +{ + assert( offsetof (header_t,track_info [2]) == header_size ); + + RETURN_ERR( parse_header( in, size, &file ) ); + set_track_count( file.header->max_track + 1 ); + + if ( file.header->vers > 2 ) + set_warning( "Unknown file version" ); + + set_voice_count( osc_count ); + apu.volume( gain() ); + + return setup_buffer( spectrum_clock ); +} + +void Ay_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* ) +{ + if ( i >= Ay_Apu::osc_count ) + beeper_output = center; + else + apu.osc_output( i, center ); +} + +// Emulation + +void Ay_Emu::set_tempo_( double t ) +{ + play_period = blip_time_t (clock_rate() / 50 / t); +} + +blargg_err_t Ay_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( mem.ram + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET + memset( mem.ram + 0x0100, 0xFF, 0x4000 - 0x100 ); + memset( mem.ram + ram_start, 0x00, sizeof mem.ram - ram_start ); + memset( mem.padding1, 0xFF, sizeof mem.padding1 ); + memset( mem.ram + 0x10000, 0xFF, sizeof mem.ram - 0x10000 ); + + // locate data blocks + byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 ); + if ( !data ) return "File data missing"; + + byte const* const more_data = get_data( file, data + 10, 6 ); + if ( !more_data ) return "File data missing"; + + byte const* blocks = get_data( file, data + 12, 8 ); + if ( !blocks ) return "File data missing"; + + // initial addresses + cpu::reset( mem.ram ); + r.sp = get_be16( more_data ); + r.b.a = r.b.b = r.b.d = r.b.h = data [8]; + r.b.flags = r.b.c = r.b.e = r.b.l = data [9]; + r.alt.w = r.w; + r.ix = r.iy = r.w.hl; + + unsigned addr = get_be16( blocks ); + if ( !addr ) return "File data missing"; + + unsigned init = get_be16( more_data + 2 ); + if ( !init ) + init = addr; + + // copy blocks into memory + do + { + blocks += 2; + unsigned len = get_be16( blocks ); blocks += 2; + if ( addr + len > 0x10000 ) + { + set_warning( "Bad data block size" ); + len = 0x10000 - addr; + } + check( len ); + byte const* in = get_data( file, blocks, 0 ); blocks += 2; + if ( len > blargg_ulong (file.end - in) ) + { + set_warning( "Missing file data" ); + len = unsigned(file.end - in); + } + //dprintf( "addr: $%04X, len: $%04X\n", addr, len ); + if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data + dprintf( "Block addr in ROM\n" ); + memcpy( mem.ram + addr, in, len ); + + if ( file.end - blocks < 8 ) + { + set_warning( "Missing file data" ); + break; + } + } + while ( (addr = get_be16( blocks )) != 0 ); + + // copy and configure driver + static byte const passive [] = { + 0xF3, // DI + 0xCD, 0, 0, // CALL init + 0xED, 0x5E, // LOOP: IM 2 + 0xFB, // EI + 0x76, // HALT + 0x18, 0xFA // JR LOOP + }; + static byte const active [] = { + 0xF3, // DI + 0xCD, 0, 0, // CALL init + 0xED, 0x56, // LOOP: IM 1 + 0xFB, // EI + 0x76, // HALT + 0xCD, 0, 0, // CALL play + 0x18, 0xF7 // JR LOOP + }; + memcpy( mem.ram, passive, sizeof passive ); + unsigned play_addr = get_be16( more_data + 4 ); + //dprintf( "Play: $%04X\n", play_addr ); + if ( play_addr ) + { + memcpy( mem.ram, active, sizeof active ); + mem.ram [ 9] = play_addr; + mem.ram [10] = play_addr >> 8; + } + mem.ram [2] = init; + mem.ram [3] = init >> 8; + + mem.ram [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET) + + memcpy( mem.ram + 0x10000, mem.ram, 0x80 ); // some code wraps around (ugh) + + beeper_delta = int (apu.amp_range * 0.65); + last_beeper = 0; + apu.reset(); + next_play = play_period; + + // start at spectrum speed + change_clock_rate( spectrum_clock ); + set_tempo( tempo() ); + + spectrum_mode = false; + cpc_mode = false; + cpc_latch = 0; + + return 0; +} + +// Emulation + +void Ay_Emu::cpu_out_misc( cpu_time_t time, unsigned addr, int data ) +{ + if ( !cpc_mode ) + { + switch ( addr & 0xFEFF ) + { + case 0xFEFD: + spectrum_mode = true; + apu_addr = data & 0x0F; + return; + + case 0xBEFD: + spectrum_mode = true; + apu.write( time, apu_addr, data ); + return; + } + } + + if ( !spectrum_mode ) + { + switch ( addr >> 8 ) + { + case 0xF6: + switch ( data & 0xC0 ) + { + case 0xC0: + apu_addr = cpc_latch & 0x0F; + goto enable_cpc; + + case 0x80: + apu.write( time, apu_addr, cpc_latch ); + goto enable_cpc; + } + break; + + case 0xF4: + cpc_latch = data; + goto enable_cpc; + } + } + + dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data ); + return; + +enable_cpc: + if ( !cpc_mode ) + { + cpc_mode = true; + change_clock_rate( cpc_clock ); + set_tempo( tempo() ); + } +} + +void ay_cpu_out( Ay_Cpu* cpu, cpu_time_t time, unsigned addr, int data ) +{ + Ay_Emu& emu = STATIC_CAST(Ay_Emu&,*cpu); + + if ( (addr & 0xFF) == 0xFE && !emu.cpc_mode ) + { + int delta = emu.beeper_delta; + data &= 0x10; + if ( emu.last_beeper != data ) + { + emu.last_beeper = data; + emu.beeper_delta = -delta; + emu.spectrum_mode = true; + if ( emu.beeper_output ) + emu.apu.synth_.offset( time, delta, emu.beeper_output ); + } + } + else + { + emu.cpu_out_misc( time, addr, data ); + } +} + +int ay_cpu_in( Ay_Cpu*, unsigned addr ) +{ + // keyboard read and other things + if ( (addr & 0xFF) == 0xFE ) + return 0xFF; // other values break some beeper tunes + + dprintf( "Unmapped IN : $%04X\n", addr ); + return 0xFF; +} + +blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int ) +{ + set_time( 0 ); + if ( !(spectrum_mode | cpc_mode) ) + duration /= 2; // until mode is set, leave room for halved clock rate + + while ( time() < duration ) + { + cpu::run( min( duration, (blip_time_t) next_play ) ); + + if ( time() >= next_play ) + { + next_play += play_period; + + if ( r.iff1 ) + { + if ( mem.ram [r.pc] == 0x76 ) + r.pc++; + + r.iff1 = r.iff2 = 0; + + mem.ram [--r.sp] = uint8_t (r.pc >> 8); + mem.ram [--r.sp] = uint8_t (r.pc); + r.pc = 0x38; + cpu::adjust_time( 12 ); + if ( r.im == 2 ) + { + cpu::adjust_time( 6 ); + unsigned addr = r.i * 0x100u + 0xFF; + r.pc = mem.ram [(addr + 1) & 0xFFFF] * 0x100u + mem.ram [addr]; + } + } + } + } + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + adjust_time( -duration ); + + apu.end_frame( duration ); + + return 0; +} diff --git a/game-music-emu/gme/Ay_Emu.h b/game-music-emu/gme/Ay_Emu.h new file mode 100644 index 000000000..ba8445d31 --- /dev/null +++ b/game-music-emu/gme/Ay_Emu.h @@ -0,0 +1,70 @@ +// Sinclair Spectrum AY music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef AY_EMU_H +#define AY_EMU_H + +#include "Classic_Emu.h" +#include "Ay_Apu.h" +#include "Ay_Cpu.h" + +class Ay_Emu : private Ay_Cpu, public Classic_Emu { + typedef Ay_Cpu cpu; +public: + // AY file header + enum { header_size = 0x14 }; + struct header_t + { + byte tag [8]; + byte vers; + byte player; + byte unused [2]; + byte author [2]; + byte comment [2]; + byte max_track; + byte first_track; + byte track_info [2]; + }; + + static gme_type_t static_type() { return gme_ay_type; } +public: + Ay_Emu(); + ~Ay_Emu(); + struct file_t { + header_t const* header; + byte const* end; + byte const* tracks; + }; +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); +private: + file_t file; + + unsigned play_addr; + cpu_time_t play_period; + cpu_time_t next_play; + Blip_Buffer* beeper_output; + int beeper_delta; + int last_beeper; + int apu_addr; + int cpc_latch; + bool spectrum_mode; + bool cpc_mode; + + // large items + struct { + byte padding1 [0x100]; + byte ram [0x10000 + 0x100]; + } mem; + Ay_Apu apu; + friend void ay_cpu_out( Ay_Cpu*, cpu_time_t, unsigned addr, int data ); + void cpu_out_misc( cpu_time_t, unsigned addr, int data ); +}; + +#endif diff --git a/game-music-emu/gme/Blip_Buffer.cpp b/game-music-emu/gme/Blip_Buffer.cpp new file mode 100644 index 000000000..9dc89ea8d --- /dev/null +++ b/game-music-emu/gme/Blip_Buffer.cpp @@ -0,0 +1,446 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "Blip_Buffer.h" + +#include +#include +#include +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const silent_buf_size = 1; // size used for Silent_Blip_Buffer + +Blip_Buffer::Blip_Buffer() +{ + factor_ = (blip_ulong)-1 / 2; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum_ = 0; + bass_shift_ = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; + + // assumptions code makes about implementation-defined features + #ifndef NDEBUG + // right shift of negative value preserves sign + buf_t_ i = -0x7FFFFFFE; + assert( (i >> 1) == -0x3FFFFFFF ); + + // casting to short truncates to 16 bits and sign-extends + i = 0x18000; + assert( (short) i == -0x8000 ); + #endif +} + +Blip_Buffer::~Blip_Buffer() +{ + if ( buffer_size_ != silent_buf_size ) + free( buffer_ ); +} + +Silent_Blip_Buffer::Silent_Blip_Buffer() +{ + factor_ = 0; + buffer_ = buf; + buffer_size_ = silent_buf_size; + memset( buf, 0, sizeof buf ); // in case machine takes exception for signed overflow +} + +void Blip_Buffer::clear( int entire_buffer ) +{ + offset_ = 0; + reader_accum_ = 0; + modified_ = 0; + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) ); + } +} + +Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return "Internal (tried to resize Silent_Blip_Buffer)"; + } + + // start with maximum length that resampled time can represent + long new_size = (UINT_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64; + if ( msec != blip_max_length ) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + assert( 0 ); // fails if requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ ); + if ( !p ) + return "Out of memory"; + buffer_ = (buf_t_*) p; + } + + buffer_size_ = new_size; + assert( buffer_size_ != silent_buf_size ); + + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + + clear(); + + return 0; // success +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long rate ) const +{ + double ratio = (double) sample_rate_ / rate; + blip_long factor = (blip_long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large + return (blip_resampled_time_t) factor; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + bass_shift_ = shift; +} + +void Blip_Buffer::end_frame( blip_time_t t ) +{ + offset_ += t * factor_; + assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length +} + +void Blip_Buffer::remove_silence( long count ) +{ + assert( count <= samples_avail() ); // tried to remove more samples than available + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return (long) (last_sample - first_sample); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( !factor_ ) + { + assert( 0 ); // sample rate and clock rates must be set first + return 0; + } + + if ( count > buffer_size_ ) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); +} + +void Blip_Buffer::remove_samples( long count ) +{ + if ( count ) + { + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + blip_buffer_extra_; + memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); + memset( buffer_ + remain, 0, count * sizeof *buffer_ ); + } +} + +// Blip_Synth_ + +Blip_Synth_Fast_::Blip_Synth_Fast_() +{ + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +void Blip_Synth_Fast_::volume_unit( double new_unit ) +{ + delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5); +} + +#if !BLIP_BUFFER_FAST + +Blip_Synth_::Blip_Synth_( short* p, int w ) : + impulses( p ), + width( w ) +{ + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +#undef PI +#define PI 3.1415926535897932384626433832795029 + +static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +{ + if ( cutoff >= 0.999 ) + cutoff = 0.999; + + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); + double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); + double const to_angle = PI / 2 / maxh / oversample; + for ( int i = 0; i < count; i++ ) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); + double cos_nc_angle = cos( maxh * cutoff * angle ); + double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); + double cos_angle = cos( angle ); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d + } +} + +void blip_eq_t::generate( float* out, int count ) const +{ + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if ( cutoff_freq ) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc( out, count, blip_res * oversample, treble, cutoff ); + + // apply (half of) hamming window + double to_fraction = PI / (count - 1); + for ( int i = count; i--; ) + out [i] *= 0.54f - 0.46f * (float) cos( i * to_fraction ); +} + +void Blip_Synth_::adjust_impulse() +{ + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for ( int p = blip_res; p-- >= blip_res / 2; ) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for ( int i = 1; i < size; i += blip_res ) + { + error -= impulses [i + p ]; + error -= impulses [i + p2]; + } + if ( p == p2 ) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses [size - blip_res + p] += (short) error; + //printf( "error: %ld\n", error ); + } + + //for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); +} + +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +{ + float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate( &fimpulse [blip_res], half_size ); + + int i; + + // need mirror slightly past center for calculation + for ( i = blip_res; i--; ) + fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; + + // starts at 0 + for ( i = 0; i < blip_res; i++ ) + fimpulse [i] = 0.0f; + + // find rescale factor + double total = 0.0; + for ( i = 0; i < half_size; i++ ) + total += fimpulse [blip_res + i]; + + //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + //double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long) base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const impulses_size = this->impulses_size(); + for ( i = 0; i < impulses_size; i++ ) + { + impulses [i] = (short) floor( (next - sum) * rescale + 0.5 ); + sum += fimpulse [i]; + next += fimpulse [i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if ( vol ) + { + volume_unit_ = 0.0; + volume_unit( vol ); + } +} + +void Blip_Synth_::volume_unit( double new_unit ) +{ + if ( new_unit != volume_unit_ ) + { + // use default eq if it hasn't been set yet + if ( !kernel_unit ) + treble_eq( -8.0 ); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if ( factor > 0.0 ) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while ( factor < 2.0 ) + { + shift++; + factor *= 2.0; + } + + if ( shift ) + { + kernel_unit >>= shift; + assert( kernel_unit > 0 ); // fails if volume unit is too low + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for ( int i = impulses_size(); i--; ) + impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int) floor( factor + 0.5 ); + //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } +} +#endif + +long Blip_Buffer::read_samples( blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo ) +{ + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const bass = BLIP_READER_BASS( *this ); + BLIP_READER_BEGIN( reader, *this ); + + if ( !stereo ) + { + for ( blip_long n = count; n; --n ) + { + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); + *out++ = (blip_sample_t) s; + BLIP_READER_NEXT( reader, bass ); + } + } + else + { + for ( blip_long n = count; n; --n ) + { + blip_long s = BLIP_READER_READ( reader ); + if ( (blip_sample_t) s != s ) + s = 0x7FFF - (s >> 24); + *out = (blip_sample_t) s; + out += 2; + BLIP_READER_NEXT( reader, bass ); + } + } + BLIP_READER_END( reader, *this ); + + remove_samples( count ); + } + return count; +} + +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +{ + if ( buffer_size_ == silent_buf_size ) + { + assert( 0 ); + return; + } + + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + blip_long s = (blip_long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + diff --git a/game-music-emu/gme/Blip_Buffer.h b/game-music-emu/gme/Blip_Buffer.h new file mode 100644 index 000000000..4cc526d2f --- /dev/null +++ b/game-music-emu/gme/Blip_Buffer.h @@ -0,0 +1,488 @@ +// Band-limited sound synthesis buffer + +// Blip_Buffer 0.4.1 +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + + // internal + #include + #if INT_MAX < 0x7FFFFFFF + #error "int must be at least 32 bits" + #endif + + typedef int blip_long; + typedef unsigned blip_ulong; + +// Time unit at source clock rate +typedef blip_long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; + +class Blip_Buffer { +public: + typedef const char* blargg_err_t; + + // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there + // isn't enough memory, returns error without affecting current buffer setup. + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); + + // Set number of source time units per second + void clock_rate( long ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame( blip_time_t time ); + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); + +// Additional optional features + + // Current output sample rate + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + + // Set frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq( int frequency ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear( int entire_buffer = 1 ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + +// Experimental features + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks( long count ) const; + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( blip_sample_t const* buf, long count ); + + // not documented yet + void set_modified() { modified_ = 1; } + int clear_modified() { int b = modified_; modified_ = 0; return b; } + typedef blip_ulong blip_resampled_time_t; + void remove_silence( long count ); + blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } + blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; +public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); } + blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); +public: + typedef blip_time_t buf_t_; + blip_ulong factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + blip_long buffer_size_; + blip_long reader_accum_; + int bass_shift_; +private: + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + int modified_; + friend class Blip_Reader; +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #if BLIP_BUFFER_FAST + #define BLIP_PHASE_BITS 8 + #else + #define BLIP_PHASE_BITS 6 + #endif +#endif + + // Internal + typedef blip_ulong blip_resampled_time_t; + int const blip_widest_impulse_ = 16; + int const blip_buffer_extra_ = blip_widest_impulse_ + 2; + int const blip_res = 1 << BLIP_PHASE_BITS; + class blip_eq_t; + + class Blip_Synth_Fast_ { + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + void volume_unit( double ); + Blip_Synth_Fast_(); + void treble_eq( blip_eq_t const& ) { } + }; + + class Blip_Synth_ { + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + void volume_unit( double ); + Blip_Synth_( short* impulses, int width ); + void treble_eq( blip_eq_t const& ); + private: + double volume_unit_; + short* const impulses; + int const width; + blip_long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + }; + +// Quality level. Start with blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template +class Blip_Synth { +public: + // Set overall volume of waveform + void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } + + // Configure low-pass filter (see blip_buffer.txt) + void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } + + // Get/set Blip_Buffer used for output + Blip_Buffer* output() const { return impl.buf; } + void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } + + // Update amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update( blip_time_t time, int amplitude ); + +// Low-level interface + + // Add an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } + + // Works directly in terms of fractional output samples. Contact author for more info. + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t t, int delta ) const { + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); + } + +private: +#if BLIP_BUFFER_FAST + Blip_Synth_Fast_ impl; +#else + Blip_Synth_ impl; + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; +public: + Blip_Synth() : impl( impulses, quality ) { } +#endif +}; + +// Low-pass equalization parameters +class blip_eq_t { +public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t( double treble_db = 0 ); + + // See blip_buffer.txt + blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); + +private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate( float* out, int count ) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Dummy Blip_Buffer to direct sound output to, for easy muting without +// having to stop sound code. +class Silent_Blip_Buffer : public Blip_Buffer { + buf_t_ buf [blip_buffer_extra_ + 1]; +public: + // The following cannot be used (an assertion will fail if attempted): + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length ); + blip_time_t count_clocks( long count ) const; + void mix_samples( blip_sample_t const* buf, long count ); + + Silent_Blip_Buffer(); +}; + + #if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLIP_RESTRICT __restrict + #else + #define BLIP_RESTRICT + #endif + +// Optimized reading from Blip_Buffer, for use in custom sample output + +// Begin reading from buffer. Name should be unique to the current block. +#define BLIP_READER_BEGIN( name, blip_buffer ) \ + const Blip_Buffer::buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\ + blip_long name##_reader_accum = (blip_buffer).reader_accum_ + +// Get value to pass to BLIP_READER_NEXT() +#define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_) + +// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal +// code at the cost of having no bass control +int const blip_reader_default_bass = 9; + +// Current sample +#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) + +// Current raw sample in full internal resolution +#define BLIP_READER_READ_RAW( name ) (name##_reader_accum) + +// Advance to next sample +#define BLIP_READER_NEXT( name, bass ) \ + (void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass))) + +// End reading samples from buffer. The number of samples read must now be removed +// using Blip_Buffer::remove_samples(). +#define BLIP_READER_END( name, blip_buffer ) \ + (void) ((blip_buffer).reader_accum_ = name##_reader_accum) + + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +// Deprecated; use BLIP_READER macros as follows: +// Blip_Reader r; r.begin( buf ); -> BLIP_READER_BEGIN( r, buf ); +// int bass = r.begin( buf ) -> BLIP_READER_BEGIN( r, buf ); int bass = BLIP_READER_BASS( buf ); +// r.read() -> BLIP_READER_READ( r ) +// r.read_raw() -> BLIP_READER_READ_RAW( r ) +// r.next( bass ) -> BLIP_READER_NEXT( r, bass ) +// r.next() -> BLIP_READER_NEXT( r, blip_reader_default_bass ) +// r.end( buf ) -> BLIP_READER_END( r, buf ) +class Blip_Reader { +public: + int begin( Blip_Buffer& ); + blip_long read() const { return accum >> (blip_sample_bits - 16); } + blip_long read_raw() const { return accum; } + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + void end( Blip_Buffer& b ) { b.reader_accum_ = accum; } + +private: + const Blip_Buffer::buf_t_* buf; + blip_long accum; +}; + +// End of public interface + +#include + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + delta *= impl.delta_factor; + blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + +#if BLIP_BUFFER_FAST + blip_long left = buf [0] + delta; + + // Kind of crappy, but doing shift after multiply results in overflow. + // Alternate way of delaying multiply by delta_factor results in worse + // sub-sample resolution. + blip_long right = (delta >> BLIP_PHASE_BITS) * phase; + left -= right; + right += buf [1]; + + buf [0] = left; + buf [1] = right; +#else + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + int const mid = quality / 2 - 1; + + imp_t const* BLIP_RESTRICT imp = impulses + blip_res - phase; + + #if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + + // straight forward implementation resulted in better code on GCC for x86 + + #define ADD_IMP( out, in ) \ + buf [out] += (blip_long) imp [blip_res * (in)] * delta + + #define BLIP_FWD( i ) {\ + ADD_IMP( fwd + i, i );\ + ADD_IMP( fwd + 1 + i, i + 1 );\ + } + #define BLIP_REV( r ) {\ + ADD_IMP( rev - r, r + 1 );\ + ADD_IMP( rev + 1 - r, r );\ + } + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + ADD_IMP( fwd + mid - 1, mid - 1 ); + ADD_IMP( fwd + mid , mid ); + imp = impulses + phase; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + ADD_IMP( rev , 1 ); + ADD_IMP( rev + 1, 0 ); + + #else + + // for RISC processors, help compiler by reading ahead of writes + + #define BLIP_FWD( i ) {\ + blip_long t0 = i0 * delta + buf [fwd + i];\ + blip_long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i];\ + i0 = imp [blip_res * (i + 2)];\ + buf [fwd + i] = t0;\ + buf [fwd + 1 + i] = t1;\ + } + #define BLIP_REV( r ) {\ + blip_long t0 = i0 * delta + buf [rev - r];\ + blip_long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r];\ + i0 = imp [blip_res * (r - 1)];\ + buf [rev - r] = t0;\ + buf [rev + 1 - r] = t1;\ + } + + blip_long i0 = *imp; + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + blip_long t0 = i0 * delta + buf [fwd + mid - 1]; + blip_long t1 = imp [blip_res * mid] * delta + buf [fwd + mid ]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid ] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + blip_long t0 = i0 * delta + buf [rev ]; + blip_long t1 = *imp * delta + buf [rev + 1]; + buf [rev ] = t0; + buf [rev + 1] = t1; + #endif + +#endif +} + +#undef BLIP_FWD +#undef BLIP_REV + +template +#if BLIP_BUFFER_FAST + inline +#endif +void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); +} + +template +#if BLIP_BUFFER_FAST + inline +#endif +void Blip_Synth::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); +} + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : + treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + +inline int Blip_Buffer::length() const { return length_; } +inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } +inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum_; + return blip_buf.bass_shift_; +} + +int const blip_max_length = 0; +int const blip_default_length = 250; + +#endif diff --git a/game-music-emu/gme/Classic_Emu.cpp b/game-music-emu/gme/Classic_Emu.cpp new file mode 100644 index 000000000..622a5154f --- /dev/null +++ b/game-music-emu/gme/Classic_Emu.cpp @@ -0,0 +1,184 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Classic_Emu.h" + +#include "Multi_Buffer.h" +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Classic_Emu::Classic_Emu() +{ + buf = 0; + stereo_buffer = 0; + voice_types = 0; + + // avoid inconsistency in our duplicated constants + assert( (int) wave_type == (int) Multi_Buffer::wave_type ); + assert( (int) noise_type == (int) Multi_Buffer::noise_type ); + assert( (int) mixed_type == (int) Multi_Buffer::mixed_type ); +} + +Classic_Emu::~Classic_Emu() +{ + delete stereo_buffer; +} + +void Classic_Emu::set_equalizer_( equalizer_t const& eq ) +{ + Music_Emu::set_equalizer_( eq ); + update_eq( eq.treble ); + if ( buf ) + buf->bass_freq( (int) equalizer().bass ); +} + +blargg_err_t Classic_Emu::set_sample_rate_( long rate ) +{ + if ( !buf ) + { + if ( !stereo_buffer ) + CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer ); + buf = stereo_buffer; + } + return buf->set_sample_rate( rate, 1000 / 20 ); +} + +void Classic_Emu::mute_voices_( int mask ) +{ + Music_Emu::mute_voices_( mask ); + for ( int i = voice_count(); i--; ) + { + if ( mask & (1 << i) ) + { + set_voice( i, 0, 0, 0 ); + } + else + { + Multi_Buffer::channel_t ch = buf->channel( i, (voice_types ? voice_types [i] : 0) ); + assert( (ch.center && ch.left && ch.right) || + (!ch.center && !ch.left && !ch.right) ); // all or nothing + set_voice( i, ch.center, ch.left, ch.right ); + } + } +} + +void Classic_Emu::change_clock_rate( long rate ) +{ + clock_rate_ = rate; + buf->clock_rate( rate ); +} + +blargg_err_t Classic_Emu::setup_buffer( long rate ) +{ + change_clock_rate( rate ); + RETURN_ERR( buf->set_channel_count( voice_count() ) ); + set_equalizer( equalizer() ); + buf_changed_count = buf->channels_changed_count(); + return 0; +} + +blargg_err_t Classic_Emu::start_track_( int track ) +{ + RETURN_ERR( Music_Emu::start_track_( track ) ); + buf->clear(); + return 0; +} + +blargg_err_t Classic_Emu::play_( long count, sample_t* out ) +{ + long remain = count; + while ( remain ) + { + remain -= buf->read_samples( &out [count - remain], remain ); + if ( remain ) + { + if ( buf_changed_count != buf->channels_changed_count() ) + { + buf_changed_count = buf->channels_changed_count(); + remute_voices(); + } + int msec = buf->length(); + blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000; + RETURN_ERR( run_clocks( clocks_emulated, msec ) ); + assert( clocks_emulated ); + buf->end_frame( clocks_emulated ); + } + } + return 0; +} + +// Rom_Data + +blargg_err_t Rom_Data_::load_rom_data_( Data_Reader& in, + int header_size, void* header_out, int fill, long pad_size ) +{ + long file_offset = pad_size - header_size; + + rom_addr = 0; + mask = 0; + size_ = 0; + rom.clear(); + + file_size_ = in.remain(); + if ( file_size_ <= header_size ) // <= because there must be data after header + return gme_wrong_file_type; + blargg_err_t err = rom.resize( file_offset + file_size_ + pad_size ); + if ( !err ) + err = in.read( rom.begin() + file_offset, file_size_ ); + if ( err ) + { + rom.clear(); + return err; + } + + file_size_ -= header_size; + memcpy( header_out, &rom [file_offset], header_size ); + + memset( rom.begin() , fill, pad_size ); + memset( rom.end() - pad_size, fill, pad_size ); + + return 0; +} + +void Rom_Data_::set_addr_( long addr, int unit ) +{ + rom_addr = addr - unit - pad_extra; + + long rounded = (addr + file_size_ + unit - 1) / unit * unit; + if ( rounded <= 0 ) + { + rounded = 0; + } + else + { + int shift = 0; + unsigned long max_addr = (unsigned long) (rounded - 1); + while ( max_addr >> shift ) + shift++; + mask = (1L << shift) - 1; + } + + if ( addr < 0 ) + addr = 0; + size_ = rounded; + if ( rom.resize( rounded - rom_addr + pad_extra ) ) { } // OK if shrink fails + + if ( 0 ) + { + dprintf( "addr: %X\n", addr ); + dprintf( "file_size: %d\n", file_size_ ); + dprintf( "rounded: %d\n", rounded ); + dprintf( "mask: $%X\n", mask ); + } +} diff --git a/game-music-emu/gme/Classic_Emu.h b/game-music-emu/gme/Classic_Emu.h new file mode 100644 index 000000000..8cd822ca2 --- /dev/null +++ b/game-music-emu/gme/Classic_Emu.h @@ -0,0 +1,127 @@ +// Common aspects of emulators which use Blip_Buffer for sound output + +// Game_Music_Emu 0.5.2 +#ifndef CLASSIC_EMU_H +#define CLASSIC_EMU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" +#include "Music_Emu.h" + +class Classic_Emu : public Music_Emu { +public: + Classic_Emu(); + ~Classic_Emu(); + void set_buffer( Multi_Buffer* ); +protected: + // Services + enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; + void set_voice_types( int const* t ) { voice_types = t; } + blargg_err_t setup_buffer( long clock_rate ); + long clock_rate() const { return clock_rate_; } + void change_clock_rate( long ); // experimental + + // Overridable + virtual void set_voice( int index, Blip_Buffer* center, + Blip_Buffer* left, Blip_Buffer* right ) = 0; + virtual void update_eq( blip_eq_t const& ) = 0; + virtual blargg_err_t start_track_( int track ) = 0; + virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) = 0; +protected: + blargg_err_t set_sample_rate_( long sample_rate ); + void mute_voices_( int ); + void set_equalizer_( equalizer_t const& ); + blargg_err_t play_( long, sample_t* ); +private: + Multi_Buffer* buf; + Multi_Buffer* stereo_buffer; // NULL if using custom buffer + long clock_rate_; + unsigned buf_changed_count; + int const* voice_types; +}; + +inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf ) +{ + assert( !buf && new_buf ); + buf = new_buf; +} + +// ROM data handler, used by several Classic_Emu derivitives. Loads file data +// with padding on both sides, allowing direct use in bank mapping. The main purpose +// is to allow all file data to be loaded with only one read() call (for efficiency). + +class Rom_Data_ { +public: + typedef unsigned char byte; +protected: + enum { pad_extra = 8 }; + blargg_vector rom; + long file_size_; + blargg_long rom_addr; + blargg_long mask; + blargg_long size_; // TODO: eliminate + + blargg_err_t load_rom_data_( Data_Reader& in, int header_size, void* header_out, + int fill, long pad_size ); + void set_addr_( long addr, int unit ); +}; + +template +class Rom_Data : public Rom_Data_ { + enum { pad_size = unit + pad_extra }; +public: + // Load file data, using already-loaded header 'h' if not NULL. Copy header + // from loaded file data into *out and fill unmapped bytes with 'fill'. + blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill ) + { + return load_rom_data_( in, header_size, header_out, fill, pad_size ); + } + + // Size of file data read in (excluding header) + long file_size() const { return file_size_; } + + // Pointer to beginning of file data + byte* begin() const { return rom.begin() + pad_size; } + + // Set address that file data should start at + void set_addr( long addr ) { set_addr_( addr, unit ); } + + // Free data + void clear() { rom.clear(); } + + // Size of data + start addr, rounded to a multiple of unit + long size() const { return size_; } + + // Pointer to unmapped page filled with same value + byte* unmapped() { return rom.begin(); } + + // Mask address to nearest power of two greater than size() + blargg_long mask_addr( blargg_long addr ) const + { + #ifdef check + check( addr <= mask ); + #endif + return addr & mask; + } + + // Pointer to page starting at addr. Returns unmapped() if outside data. + byte* at_addr( blargg_long addr ) + { + blargg_ulong offset = mask_addr( addr ) - rom_addr; + if ( offset > blargg_ulong (rom.size() - pad_size) ) + offset = 0; // unmapped + return &rom [offset]; + } +}; + +#ifndef GME_APU_HOOK + #define GME_APU_HOOK( emu, addr, data ) ((void) 0) +#endif + +#ifndef GME_FRAME_HOOK + #define GME_FRAME_HOOK( emu ) ((void) 0) +#else + #define GME_FRAME_HOOK_DEFINED 1 +#endif + +#endif diff --git a/game-music-emu/gme/Data_Reader.cpp b/game-music-emu/gme/Data_Reader.cpp new file mode 100644 index 000000000..8c4d8d0e1 --- /dev/null +++ b/game-music-emu/gme/Data_Reader.cpp @@ -0,0 +1,318 @@ +// File_Extractor 0.4.0. http://www.slack.net/~ant/ + +#define _CRT_SECURE_NO_WARNINGS +#define IN_GME 1 + +#include +#include +#include + +#include "Data_Reader.h" + +#include "blargg_endian.h" +/* Copyright (C) 2005-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +const char Data_Reader::eof_error [] = "Unexpected end of file"; + +blargg_err_t Data_Reader::read( void* p, long s ) +{ + long result = read_avail( p, s ); + if ( result != s ) + { + if ( result >= 0 && result < s ) + return eof_error; + + return "Read error"; + } + + return 0; +} + +blargg_err_t Data_Reader::skip( long count ) +{ + char buf [512]; + while ( count ) + { + long n = sizeof buf; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( read( buf, n ) ); + } + return 0; +} + +long File_Reader::remain() const { return size() - tell(); } + +blargg_err_t File_Reader::skip( long n ) +{ + assert( n >= 0 ); + if ( !n ) + return 0; + return seek( tell() + n ); +} + +// Subset_Reader + +Subset_Reader::Subset_Reader( Data_Reader* dr, long size ) +{ + in = dr; + remain_ = dr->remain(); + if ( remain_ > size ) + remain_ = size; +} + +long Subset_Reader::remain() const { return remain_; } + +long Subset_Reader::read_avail( void* p, long s ) +{ + if ( s > remain_ ) + s = remain_; + remain_ -= s; + return in->read_avail( p, s ); +} + +// Remaining_Reader + +Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r ) +{ + header = (char const*) h; + header_end = header + size; + in = r; +} + +long Remaining_Reader::remain() const { return long(header_end - header + in->remain()); } + +long Remaining_Reader::read_first( void* out, long count ) +{ + long first = long(header_end - header); + if ( first ) + { + if ( first > count ) + first = count; + void const* old = header; + header += first; + memcpy( out, old, first ); + } + return first; +} + +long Remaining_Reader::read_avail( void* out, long count ) +{ + long first = read_first( out, count ); + long second = count - first; + if ( second ) + { + second = in->read_avail( (char*) out + first, second ); + if ( second <= 0 ) + return second; + } + return first + second; +} + +blargg_err_t Remaining_Reader::read( void* out, long count ) +{ + long first = read_first( out, count ); + long second = count - first; + if ( !second ) + return 0; + return in->read( (char*) out + first, second ); +} + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( (const char*) p ), + size_( s ) +{ + pos = 0; +} + +long Mem_File_Reader::size() const { return size_; } + +long Mem_File_Reader::read_avail( void* p, long s ) +{ + long r = remain(); + if ( s > r ) + s = r; + memcpy( p, begin + pos, s ); + pos += s; + return s; +} + +long Mem_File_Reader::tell() const { return pos; } + +blargg_err_t Mem_File_Reader::seek( long n ) +{ + if ( n > size_ ) + return eof_error; + pos = n; + return 0; +} + +// Callback_Reader + +Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) : + callback( c ), + data( d ) +{ + remain_ = size; +} + +long Callback_Reader::remain() const { return remain_; } + +long Callback_Reader::read_avail( void* out, long count ) +{ + if ( count > remain_ ) + count = remain_; + if ( Callback_Reader::read( out, count ) ) + count = -1; + return count; +} + +blargg_err_t Callback_Reader::read( void* out, long count ) +{ + if ( count > remain_ ) + return eof_error; + return callback( data, out, count ); +} + +// Std_File_Reader + +Std_File_Reader::Std_File_Reader() : file_( 0 ) { } + +Std_File_Reader::~Std_File_Reader() { close(); } + +blargg_err_t Std_File_Reader::open( const char* path ) +{ + file_ = fopen( path, "rb" ); + if ( !file_ ) + return "Couldn't open file"; + return 0; +} + +long Std_File_Reader::size() const +{ + long pos = tell(); + fseek( (FILE*) file_, 0, SEEK_END ); + long result = tell(); + fseek( (FILE*) file_, pos, SEEK_SET ); + return result; +} + +long Std_File_Reader::read_avail( void* p, long s ) +{ + return (long)fread( p, 1, s, (FILE*) file_ ); +} + +blargg_err_t Std_File_Reader::read( void* p, long s ) +{ + if ( s == (long) fread( p, 1, s, (FILE*) file_ ) ) + return 0; + if ( feof( (FILE*) file_ ) ) + return eof_error; + return "Couldn't read from file"; +} + +long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); } + +blargg_err_t Std_File_Reader::seek( long n ) +{ + if ( !fseek( (FILE*) file_, n, SEEK_SET ) ) + return 0; + if ( n > size() ) + return eof_error; + return "Error seeking in file"; +} + +void Std_File_Reader::close() +{ + if ( file_ ) + { + fclose( (FILE*) file_ ); + file_ = 0; + } +} + +// Gzip_File_Reader + +#ifdef HAVE_ZLIB_H + +#include "zlib.h" + +static const char* get_gzip_eof( const char* path, long* eof ) +{ + FILE* file = fopen( path, "rb" ); + if ( !file ) + return "Couldn't open file"; + + unsigned char buf [4]; + if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B ) + { + fseek( file, -4, SEEK_END ); + fread( buf, 4, 1, file ); + *eof = get_le32( buf ); + } + else + { + fseek( file, 0, SEEK_END ); + *eof = ftell( file ); + } + const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0; + fclose( file ); + return err; +} + +Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { } + +Gzip_File_Reader::~Gzip_File_Reader() { close(); } + +blargg_err_t Gzip_File_Reader::open( const char* path ) +{ + close(); + + RETURN_ERR( get_gzip_eof( path, &size_ ) ); + + file_ = gzopen( path, "rb" ); + if ( !file_ ) + return "Couldn't open file"; + + return 0; +} + +long Gzip_File_Reader::size() const { return size_; } + +long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); } + +long Gzip_File_Reader::tell() const { return gztell( file_ ); } + +blargg_err_t Gzip_File_Reader::seek( long n ) +{ + if ( gzseek( file_, n, SEEK_SET ) >= 0 ) + return 0; + if ( n > size_ ) + return eof_error; + return "Error seeking in file"; +} + +void Gzip_File_Reader::close() +{ + if ( file_ ) + { + gzclose( file_ ); + file_ = 0; + } +} + +#endif diff --git a/game-music-emu/gme/Data_Reader.h b/game-music-emu/gme/Data_Reader.h new file mode 100644 index 000000000..e6321eb04 --- /dev/null +++ b/game-music-emu/gme/Data_Reader.h @@ -0,0 +1,152 @@ +// Data reader interface for uniform access + +// File_Extractor 0.4.0 +#ifndef DATA_READER_H +#define DATA_READER_H + +#include "blargg_common.h" +#include "gme.h" + +// Supports reading and finding out how many bytes are remaining +class Data_Reader { +public: + virtual ~Data_Reader() { } + + static const char eof_error []; // returned by read() when request goes beyond end + + // Read at most count bytes and return number actually read, or <= 0 if error + virtual long read_avail( void*, long n ) = 0; + + // Read exactly count bytes and return error if they couldn't be read + virtual blargg_err_t read( void*, long count ); + + // Number of bytes remaining until end of file + virtual long remain() const = 0; + + // Read and discard count bytes + virtual blargg_err_t skip( long count ); + +public: + Data_Reader() { } + typedef blargg_err_t error_t; // deprecated +private: + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); +}; + +// Supports seeking in addition to Data_Reader operations +class File_Reader : public Data_Reader { +public: + // Size of file + virtual long size() const = 0; + + // Current position in file + virtual long tell() const = 0; + + // Go to new position + virtual blargg_err_t seek( long ) = 0; + + long remain() const; + blargg_err_t skip( long n ); +}; + +// Disk file reader +class Std_File_Reader : public File_Reader { +public: + blargg_err_t open( const char* path ); + void close(); + +public: + Std_File_Reader(); + ~Std_File_Reader(); + long size() const; + blargg_err_t read( void*, long ); + long read_avail( void*, long ); + long tell() const; + blargg_err_t seek( long ); +private: + void* file_; +}; + +// Treats range of memory as a file +class Mem_File_Reader : public File_Reader { +public: + Mem_File_Reader( const void*, long size ); + +public: + long size() const; + long read_avail( void*, long ); + long tell() const; + blargg_err_t seek( long ); +private: + const char* const begin; + const long size_; + long pos; +}; + +// Makes it look like there are only count bytes remaining +class Subset_Reader : public Data_Reader { +public: + Subset_Reader( Data_Reader*, long count ); + +public: + long remain() const; + long read_avail( void*, long ); +private: + Data_Reader* in; + long remain_; +}; + +// Joins already-read header and remaining data into original file (to avoid seeking) +class Remaining_Reader : public Data_Reader { +public: + Remaining_Reader( void const* header, long size, Data_Reader* ); + +public: + long remain() const; + long read_avail( void*, long ); + blargg_err_t read( void*, long ); +private: + char const* header; + char const* header_end; + Data_Reader* in; + long read_first( void* out, long count ); +}; + +// Invokes callback function to read data. Size of data must be specified in advance. +class Callback_Reader : public Data_Reader { +public: + typedef const char* (GMEAPI *callback_t)( void* data, void* out, int count ); + Callback_Reader( callback_t, long size, void* data = 0 ); +public: + long read_avail( void*, long ); + blargg_err_t read( void*, long ); + long remain() const; +private: + callback_t const callback; + void* const data; + long remain_; +}; + +#ifdef HAVE_ZLIB_H +// Gzip compressed file reader +class Gzip_File_Reader : public File_Reader { +public: + blargg_err_t open( const char* path ); + void close(); + +public: + Gzip_File_Reader(); + ~Gzip_File_Reader(); + long size() const; + long read_avail( void*, long ); + long tell() const; + blargg_err_t seek( long ); +private: + void* file_; + long size_; +}; +#endif + +#endif diff --git a/game-music-emu/gme/Dual_Resampler.cpp b/game-music-emu/gme/Dual_Resampler.cpp new file mode 100644 index 000000000..8644517ca --- /dev/null +++ b/game-music-emu/gme/Dual_Resampler.cpp @@ -0,0 +1,131 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Dual_Resampler.h" + +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +unsigned const resampler_extra = 256; + +Dual_Resampler::Dual_Resampler() { } + +Dual_Resampler::~Dual_Resampler() { } + +blargg_err_t Dual_Resampler::reset( int pairs ) +{ + // expand allocations a bit + RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) ); + resize( pairs ); + resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2); + return resampler.buffer_size( resampler_size ); +} + +void Dual_Resampler::resize( int pairs ) +{ + int new_sample_buf_size = pairs * 2; + if ( sample_buf_size != new_sample_buf_size ) + { + if ( (unsigned) new_sample_buf_size > sample_buf.size() ) + { + check( false ); + return; + } + sample_buf_size = new_sample_buf_size; + oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2; + clear(); + } +} + +void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, dsample_t* out ) +{ + long pair_count = sample_buf_size >> 1; + blip_time_t blip_time = blip_buf.count_clocks( pair_count ); + int sample_count = oversamples_per_frame - resampler.written(); + + int new_count = play_frame( blip_time, sample_count, resampler.buffer() ); + assert( new_count < resampler_size ); + + blip_buf.end_frame( blip_time ); + assert( blip_buf.samples_avail() == pair_count ); + + resampler.write( new_count ); + + long count = resampler.read( sample_buf.begin(), sample_buf_size ); + assert( count == (long) sample_buf_size ); + + mix_samples( blip_buf, out ); + blip_buf.remove_samples( pair_count ); +} + +void Dual_Resampler::dual_play( long count, dsample_t* out, Blip_Buffer& blip_buf ) +{ + // empty extra buffer + long remain = sample_buf_size - buf_pos; + if ( remain ) + { + if ( remain > count ) + remain = count; + count -= remain; + memcpy( out, &sample_buf [buf_pos], remain * sizeof *out ); + out += remain; + buf_pos += remain; + } + + // entire frames + while ( count >= (long) sample_buf_size ) + { + play_frame_( blip_buf, out ); + out += sample_buf_size; + count -= sample_buf_size; + } + + // extra + if ( count ) + { + play_frame_( blip_buf, sample_buf.begin() ); + buf_pos = count; + memcpy( out, sample_buf.begin(), count * sizeof *out ); + out += count; + } +} + +void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, dsample_t* out ) +{ + Blip_Reader sn; + int bass = sn.begin( blip_buf ); + const dsample_t* in = sample_buf.begin(); + + for ( int n = sample_buf_size >> 1; n--; ) + { + int s = sn.read(); + blargg_long l = (blargg_long) in [0] * 2 + s; + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + sn.next( bass ); + blargg_long r = (blargg_long) in [1] * 2 + s; + if ( (BOOST::int16_t) r != r ) + r = 0x7FFF - (r >> 24); + + in += 2; + out [0] = l; + out [1] = r; + out += 2; + } + + sn.end( blip_buf ); +} + diff --git a/game-music-emu/gme/Dual_Resampler.h b/game-music-emu/gme/Dual_Resampler.h new file mode 100644 index 000000000..61beb8a08 --- /dev/null +++ b/game-music-emu/gme/Dual_Resampler.h @@ -0,0 +1,50 @@ +// Combination of Fir_Resampler and Blip_Buffer mixing. Used by Sega FM emulators. + +// Game_Music_Emu 0.5.2 +#ifndef DUAL_RESAMPLER_H +#define DUAL_RESAMPLER_H + +#include "Fir_Resampler.h" +#include "Blip_Buffer.h" + +class Dual_Resampler { +public: + Dual_Resampler(); + virtual ~Dual_Resampler(); + + typedef short dsample_t; + + double setup( double oversample, double rolloff, double gain ); + blargg_err_t reset( int max_pairs ); + void resize( int pairs_per_frame ); + void clear(); + + void dual_play( long count, dsample_t* out, Blip_Buffer& ); + +protected: + virtual int play_frame( blip_time_t, int pcm_count, dsample_t* pcm_out ) = 0; +private: + + blargg_vector sample_buf; + int sample_buf_size; + int oversamples_per_frame; + int buf_pos; + int resampler_size; + + Fir_Resampler<12> resampler; + void mix_samples( Blip_Buffer&, dsample_t* ); + void play_frame_( Blip_Buffer&, dsample_t* ); +}; + +inline double Dual_Resampler::setup( double oversample, double rolloff, double gain ) +{ + return resampler.time_ratio( oversample, rolloff, gain * 0.5 ); +} + +inline void Dual_Resampler::clear() +{ + buf_pos = sample_buf_size; + resampler.clear(); +} + +#endif diff --git a/game-music-emu/gme/Effects_Buffer.cpp b/game-music-emu/gme/Effects_Buffer.cpp new file mode 100644 index 000000000..730f8e94c --- /dev/null +++ b/game-music-emu/gme/Effects_Buffer.cpp @@ -0,0 +1,529 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Effects_Buffer.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +typedef blargg_long fixed_t; + +#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) +#define FMUL( x, y ) (((x) * (y)) >> 15) + +const unsigned echo_size = 4096; +const unsigned echo_mask = echo_size - 1; +BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2 + +const unsigned reverb_size = 8192 * 2; +const unsigned reverb_mask = reverb_size - 1; +BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2 + +Effects_Buffer::config_t::config_t() +{ + pan_1 = -0.15f; + pan_2 = 0.15f; + reverb_delay = 88.0f; + reverb_level = 0.12f; + echo_delay = 61.0f; + echo_level = 0.10f; + delay_variance = 18.0f; + effects_enabled = false; +} + +void Effects_Buffer::set_depth( double d ) +{ + float f = (float) d; + config_t c; + c.pan_1 = -0.6f * f; + c.pan_2 = 0.6f * f; + c.reverb_delay = 880 * 0.1f; + c.echo_delay = 610 * 0.1f; + if ( f > 0.5 ) + f = 0.5; // TODO: more linear reduction of extreme reverb/echo + c.reverb_level = 0.5f * f; + c.echo_level = 0.30f * f; + c.delay_variance = 180 * 0.1f; + c.effects_enabled = (d > 0.0f); + config( c ); +} + +Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 ) +{ + buf_count = center_only ? max_buf_count - 4 : max_buf_count; + + echo_pos = 0; + reverb_pos = 0; + + stereo_remain = 0; + effect_remain = 0; + effects_enabled = false; + set_depth( 0 ); +} + +Effects_Buffer::~Effects_Buffer() { } + +blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + if ( !echo_buf.size() ) + RETURN_ERR( echo_buf.resize( echo_size ) ); + + if ( !reverb_buf.size() ) + RETURN_ERR( reverb_buf.resize( reverb_size ) ); + + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + + config( config_ ); + clear(); + + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Effects_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Effects_Buffer::bass_freq( int freq ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( freq ); +} + +void Effects_Buffer::clear() +{ + stereo_remain = 0; + effect_remain = 0; + if ( echo_buf.size() ) + memset( &echo_buf [0], 0, echo_size * sizeof echo_buf [0] ); + + if ( reverb_buf.size() ) + memset( &reverb_buf [0], 0, reverb_size * sizeof reverb_buf [0] ); + + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +inline int pin_range( int n, int max, int min = 0 ) +{ + if ( n < min ) + return min; + if ( n > max ) + return max; + return n; +} + +void Effects_Buffer::config( const config_t& cfg ) +{ + channels_changed(); + + // clear echo and reverb buffers + if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf.size() ) + { + memset( &echo_buf [0], 0, echo_size * sizeof echo_buf [0] ); + memset( &reverb_buf [0], 0, reverb_size * sizeof reverb_buf [0] ); + } + + config_ = cfg; + + if ( config_.effects_enabled ) + { + // convert to internal format + + chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); + chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; + + chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); + chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; + + chans.reverb_level = TO_FIXED( config_.reverb_level ); + chans.echo_level = TO_FIXED( config_.echo_level ); + + int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate()); + + int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate()); + chans.reverb_delay_l = pin_range( reverb_size - + (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); + chans.reverb_delay_r = pin_range( reverb_size + 1 - + (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); + + int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate()); + chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), + echo_size - 1 ); + chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), + echo_size - 1 ); + + chan_types [0].center = &bufs [0]; + chan_types [0].left = &bufs [3]; + chan_types [0].right = &bufs [4]; + + chan_types [1].center = &bufs [1]; + chan_types [1].left = &bufs [3]; + chan_types [1].right = &bufs [4]; + + chan_types [2].center = &bufs [2]; + chan_types [2].left = &bufs [5]; + chan_types [2].right = &bufs [6]; + assert( 2 < chan_types_count ); + } + else + { + // set up outputs + for ( unsigned i = 0; i < chan_types_count; i++ ) + { + channel_t& c = chan_types [i]; + c.center = &bufs [0]; + c.left = &bufs [1]; + c.right = &bufs [2]; + } + } + + if ( buf_count < max_buf_count ) + { + for ( int i = 0; i < chan_types_count; i++ ) + { + channel_t& c = chan_types [i]; + c.left = c.center; + c.right = c.center; + } + } +} + +Effects_Buffer::channel_t Effects_Buffer::channel( int i, int type ) +{ + int out = 2; + if ( !type ) + { + out = i % 5; + if ( out > 2 ) + out = 2; + } + else if ( !(type & noise_type) && (type & type_index_mask) % 3 != 0 ) + { + out = type & 1; + } + return chan_types [out]; +} + +void Effects_Buffer::end_frame( blip_time_t clock_count ) +{ + int bufs_used = 0; + for ( int i = 0; i < buf_count; i++ ) + { + bufs_used |= bufs [i].clear_modified() << i; + bufs [i].end_frame( clock_count ); + } + + int stereo_mask = (config_.effects_enabled ? 0x78 : 0x06); + if ( (bufs_used & stereo_mask) && buf_count == max_buf_count ) + stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + if ( effects_enabled || config_.effects_enabled ) + effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + effects_enabled = config_.effects_enabled; +} + +long Effects_Buffer::samples_avail() const +{ + return bufs [0].samples_avail() * 2; +} + +long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) +{ + require( total_samples % 2 == 0 ); // count must be even + + long remain = bufs [0].samples_avail(); + if ( remain > (total_samples >> 1) ) + remain = (total_samples >> 1); + total_samples = remain; + while ( remain ) + { + int active_bufs = buf_count; + long count = remain; + + // optimizing mixing to skip any channels which had nothing added + if ( effect_remain ) + { + if ( count > effect_remain ) + count = effect_remain; + + if ( stereo_remain ) + { + mix_enhanced( out, count ); + } + else + { + mix_mono_enhanced( out, count ); + active_bufs = 3; + } + } + else if ( stereo_remain ) + { + mix_stereo( out, count ); + active_bufs = 3; + } + else + { + mix_mono( out, count ); + active_bufs = 1; + } + + out += count * 2; + remain -= count; + + stereo_remain -= count; + if ( stereo_remain < 0 ) + stereo_remain = 0; + + effect_remain -= count; + if ( effect_remain < 0 ) + effect_remain = 0; + + for ( int i = 0; i < buf_count; i++ ) + { + if ( i < active_bufs ) + bufs [i].remove_samples( count ); + else + bufs [i].remove_silence( count ); // keep time synchronized + } + } + + return total_samples * 2; +} + +void Effects_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [0] ); + BLIP_READER_BEGIN( c, bufs [0] ); + + // unrolled loop + for ( blargg_long n = count >> 1; n; --n ) + { + blargg_long cs0 = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + + blargg_long cs1 = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + + if ( (BOOST::int16_t) cs0 != cs0 ) + cs0 = 0x7FFF - (cs0 >> 24); + ((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16); + + if ( (BOOST::int16_t) cs1 != cs1 ) + cs1 = 0x7FFF - (cs1 >> 24); + ((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16); + out += 4; + } + + if ( count & 1 ) + { + int s = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + out [0] = s; + out [1] = s; + if ( (BOOST::int16_t) s != s ) + { + s = 0x7FFF - (s >> 24); + out [0] = s; + out [1] = s; + } + } + + BLIP_READER_END( c, bufs [0] ); +} + +void Effects_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [0] ); + BLIP_READER_BEGIN( c, bufs [0] ); + BLIP_READER_BEGIN( l, bufs [1] ); + BLIP_READER_BEGIN( r, bufs [2] ); + + while ( count-- ) + { + int cs = BLIP_READER_READ( c ); + BLIP_READER_NEXT( c, bass ); + int left = cs + BLIP_READER_READ( l ); + int right = cs + BLIP_READER_READ( r ); + BLIP_READER_NEXT( l, bass ); + BLIP_READER_NEXT( r, bass ); + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + + BLIP_READER_END( r, bufs [2] ); + BLIP_READER_END( l, bufs [1] ); + BLIP_READER_END( c, bufs [0] ); +} + +void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [2] ); + BLIP_READER_BEGIN( center, bufs [2] ); + BLIP_READER_BEGIN( sq1, bufs [0] ); + BLIP_READER_BEGIN( sq2, bufs [1] ); + + blip_sample_t* const reverb_buf = this->reverb_buf.begin(); + blip_sample_t* const echo_buf = this->echo_buf.begin(); + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = BLIP_READER_READ( sq1 ); + int sum2_s = BLIP_READER_READ( sq2 ); + + BLIP_READER_NEXT( sq1, bass ); + BLIP_READER_NEXT( sq2, bass ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = (blip_sample_t) FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = (blip_sample_t) FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = BLIP_READER_READ( center ); + BLIP_READER_NEXT( center, bass ); + + int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + BLIP_READER_END( sq1, bufs [0] ); + BLIP_READER_END( sq2, bufs [1] ); + BLIP_READER_END( center, bufs [2] ); +} + +void Effects_Buffer::mix_enhanced( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [2] ); + BLIP_READER_BEGIN( center, bufs [2] ); + BLIP_READER_BEGIN( l1, bufs [3] ); + BLIP_READER_BEGIN( r1, bufs [4] ); + BLIP_READER_BEGIN( l2, bufs [5] ); + BLIP_READER_BEGIN( r2, bufs [6] ); + BLIP_READER_BEGIN( sq1, bufs [0] ); + BLIP_READER_BEGIN( sq2, bufs [1] ); + + blip_sample_t* const reverb_buf = this->reverb_buf.begin(); + blip_sample_t* const echo_buf = this->echo_buf.begin(); + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = BLIP_READER_READ( sq1 ); + int sum2_s = BLIP_READER_READ( sq2 ); + + BLIP_READER_NEXT( sq1, bass ); + BLIP_READER_NEXT( sq2, bass ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + BLIP_READER_READ( l1 ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + BLIP_READER_READ( r1 ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + BLIP_READER_NEXT( l1, bass ); + BLIP_READER_NEXT( r1, bass ); + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = (blip_sample_t) FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = (blip_sample_t) FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = BLIP_READER_READ( center ); + BLIP_READER_NEXT( center, bass ); + + int left = new_reverb_l + sum3_s + BLIP_READER_READ( l2 ) + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + BLIP_READER_READ( r2 ) + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + BLIP_READER_NEXT( l2, bass ); + BLIP_READER_NEXT( r2, bass ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + BLIP_READER_END( l1, bufs [3] ); + BLIP_READER_END( r1, bufs [4] ); + BLIP_READER_END( l2, bufs [5] ); + BLIP_READER_END( r2, bufs [6] ); + BLIP_READER_END( sq1, bufs [0] ); + BLIP_READER_END( sq2, bufs [1] ); + BLIP_READER_END( center, bufs [2] ); +} + diff --git a/game-music-emu/gme/Effects_Buffer.h b/game-music-emu/gme/Effects_Buffer.h new file mode 100644 index 000000000..eb0aa67a3 --- /dev/null +++ b/game-music-emu/gme/Effects_Buffer.h @@ -0,0 +1,86 @@ +// Multi-channel effects buffer with panning, echo and reverb + +// Game_Music_Emu 0.5.2 +#ifndef EFFECTS_BUFFER_H +#define EFFECTS_BUFFER_H + +#include "Multi_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Effects_Buffer : public Multi_Buffer { +public: + // If center_only is true, only center buffers are created and + // less memory is used. + Effects_Buffer( bool center_only = false ); + + // Channel Effect Center Pan + // --------------------------------- + // 0,5 reverb pan_1 + // 1,6 reverb pan_2 + // 2,7 echo - + // 3 echo - + // 4 echo - + + // Channel configuration + struct config_t { + double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right + double pan_2; + double echo_delay; // msec + double echo_level; // 0.0 to 1.0 + double reverb_delay; // msec + double delay_variance; // difference between left/right delays (msec) + double reverb_level; // 0.0 to 1.0 + bool effects_enabled; // if false, use optimized simple mixer + config_t(); + }; + + // Set configuration of buffer + virtual void config( const config_t& ); + void set_depth( double ); + +public: + ~Effects_Buffer(); + blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int, int ); + void end_frame( blip_time_t ); + long read_samples( blip_sample_t*, long ); + long samples_avail() const; +private: + typedef long fixed_t; + + enum { max_buf_count = 7 }; + Blip_Buffer bufs [max_buf_count]; + enum { chan_types_count = 3 }; + channel_t chan_types [3]; + config_t config_; + long stereo_remain; + long effect_remain; + int buf_count; + bool effects_enabled; + + blargg_vector reverb_buf; + blargg_vector echo_buf; + int reverb_pos; + int echo_pos; + + struct { + fixed_t pan_1_levels [2]; + fixed_t pan_2_levels [2]; + int echo_delay_l; + int echo_delay_r; + fixed_t echo_level; + int reverb_delay_l; + int reverb_delay_r; + fixed_t reverb_level; + } chans; + + void mix_mono( blip_sample_t*, blargg_long ); + void mix_stereo( blip_sample_t*, blargg_long ); + void mix_enhanced( blip_sample_t*, blargg_long ); + void mix_mono_enhanced( blip_sample_t*, blargg_long ); +}; + +#endif diff --git a/game-music-emu/gme/Fir_Resampler.cpp b/game-music-emu/gme/Fir_Resampler.cpp new file mode 100644 index 000000000..56ca47801 --- /dev/null +++ b/game-music-emu/gme/Fir_Resampler.cpp @@ -0,0 +1,199 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Fir_Resampler.h" + +#include +#include +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#undef PI +#define PI 3.1415926535897932384626433832795029 + +static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale, + int count, short* out ) +{ + double const maxh = 256; + double const step = PI / maxh * spacing; + double const to_w = maxh * 2 / width; + double const pow_a_n = pow( rolloff, maxh ); + scale /= maxh * 2; + + double angle = (count / 2 - 1 + offset) * -step; + while ( count-- ) + { + *out++ = 0; + double w = angle * to_w; + if ( fabs( w ) < PI ) + { + double rolloff_cos_a = rolloff * cos( angle ); + double num = 1 - rolloff_cos_a - + pow_a_n * cos( maxh * angle ) + + pow_a_n * rolloff * cos( (maxh - 1) * angle ); + double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; + double sinc = scale * num / den - scale; + + out [-1] = (short) (cos( w ) * sinc + sinc); + } + angle += step; + } +} + +Fir_Resampler_::Fir_Resampler_( int width, sample_t* impulses_ ) : + width_( width ), + write_offset( width * stereo - stereo ), + impulses( impulses_ ) +{ + write_pos = 0; + res = 1; + imp_phase = 0; + skip_bits = 0; + step = stereo; + ratio_ = 1.0; +} + +Fir_Resampler_::~Fir_Resampler_() { } + +void Fir_Resampler_::clear() +{ + imp_phase = 0; + if ( buf.size() ) + { + write_pos = &buf [write_offset]; + memset( buf.begin(), 0, write_offset * sizeof buf [0] ); + } +} + +blargg_err_t Fir_Resampler_::buffer_size( int new_size ) +{ + RETURN_ERR( buf.resize( new_size + write_offset ) ); + clear(); + return 0; +} + +double Fir_Resampler_::time_ratio( double new_factor, double rolloff, double gain ) +{ + ratio_ = new_factor; + + double fstep = 0.0; + { + double least_error = 2; + double pos = 0; + res = -1; + for ( int r = 1; r <= max_res; r++ ) + { + pos += ratio_; + double nearest = floor( pos + 0.5 ); + double error = fabs( pos - nearest ); + if ( error < least_error ) + { + res = r; + fstep = nearest / res; + least_error = error; + } + } + } + + skip_bits = 0; + + step = stereo * (int) floor( fstep ); + + ratio_ = fstep; + fstep = fmod( fstep, 1.0 ); + + double filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_; + double pos = 0.0; + input_per_cycle = 0; + for ( int i = 0; i < res; i++ ) + { + gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter, + double (0x7FFF * gain * filter), + (int) width_, impulses + i * width_ ); + + pos += fstep; + input_per_cycle += step; + if ( pos >= 0.9999999 ) + { + pos -= 1.0; + skip_bits |= 1 << i; + input_per_cycle++; + } + } + + clear(); + + return ratio_; +} + +int Fir_Resampler_::input_needed( blargg_long output_count ) const +{ + blargg_long input_count = 0; + + unsigned long skip = skip_bits >> imp_phase; + int remain = res - imp_phase; + while ( (output_count -= 2) > 0 ) + { + input_count += step + (skip & 1) * stereo; + skip >>= 1; + if ( !--remain ) + { + skip = skip_bits; + remain = res; + } + output_count -= 2; + } + + long input_extra = long(input_count - (write_pos - &buf [(width_ - 1) * stereo])); + if ( input_extra < 0 ) + input_extra = 0; + return input_extra; +} + +int Fir_Resampler_::avail_( blargg_long input_count ) const +{ + int cycle_count = input_count / input_per_cycle; + int output_count = cycle_count * res * stereo; + input_count -= cycle_count * input_per_cycle; + + blargg_ulong skip = skip_bits >> imp_phase; + int remain = res - imp_phase; + while ( input_count >= 0 ) + { + input_count -= step + (skip & 1) * stereo; + skip >>= 1; + if ( !--remain ) + { + skip = skip_bits; + remain = res; + } + output_count += 2; + } + return output_count; +} + +int Fir_Resampler_::skip_input( long count ) +{ + int remain = int(write_pos - buf.begin()); + int max_count = remain - width_ * stereo; + if ( count > max_count ) + count = max_count; + + remain -= count; + write_pos = &buf [remain]; + memmove( buf.begin(), &buf [count], remain * sizeof buf [0] ); + + return count; +} diff --git a/game-music-emu/gme/Fir_Resampler.h b/game-music-emu/gme/Fir_Resampler.h new file mode 100644 index 000000000..36006de79 --- /dev/null +++ b/game-music-emu/gme/Fir_Resampler.h @@ -0,0 +1,171 @@ +// Finite impulse response (FIR) resampler with adjustable FIR size + +// Game_Music_Emu 0.5.2 +#ifndef FIR_RESAMPLER_H +#define FIR_RESAMPLER_H + +#include "blargg_common.h" +#include + +class Fir_Resampler_ { +public: + + // Use Fir_Resampler (below) + + // Set input/output resampling ratio and optionally low-pass rolloff and gain. + // Returns actual ratio used (rounded to internal precision). + double time_ratio( double factor, double rolloff = 0.999, double gain = 1.0 ); + + // Current input/output ratio + double ratio() const { return ratio_; } + +// Input + + typedef short sample_t; + + // Resize and clear input buffer + blargg_err_t buffer_size( int ); + + // Clear input buffer. At least two output samples will be available after + // two input samples are written. + void clear(); + + // Number of input samples that can be written + int max_write() const { return int(buf.end() - write_pos); } + + // Pointer to place to write input samples + sample_t* buffer() { return write_pos; } + + // Notify resampler that 'count' input samples have been written + void write( long count ); + + // Number of input samples in buffer + int written() const { return int(write_pos - &buf [write_offset]); } + + // Skip 'count' input samples. Returns number of samples actually skipped. + int skip_input( long count ); + +// Output + + // Number of extra input samples needed until 'count' output samples are available + int input_needed( blargg_long count ) const; + + // Number of output samples available + int avail() const { return avail_( blargg_long(write_pos - &buf [width_ * stereo]) ); } + +public: + ~Fir_Resampler_(); +protected: + enum { stereo = 2 }; + enum { max_res = 32 }; + blargg_vector buf; + sample_t* write_pos; + int res; + int imp_phase; + int const width_; + int const write_offset; + blargg_ulong skip_bits; + int step; + int input_per_cycle; + double ratio_; + sample_t* impulses; + + Fir_Resampler_( int width, sample_t* ); + int avail_( blargg_long input_count ) const; +}; + +// Width is number of points in FIR. Must be even and 4 or more. More points give +// better quality and rolloff effectiveness, and take longer to calculate. +template +class Fir_Resampler : public Fir_Resampler_ { + BOOST_STATIC_ASSERT( width >= 4 && width % 2 == 0 ); + short impulses [max_res] [width]; +public: + Fir_Resampler() : Fir_Resampler_( width, impulses [0] ) { } + + // Read at most 'count' samples. Returns number of samples actually read. + typedef short sample_t; + int read( sample_t* out, blargg_long count ); +}; + +// End of public interface + +inline void Fir_Resampler_::write( long count ) +{ + write_pos += count; + assert( write_pos <= buf.end() ); +} + +template +int Fir_Resampler::read( sample_t* out_begin, blargg_long count ) +{ + sample_t* out = out_begin; + const sample_t* in = buf.begin(); + sample_t* end_pos = write_pos; + blargg_ulong skip = skip_bits >> imp_phase; + sample_t const* imp = impulses [imp_phase]; + int remain = res - imp_phase; + int const step = this->step; + + count >>= 1; + + if ( end_pos - in >= width * stereo ) + { + end_pos -= width * stereo; + do + { + count--; + + // accumulate in extended precision + blargg_long l = 0; + blargg_long r = 0; + + const sample_t* i = in; + if ( count < 0 ) + break; + + for ( int n = width / 2; n; --n ) + { + int pt0 = imp [0]; + l += pt0 * i [0]; + r += pt0 * i [1]; + int pt1 = imp [1]; + imp += 2; + l += pt1 * i [2]; + r += pt1 * i [3]; + i += 4; + } + + remain--; + + l >>= 15; + r >>= 15; + + in += (skip * stereo) & stereo; + skip >>= 1; + in += step; + + if ( !remain ) + { + imp = impulses [0]; + skip = skip_bits; + remain = res; + } + + out [0] = (sample_t) l; + out [1] = (sample_t) r; + out += 2; + } + while ( in <= end_pos ); + } + + imp_phase = res - remain; + + int left = int(write_pos - in); + write_pos = &buf [left]; + memmove( buf.begin(), in, left * sizeof *in ); + + return int(out - out_begin); +} + +#endif diff --git a/game-music-emu/gme/Gb_Apu.cpp b/game-music-emu/gme/Gb_Apu.cpp new file mode 100644 index 000000000..932ebb838 --- /dev/null +++ b/game-music-emu/gme/Gb_Apu.cpp @@ -0,0 +1,306 @@ +// Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +unsigned const vol_reg = 0xFF24; +unsigned const status_reg = 0xFF26; + +Gb_Apu::Gb_Apu() +{ + square1.synth = &square_synth; + square2.synth = &square_synth; + wave.synth = &other_synth; + noise.synth = &other_synth; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = &wave; + oscs [3] = &noise; + + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + osc.regs = ®s [i * 5]; + osc.output = 0; + osc.outputs [0] = 0; + osc.outputs [1] = 0; + osc.outputs [2] = 0; + osc.outputs [3] = 0; + } + + set_tempo( 1.0 ); + volume( 1.0 ); + reset(); +} + +void Gb_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + other_synth.treble_eq( eq ); +} + +void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + require( (center && left && right) || (!center && !left && !right) ); + Gb_Osc& osc = *oscs [index]; + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Gb_Apu::update_volume() +{ + // TODO: doesn't handle differing left/right global volume (support would + // require modification to all oscillator code) + int data = regs [vol_reg - start_addr]; + double vol = (max( data & 7, data >> 4 & 7 ) + 1) * volume_unit; + square_synth.volume( vol ); + other_synth.volume( vol ); +} + +static unsigned char const powerup_regs [0x20] = { + 0x80,0x3F,0x00,0xFF,0xBF, // square 1 + 0xFF,0x3F,0x00,0xFF,0xBF, // square 2 + 0x7F,0xFF,0x9F,0xFF,0xBF, // wave + 0xFF,0xFF,0x00,0x00,0xBF, // noise + 0x00, // left/right enables + 0x77, // master volume + 0x80, // power + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF +}; + +void Gb_Apu::set_tempo( double t ) +{ + frame_period = 4194304 / 256; // 256 Hz + if ( t != 1.0 ) + frame_period = blip_time_t (frame_period / t); +} + +void Gb_Apu::reset() +{ + next_frame_time = 0; + last_time = 0; + frame_count = 0; + + square1.reset(); + square2.reset(); + wave.reset(); + noise.reset(); + noise.bits = 1; + wave.wave_pos = 0; + + // avoid click at beginning + regs [vol_reg - start_addr] = 0x77; + update_volume(); + + regs [status_reg - start_addr] = 0x01; // force power + write_register( 0, status_reg, 0x00 ); + + static unsigned char const initial_wave [] = { + 0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table + 0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA + }; + memcpy( wave.wave, initial_wave, sizeof wave.wave ); +} + +void Gb_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + if ( end_time == last_time ) + return; + + while ( true ) + { + blip_time_t time = next_frame_time; + if ( time > end_time ) + time = end_time; + + // run oscillators + for ( int i = 0; i < osc_count; ++i ) + { + Gb_Osc& osc = *oscs [i]; + if ( osc.output ) + { + osc.output->set_modified(); // TODO: misses optimization opportunities? + int playing = false; + if ( osc.enabled && osc.volume && + (!(osc.regs [4] & osc.len_enabled_mask) || osc.length) ) + playing = -1; + switch ( i ) + { + case 0: square1.run( last_time, time, playing ); break; + case 1: square2.run( last_time, time, playing ); break; + case 2: wave .run( last_time, time, playing ); break; + case 3: noise .run( last_time, time, playing ); break; + } + } + } + last_time = time; + + if ( time == end_time ) + break; + + next_frame_time += frame_period; + + // 256 Hz actions + square1.clock_length(); + square2.clock_length(); + wave.clock_length(); + noise.clock_length(); + + frame_count = (frame_count + 1) & 3; + if ( frame_count == 0 ) + { + // 64 Hz actions + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } + + if ( frame_count & 1 ) + square1.clock_sweep(); // 128 Hz action + } +} + +void Gb_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + assert( next_frame_time >= end_time ); + next_frame_time -= end_time; + + assert( last_time >= end_time ); + last_time -= end_time; +} + +void Gb_Apu::write_register( blip_time_t time, unsigned addr, int data ) +{ + require( (unsigned) data < 0x100 ); + + int reg = addr - start_addr; + if ( (unsigned) reg >= register_count ) + return; + + run_until( time ); + + int old_reg = regs [reg]; + regs [reg] = data; + + if ( addr < vol_reg ) + { + write_osc( reg / 5, reg, data ); + } + else if ( addr == vol_reg && data != old_reg ) // global volume + { + // return all oscs to 0 + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + int amp = osc.last_amp; + osc.last_amp = 0; + if ( amp && osc.enabled && osc.output ) + other_synth.offset( time, -amp, osc.output ); + } + + if ( wave.outputs [3] ) + other_synth.offset( time, 30, wave.outputs [3] ); + + update_volume(); + + if ( wave.outputs [3] ) + other_synth.offset( time, -30, wave.outputs [3] ); + + // oscs will update with new amplitude when next run + } + else if ( addr == 0xFF25 || addr == status_reg ) + { + int mask = (regs [status_reg - start_addr] & 0x80) ? ~0 : 0; + int flags = regs [0xFF25 - start_addr] & mask; + + // left/right assignments + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + osc.enabled &= mask; + int bits = flags >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = (bits >> 3 & 2) | (bits & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output ) + { + int amp = osc.last_amp; + osc.last_amp = 0; + if ( amp && old_output ) + other_synth.offset( time, -amp, old_output ); + } + } + + if ( addr == status_reg && data != old_reg ) + { + if ( !(data & 0x80) ) + { + for ( unsigned i = 0; i < sizeof powerup_regs; i++ ) + { + if ( i != status_reg - start_addr ) + write_register( time, i + start_addr, powerup_regs [i] ); + } + } + else + { + //dprintf( "APU powered on\n" ); + } + } + } + else if ( addr >= 0xFF30 ) + { + int index = (addr & 0x0F) * 2; + wave.wave [index] = data >> 4; + wave.wave [index + 1] = data & 0x0F; + } +} + +int Gb_Apu::read_register( blip_time_t time, unsigned addr ) +{ + run_until( time ); + + int index = addr - start_addr; + require( (unsigned) index < register_count ); + int data = regs [index]; + + if ( addr == status_reg ) + { + data = (data & 0x80) | 0x70; + for ( int i = 0; i < osc_count; i++ ) + { + const Gb_Osc& osc = *oscs [i]; + if ( osc.enabled && (osc.length || !(osc.regs [4] & osc.len_enabled_mask)) ) + data |= 1 << i; + } + } + + return data; +} diff --git a/game-music-emu/gme/Gb_Apu.h b/game-music-emu/gme/Gb_Apu.h new file mode 100644 index 000000000..e74ebc55b --- /dev/null +++ b/game-music-emu/gme/Gb_Apu.h @@ -0,0 +1,90 @@ +// Nintendo Game Boy PAPU sound chip emulator + +// Gb_Snd_Emu 0.1.5 +#ifndef GB_APU_H +#define GB_APU_H + +#include "Gb_Oscs.h" + +class Gb_Apu { +public: + + // Set overall volume of all oscillators, where 1.0 is full volume + void volume( double ); + + // Set treble equalization + void treble_eq( const blip_eq_t& ); + + // Outputs can be assigned to a single buffer for mono output, or to three + // buffers for stereo output (using Stereo_Buffer to do the mixing). + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silences all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, + // which refer to Square 1, Square 2, Wave, and Noise. If buffer is NULL, + // silences oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reset oscillators and internal state + void reset(); + + // Reads and writes at addr must satisfy start_addr <= addr <= end_addr + enum { start_addr = 0xFF10 }; + enum { end_addr = 0xFF3F }; + enum { register_count = end_addr - start_addr + 1 }; + + // Write 'data' to address at specified time + void write_register( blip_time_t, unsigned addr, int data ); + + // Read from address at specified time + int read_register( blip_time_t, unsigned addr ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new frame at time 0. + void end_frame( blip_time_t ); + + void set_tempo( double ); + +public: + Gb_Apu(); +private: + // noncopyable + Gb_Apu( const Gb_Apu& ); + Gb_Apu& operator = ( const Gb_Apu& ); + + Gb_Osc* oscs [osc_count]; + blip_time_t next_frame_time; + blip_time_t last_time; + blip_time_t frame_period; + double volume_unit; + int frame_count; + + Gb_Square square1; + Gb_Square square2; + Gb_Wave wave; + Gb_Noise noise; + BOOST::uint8_t regs [register_count]; + Gb_Square::Synth square_synth; // used by squares + Gb_Wave::Synth other_synth; // used by wave and noise + + void update_volume(); + void run_until( blip_time_t ); + void write_osc( int index, int reg, int data ); +}; + +inline void Gb_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } + +inline void Gb_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } + +inline void Gb_Apu::volume( double vol ) +{ + volume_unit = 0.60 / osc_count / 15 /*steps*/ / 2 /*?*/ / 8 /*master vol range*/ * vol; + update_volume(); +} + +#endif diff --git a/game-music-emu/gme/Gb_Cpu.cpp b/game-music-emu/gme/Gb_Cpu.cpp new file mode 100644 index 000000000..bc7ac1876 --- /dev/null +++ b/game-music-emu/gme/Gb_Cpu.cpp @@ -0,0 +1,1061 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Gb_Cpu.h" + +#include + +//#include "gb_cpu_log.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "gb_cpu_io.h" + +#include "blargg_source.h" + +// Common instructions: +// +// 365880 FA LD A,IND16 +// 355863 20 JR NZ +// 313655 21 LD HL,IMM +// 274580 28 JR Z +// 252878 FE CMP IMM +// 230541 7E LD A,(HL) +// 226209 2A LD A,(HL+) +// 217467 CD CALL +// 212034 C9 RET +// 208376 CB CB prefix +// +// 27486 CB 7E BIT 7,(HL) +// 15925 CB 76 BIT 6,(HL) +// 13035 CB 19 RR C +// 11557 CB 7F BIT 7,A +// 10898 CB 37 SWAP A +// 10208 CB 66 BIT 4,(HL) + +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +inline void Gb_Cpu::set_code_page( int i, uint8_t* p ) +{ + state->code_map [i] = p - PAGE_OFFSET( i * (blargg_long) page_size ); +} + +void Gb_Cpu::reset( void* unmapped ) +{ + check( state == &state_ ); + state = &state_; + + state_.remain = 0; + + for ( int i = 0; i < page_count + 1; i++ ) + set_code_page( i, (uint8_t*) unmapped ); + + memset( &r, 0, sizeof r ); + //interrupts_enabled = false; + + blargg_verify_byte_order(); +} + +void Gb_Cpu::map_code( gb_addr_t start, unsigned size, void* data ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + set_code_page( first_page + i, (uint8_t*) data + i * page_size ); +} + +#define READ( addr ) CPU_READ( this, (addr), s.remain ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), s.remain );} +#define READ_FAST( addr, out ) CPU_READ_FAST( this, (addr), s.remain, out ) +#define READ_PROG( addr ) (s.code_map [(addr) >> page_shift] [PAGE_OFFSET( addr )]) + +unsigned const z_flag = 0x80; +unsigned const n_flag = 0x40; +unsigned const h_flag = 0x20; +unsigned const c_flag = 0x10; + +#ifdef _MSC_VER +// warning C4101: 'blargg_failed_' : unreferenced local variable +// -- produced by the BOOST_STATIC_ASSERT line below +#pragma warning(disable:4101) +#endif +bool Gb_Cpu::run( blargg_long cycle_count ) +{ + state_.remain = blargg_ulong (cycle_count + clocks_per_instr) / clocks_per_instr; + state_t s; + this->state = &s; + memcpy( &s, &this->state_, sizeof s ); + + typedef BOOST::uint16_t uint16_t; + +#if BLARGG_BIG_ENDIAN + #define R8( n ) (r8_ [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n ) (r8_ [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + + union { + core_regs_t rg; // individual registers + + struct { + BOOST::uint16_t bc, de, hl, unused; // pairs + } rp; + + uint8_t r8_ [8]; // indexed registers (use R8 macro due to endian dependence) + BOOST::uint16_t r16 [4]; // indexed pairs + }; + BOOST_STATIC_ASSERT( sizeof rg == 8 && sizeof rp == 8 ); + + rg = r; + unsigned pc = r.pc; + unsigned sp = r.sp; + unsigned flags = r.flags; + +loop: + + check( (unsigned long) pc < 0x10000 ); + check( (unsigned long) sp < 0x10000 ); + check( (flags & ~0xF0) == 0 ); + + uint8_t const* instr = s.code_map [pc >> page_shift]; + unsigned op; + + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + op = instr [pc]; + pc++; + instr += pc; + #else + instr += PAGE_OFFSET( pc ); + op = *instr++; + pc++; + #endif + +#define GET_ADDR() GET_LE16( instr ) + + if ( !--s.remain ) + goto stop; + + unsigned data; + data = *instr; + + #ifdef GB_CPU_LOG_H + gb_cpu_log( "new", pc - 1, op, data, instr [1] ); + #endif + + switch ( op ) + { + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + pc++;\ + int offset = (BOOST::int8_t) data;\ + if ( !(cond) ) goto loop;\ + pc = uint16_t (pc + offset);\ + goto loop;\ +} + +// Most Common + + case 0x20: // JR NZ + BRANCH( !(flags & z_flag) ) + + case 0x21: // LD HL,IMM (common) + rp.hl = GET_ADDR(); + pc += 2; + goto loop; + + case 0x28: // JR Z + BRANCH( flags & z_flag ) + + { + unsigned temp; + case 0xF0: // LD A,(0xFF00+imm) + temp = data | 0xFF00; + pc++; + goto ld_a_ind_comm; + + case 0xF2: // LD A,(0xFF00+C) + temp = rg.c | 0xFF00; + goto ld_a_ind_comm; + + case 0x0A: // LD A,(BC) + temp = rp.bc; + goto ld_a_ind_comm; + + case 0x3A: // LD A,(HL-) + temp = rp.hl; + rp.hl = temp - 1; + goto ld_a_ind_comm; + + case 0x1A: // LD A,(DE) + temp = rp.de; + goto ld_a_ind_comm; + + case 0x2A: // LD A,(HL+) (common) + temp = rp.hl; + rp.hl = temp + 1; + goto ld_a_ind_comm; + + case 0xFA: // LD A,IND16 (common) + temp = GET_ADDR(); + pc += 2; + ld_a_ind_comm: + READ_FAST( temp, rg.a ); + goto loop; + } + + case 0xBE: // CMP (HL) + data = READ( rp.hl ); + goto cmp_comm; + + case 0xB8: // CMP B + case 0xB9: // CMP C + case 0xBA: // CMP D + case 0xBB: // CMP E + case 0xBC: // CMP H + case 0xBD: // CMP L + data = R8( op & 7 ); + goto cmp_comm; + + case 0xFE: // CMP IMM + pc++; + cmp_comm: + op = rg.a; + data = op - data; + sub_set_flags: + flags = ((op & 15) - (data & 15)) & h_flag; + flags |= (data >> 4) & c_flag; + flags |= n_flag; + if ( data & 0xFF ) + goto loop; + flags |= z_flag; + goto loop; + + case 0x46: // LD B,(HL) + case 0x4E: // LD C,(HL) + case 0x56: // LD D,(HL) + case 0x5E: // LD E,(HL) + case 0x66: // LD H,(HL) + case 0x6E: // LD L,(HL) + case 0x7E:{// LD A,(HL) + unsigned addr = rp.hl; + READ_FAST( addr, R8( (op >> 3) & 7 ) ); + goto loop; + } + + case 0xC4: // CNZ (next-most-common) + pc += 2; + if ( flags & z_flag ) + goto loop; + call: + pc -= 2; + case 0xCD: // CALL (most-common) + data = pc + 2; + pc = GET_ADDR(); + push: + sp = (sp - 1) & 0xFFFF; + WRITE( sp, data >> 8 ); + sp = (sp - 1) & 0xFFFF; + WRITE( sp, data & 0xFF ); + goto loop; + + case 0xC8: // RNZ (next-most-common) + if ( !(flags & z_flag) ) + goto loop; + case 0xC9: // RET (most common) + ret: + pc = READ( sp ); + pc += 0x100 * READ( sp + 1 ); + sp = (sp + 2) & 0xFFFF; + goto loop; + + case 0x00: // NOP + case 0x40: // LD B,B + case 0x49: // LD C,C + case 0x52: // LD D,D + case 0x5B: // LD E,E + case 0x64: // LD H,H + case 0x6D: // LD L,L + case 0x7F: // LD A,A + goto loop; + +// CB Instructions + + case 0xCB: + pc++; + // now data is the opcode + switch ( data ) { + + { + int temp; + case 0x46: // BIT b,(HL) + case 0x4E: + case 0x56: + case 0x5E: + case 0x66: + case 0x6E: + case 0x76: + case 0x7E: + { + unsigned addr = rp.hl; + READ_FAST( addr, temp ); + goto bit_comm; + } + + case 0x40: case 0x41: case 0x42: case 0x43: // BIT b,r + case 0x44: case 0x45: case 0x47: case 0x48: + case 0x49: case 0x4A: case 0x4B: case 0x4C: + case 0x4D: case 0x4F: case 0x50: case 0x51: + case 0x52: case 0x53: case 0x54: case 0x55: + case 0x57: case 0x58: case 0x59: case 0x5A: + case 0x5B: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: + case 0x64: case 0x65: case 0x67: case 0x68: + case 0x69: case 0x6A: case 0x6B: case 0x6C: + case 0x6D: case 0x6F: case 0x70: case 0x71: + case 0x72: case 0x73: case 0x74: case 0x75: + case 0x77: case 0x78: case 0x79: case 0x7A: + case 0x7B: case 0x7C: case 0x7D: case 0x7F: + temp = R8( data & 7 ); + bit_comm: + int bit = (~data >> 3) & 7; + flags &= ~n_flag; + flags |= h_flag | z_flag; + flags ^= (temp << bit) & z_flag; + goto loop; + } + + case 0x86: // RES b,(HL) + case 0x8E: + case 0x96: + case 0x9E: + case 0xA6: + case 0xAE: + case 0xB6: + case 0xBE: + case 0xC6: // SET b,(HL) + case 0xCE: + case 0xD6: + case 0xDE: + case 0xE6: + case 0xEE: + case 0xF6: + case 0xFE: { + int temp = READ( rp.hl ); + int bit = 1 << ((data >> 3) & 7); + temp &= ~bit; + if ( !(data & 0x40) ) + bit = 0; + WRITE( rp.hl, temp | bit ); + goto loop; + } + + case 0xC0: case 0xC1: case 0xC2: case 0xC3: // SET b,r + case 0xC4: case 0xC5: case 0xC7: case 0xC8: + case 0xC9: case 0xCA: case 0xCB: case 0xCC: + case 0xCD: case 0xCF: case 0xD0: case 0xD1: + case 0xD2: case 0xD3: case 0xD4: case 0xD5: + case 0xD7: case 0xD8: case 0xD9: case 0xDA: + case 0xDB: case 0xDC: case 0xDD: case 0xDF: + case 0xE0: case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE7: case 0xE8: + case 0xE9: case 0xEA: case 0xEB: case 0xEC: + case 0xED: case 0xEF: case 0xF0: case 0xF1: + case 0xF2: case 0xF3: case 0xF4: case 0xF5: + case 0xF7: case 0xF8: case 0xF9: case 0xFA: + case 0xFB: case 0xFC: case 0xFD: case 0xFF: + R8( data & 7 ) |= 1 << ((data >> 3) & 7); + goto loop; + + case 0x80: case 0x81: case 0x82: case 0x83: // RES b,r + case 0x84: case 0x85: case 0x87: case 0x88: + case 0x89: case 0x8A: case 0x8B: case 0x8C: + case 0x8D: case 0x8F: case 0x90: case 0x91: + case 0x92: case 0x93: case 0x94: case 0x95: + case 0x97: case 0x98: case 0x99: case 0x9A: + case 0x9B: case 0x9C: case 0x9D: case 0x9F: + case 0xA0: case 0xA1: case 0xA2: case 0xA3: + case 0xA4: case 0xA5: case 0xA7: case 0xA8: + case 0xA9: case 0xAA: case 0xAB: case 0xAC: + case 0xAD: case 0xAF: case 0xB0: case 0xB1: + case 0xB2: case 0xB3: case 0xB4: case 0xB5: + case 0xB7: case 0xB8: case 0xB9: case 0xBA: + case 0xBB: case 0xBC: case 0xBD: case 0xBF: + R8( data & 7 ) &= ~(1 << ((data >> 3) & 7)); + goto loop; + + { + int temp; + case 0x36: // SWAP (HL) + temp = READ( rp.hl ); + goto swap_comm; + + case 0x30: // SWAP B + case 0x31: // SWAP C + case 0x32: // SWAP D + case 0x33: // SWAP E + case 0x34: // SWAP H + case 0x35: // SWAP L + case 0x37: // SWAP A + temp = R8( data & 7 ); + swap_comm: + op = (temp >> 4) | (temp << 4); + flags = 0; + goto shift_comm; + } + +// Shift/Rotate + + case 0x06: // RLC (HL) + case 0x16: // RL (HL) + case 0x26: // SLA (HL) + op = READ( rp.hl ); + goto rl_comm; + + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x27: // SLA A + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x07: // RLC A + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x17: // RL A + op = R8( data & 7 ); + goto rl_comm; + + case 0x3E: // SRL (HL) + data += 0x10; // bump up to 0x4n to avoid preserving sign bit + case 0x1E: // RR (HL) + case 0x0E: // RRC (HL) + case 0x2E: // SRA (HL) + op = READ( rp.hl ); + goto rr_comm; + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3F: // SRL A + data += 0x10; // bump up to 0x4n + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1F: // RR A + case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0F: // RRC A + case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2F: // SRA A + op = R8( data & 7 ); + goto rr_comm; + + } // CB op + assert( false ); // unhandled CB op + + case 0x07: // RLCA + case 0x17: // RLA + data = op; + op = rg.a; + rl_comm: + op <<= 1; + op |= ((data & flags) >> 4) & 1; // RL and carry is set + flags = (op >> 4) & c_flag; // C = bit shifted out + if ( data < 0x10 ) // RLC + op |= op >> 8; + // SLA doesn't fill lower bit + goto shift_comm; + + case 0x0F: // RRCA + case 0x1F: // RRA + data = op; + op = rg.a; + rr_comm: + op |= (data & flags) << 4; // RR and carry is set + flags = (op << 4) & c_flag; // C = bit shifted out + if ( data < 0x10 ) // RRC + op |= op << 8; + op >>= 1; + if ( data & 0x20 ) // SRA propagates sign bit + op |= (op << 1) & 0x80; + shift_comm: + data &= 7; + if ( !(op & 0xFF) ) + flags |= z_flag; + if ( data == 6 ) + goto write_hl_op_ff; + R8( data ) = op; + goto loop; + +// Load + + case 0x70: // LD (HL),B + case 0x71: // LD (HL),C + case 0x72: // LD (HL),D + case 0x73: // LD (HL),E + case 0x74: // LD (HL),H + case 0x75: // LD (HL),L + case 0x77: // LD (HL),A + op = R8( op & 7 ); + write_hl_op_ff: + WRITE( rp.hl, op & 0xFF ); + goto loop; + + case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47: // LD r,r + case 0x48: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4F: + case 0x50: case 0x51: case 0x53: case 0x54: case 0x55: case 0x57: + case 0x58: case 0x59: case 0x5A: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x65: case 0x67: + case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6F: + case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: + R8( (op >> 3) & 7 ) = R8( op & 7 ); + goto loop; + + case 0x08: // LD IND16,SP + data = GET_ADDR(); + pc += 2; + WRITE( data, sp&0xFF ); + data++; + WRITE( data, sp >> 8 ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + + case 0x31: // LD SP,IMM + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x01: // LD BC,IMM + case 0x11: // LD DE,IMM + r16 [op >> 4] = GET_ADDR(); + pc += 2; + goto loop; + + { + unsigned temp; + case 0xE0: // LD (0xFF00+imm),A + temp = data | 0xFF00; + pc++; + goto write_data_rg_a; + + case 0xE2: // LD (0xFF00+C),A + temp = rg.c | 0xFF00; + goto write_data_rg_a; + + case 0x32: // LD (HL-),A + temp = rp.hl; + rp.hl = temp - 1; + goto write_data_rg_a; + + case 0x02: // LD (BC),A + temp = rp.bc; + goto write_data_rg_a; + + case 0x12: // LD (DE),A + temp = rp.de; + goto write_data_rg_a; + + case 0x22: // LD (HL+),A + temp = rp.hl; + rp.hl = temp + 1; + goto write_data_rg_a; + + case 0xEA: // LD IND16,A (common) + temp = GET_ADDR(); + pc += 2; + write_data_rg_a: + WRITE( temp, rg.a ); + goto loop; + } + + case 0x06: // LD B,IMM + rg.b = data; + pc++; + goto loop; + + case 0x0E: // LD C,IMM + rg.c = data; + pc++; + goto loop; + + case 0x16: // LD D,IMM + rg.d = data; + pc++; + goto loop; + + case 0x1E: // LD E,IMM + rg.e = data; + pc++; + goto loop; + + case 0x26: // LD H,IMM + rg.h = data; + pc++; + goto loop; + + case 0x2E: // LD L,IMM + rg.l = data; + pc++; + goto loop; + + case 0x36: // LD (HL),IMM + WRITE( rp.hl, data ); + pc++; + goto loop; + + case 0x3E: // LD A,IMM + rg.a = data; + pc++; + goto loop; + +// Increment/Decrement + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + r16 [op >> 4]++; + goto loop; + + case 0x33: // INC SP + sp = (sp + 1) & 0xFFFF; + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + r16 [op >> 4]--; + goto loop; + + case 0x3B: // DEC SP + sp = (sp - 1) & 0xFFFF; + goto loop; + + case 0x34: // INC (HL) + op = rp.hl; + data = READ( op ); + data++; + WRITE( op, data & 0xFF ); + goto inc_comm; + + case 0x04: // INC B + case 0x0C: // INC C (common) + case 0x14: // INC D + case 0x1C: // INC E + case 0x24: // INC H + case 0x2C: // INC L + case 0x3C: // INC A + op = (op >> 3) & 7; + R8( op ) = data = R8( op ) + 1; + inc_comm: + flags = (flags & c_flag) | (((data & 15) - 1) & h_flag) | ((data >> 1) & z_flag); + goto loop; + + case 0x35: // DEC (HL) + op = rp.hl; + data = READ( op ); + data--; + WRITE( op, data & 0xFF ); + goto dec_comm; + + case 0x05: // DEC B + case 0x0D: // DEC C + case 0x15: // DEC D + case 0x1D: // DEC E + case 0x25: // DEC H + case 0x2D: // DEC L + case 0x3D: // DEC A + op = (op >> 3) & 7; + data = R8( op ) - 1; + R8( op ) = data; + dec_comm: + flags = (flags & c_flag) | n_flag | (((data & 15) + 0x31) & h_flag); + if ( data & 0xFF ) + goto loop; + flags |= z_flag; + goto loop; + +// Add 16-bit + + { + blargg_ulong temp; // need more than 16 bits for carry + unsigned prev; + + case 0xF8: // LD HL,SP+imm + temp = BOOST::int8_t (data); // sign-extend to 16 bits + pc++; + flags = 0; + temp += sp; + prev = sp; + goto add_16_hl; + + case 0xE8: // ADD SP,IMM + temp = BOOST::int8_t (data); // sign-extend to 16 bits + pc++; + flags = 0; + temp += sp; + prev = sp; + sp = temp & 0xFFFF; + goto add_16_comm; + + case 0x39: // ADD HL,SP + temp = sp; + goto add_hl_comm; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + temp = r16 [op >> 4]; + add_hl_comm: + prev = rp.hl; + temp += prev; + flags &= z_flag; + add_16_hl: + rp.hl = temp; + add_16_comm: + flags |= (temp >> 12) & c_flag; + flags |= (((temp & 0x0FFF) - (prev & 0x0FFF)) >> 7) & h_flag; + goto loop; + } + + case 0x86: // ADD (HL) + data = READ( rp.hl ); + goto add_comm; + + case 0x80: // ADD B + case 0x81: // ADD C + case 0x82: // ADD D + case 0x83: // ADD E + case 0x84: // ADD H + case 0x85: // ADD L + case 0x87: // ADD A + data = R8( op & 7 ); + goto add_comm; + + case 0xC6: // ADD IMM + pc++; + add_comm: + flags = rg.a; + data += flags; + flags = ((data & 15) - (flags & 15)) & h_flag; + flags |= (data >> 4) & c_flag; + rg.a = data; + if ( data & 0xFF ) + goto loop; + flags |= z_flag; + goto loop; + +// Add/Subtract + + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_comm; + + case 0x88: // ADC B + case 0x89: // ADC C + case 0x8A: // ADC D + case 0x8B: // ADC E + case 0x8C: // ADC H + case 0x8D: // ADC L + case 0x8F: // ADC A + data = R8( op & 7 ); + goto adc_comm; + + case 0xCE: // ADC IMM + pc++; + adc_comm: + data += (flags >> 4) & 1; + data &= 0xFF; // to do: does carry get set when sum + carry = 0x100? + goto add_comm; + + case 0x96: // SUB (HL) + data = READ( rp.hl ); + goto sub_comm; + + case 0x90: // SUB B + case 0x91: // SUB C + case 0x92: // SUB D + case 0x93: // SUB E + case 0x94: // SUB H + case 0x95: // SUB L + case 0x97: // SUB A + data = R8( op & 7 ); + goto sub_comm; + + case 0xD6: // SUB IMM + pc++; + sub_comm: + op = rg.a; + data = op - data; + rg.a = data; + goto sub_set_flags; + + case 0x9E: // SBC (HL) + data = READ( rp.hl ); + goto sbc_comm; + + case 0x98: // SBC B + case 0x99: // SBC C + case 0x9A: // SBC D + case 0x9B: // SBC E + case 0x9C: // SBC H + case 0x9D: // SBC L + case 0x9F: // SBC A + data = R8( op & 7 ); + goto sbc_comm; + + case 0xDE: // SBC IMM + pc++; + sbc_comm: + data += (flags >> 4) & 1; + data &= 0xFF; // to do: does carry get set when sum + carry = 0x100? + goto sub_comm; + +// Logical + + case 0xA0: // AND B + case 0xA1: // AND C + case 0xA2: // AND D + case 0xA3: // AND E + case 0xA4: // AND H + case 0xA5: // AND L + data = R8( op & 7 ); + goto and_comm; + + case 0xA6: // AND (HL) + data = READ( rp.hl ); + pc--; + case 0xE6: // AND IMM + pc++; + and_comm: + rg.a &= data; + case 0xA7: // AND A + flags = h_flag | (((rg.a - 1) >> 1) & z_flag); + goto loop; + + case 0xB0: // OR B + case 0xB1: // OR C + case 0xB2: // OR D + case 0xB3: // OR E + case 0xB4: // OR H + case 0xB5: // OR L + data = R8( op & 7 ); + goto or_comm; + + case 0xB6: // OR (HL) + data = READ( rp.hl ); + pc--; + case 0xF6: // OR IMM + pc++; + or_comm: + rg.a |= data; + case 0xB7: // OR A + flags = ((rg.a - 1) >> 1) & z_flag; + goto loop; + + case 0xA8: // XOR B + case 0xA9: // XOR C + case 0xAA: // XOR D + case 0xAB: // XOR E + case 0xAC: // XOR H + case 0xAD: // XOR L + data = R8( op & 7 ); + goto xor_comm; + + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + pc--; + case 0xEE: // XOR IMM + pc++; + xor_comm: + data ^= rg.a; + rg.a = data; + data--; + flags = (data >> 1) & z_flag; + goto loop; + + case 0xAF: // XOR A + rg.a = 0; + flags = z_flag; + goto loop; + +// Stack + + case 0xF1: // POP FA + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL (common) + data = READ( sp ); + r16 [(op >> 4) & 3] = data + 0x100 * READ( sp + 1 ); + sp = (sp + 2) & 0xFFFF; + if ( op != 0xF1 ) + goto loop; + flags = rg.flags & 0xF0; + goto loop; + + case 0xC5: // PUSH BC + data = rp.bc; + goto push; + + case 0xD5: // PUSH DE + data = rp.de; + goto push; + + case 0xE5: // PUSH HL + data = rp.hl; + goto push; + + case 0xF5: // PUSH FA + data = (flags << 8) | rg.a; + goto push; + +// Flow control + + case 0xFF: + if ( pc == idle_addr + 1 ) + goto stop; + case 0xC7: case 0xCF: case 0xD7: case 0xDF: // RST + case 0xE7: case 0xEF: case 0xF7: + data = pc; + pc = (op & 0x38) + rst_base; + goto push; + + case 0xCC: // CZ + pc += 2; + if ( flags & z_flag ) + goto call; + goto loop; + + case 0xD4: // CNC + pc += 2; + if ( !(flags & c_flag) ) + goto call; + goto loop; + + case 0xDC: // CC + pc += 2; + if ( flags & c_flag ) + goto call; + goto loop; + + case 0xD9: // RETI + //interrupts_enabled = 1; + goto ret; + + case 0xC0: // RZ + if ( !(flags & z_flag) ) + goto ret; + goto loop; + + case 0xD0: // RNC + if ( !(flags & c_flag) ) + goto ret; + goto loop; + + case 0xD8: // RC + if ( flags & c_flag ) + goto ret; + goto loop; + + case 0x18: // JR + BRANCH( true ) + + case 0x30: // JR NC + BRANCH( !(flags & c_flag) ) + + case 0x38: // JR C + BRANCH( flags & c_flag ) + + case 0xE9: // JP_HL + pc = rp.hl; + goto loop; + + case 0xC3: // JP (next-most-common) + pc = GET_ADDR(); + goto loop; + + case 0xC2: // JP NZ + pc += 2; + if ( !(flags & z_flag) ) + goto jp_taken; + goto loop; + + case 0xCA: // JP Z (most common) + pc += 2; + if ( !(flags & z_flag) ) + goto loop; + jp_taken: + pc -= 2; + pc = GET_ADDR(); + goto loop; + + case 0xD2: // JP NC + pc += 2; + if ( !(flags & c_flag) ) + goto jp_taken; + goto loop; + + case 0xDA: // JP C + pc += 2; + if ( flags & c_flag ) + goto jp_taken; + goto loop; + +// Flags + + case 0x2F: // CPL + rg.a = ~rg.a; + flags |= n_flag | h_flag; + goto loop; + + case 0x3F: // CCF + flags = (flags ^ c_flag) & ~(n_flag | h_flag); + goto loop; + + case 0x37: // SCF + flags = (flags | c_flag) & ~(n_flag | h_flag); + goto loop; + + case 0xF3: // DI + //interrupts_enabled = 0; + goto loop; + + case 0xFB: // EI + //interrupts_enabled = 1; + goto loop; + +// Special + + case 0xDD: case 0xD3: case 0xDB: case 0xE3: case 0xE4: // ? + case 0xEB: case 0xEC: case 0xF4: case 0xFD: case 0xFC: + case 0x10: // STOP + case 0x27: // DAA (I'll have to implement this eventually...) + case 0xBF: + case 0xED: // Z80 prefix + case 0x76: // HALT + s.remain++; + goto stop; + } + + // If this fails then the case above is missing an opcode + assert( false ); + +stop: + pc--; + + // copy state back + STATIC_CAST(core_regs_t&,r) = rg; + r.pc = pc; + r.sp = sp; + r.flags = flags; + + this->state = &state_; + memcpy( &this->state_, &s, sizeof this->state_ ); + + return s.remain > 0; +} diff --git a/game-music-emu/gme/Gb_Cpu.h b/game-music-emu/gme/Gb_Cpu.h new file mode 100644 index 000000000..953fbaf50 --- /dev/null +++ b/game-music-emu/gme/Gb_Cpu.h @@ -0,0 +1,93 @@ +// Nintendo Game Boy CPU emulator +// Treats every instruction as taking 4 cycles + +// Game_Music_Emu 0.5.2 +#ifndef GB_CPU_H +#define GB_CPU_H + +#include "blargg_common.h" +#include "blargg_endian.h" + +typedef unsigned gb_addr_t; // 16-bit CPU address + +class Gb_Cpu { + enum { clocks_per_instr = 4 }; +public: + typedef BOOST::uint8_t uint8_t; + + // Clear registers and map all pages to unmapped + void reset( void* unmapped = 0 ); + + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. + enum { page_size = 0x2000 }; + void map_code( gb_addr_t start, unsigned size, void* code ); + + uint8_t* get_code( gb_addr_t ); + + // Push a byte on the stack + void push_byte( int ); + + // Game Boy Z80 registers. *Not* kept updated during a call to run(). + struct core_regs_t { + #if BLARGG_BIG_ENDIAN + uint8_t b, c, d, e, h, l, flags, a; + #else + uint8_t c, b, e, d, l, h, a, flags; + #endif + }; + + struct registers_t : core_regs_t { + long pc; // more than 16 bits to allow overflow detection + BOOST::uint16_t sp; + }; + registers_t r; + + // Interrupt enable flag set by EI and cleared by DI + //bool interrupts_enabled; // unused + + // Base address for RST vectors (normally 0) + gb_addr_t rst_base; + + // If CPU executes opcode 0xFF at this address, it treats as illegal instruction + enum { idle_addr = 0xF00D }; + + // Run CPU for at least 'count' cycles and return false, or return true if + // illegal instruction is encountered. + bool run( blargg_long count ); + + // Number of clock cycles remaining for most recent run() call + blargg_long remain() const { return state->remain * clocks_per_instr; } + + // Can read this many bytes past end of a page + enum { cpu_padding = 8 }; + +public: + Gb_Cpu() : rst_base( 0 ) { state = &state_; } + enum { page_shift = 13 }; + enum { page_count = 0x10000 >> page_shift }; +private: + // noncopyable + Gb_Cpu( const Gb_Cpu& ); + Gb_Cpu& operator = ( const Gb_Cpu& ); + + struct state_t { + uint8_t* code_map [page_count + 1]; + blargg_long remain; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + + void set_code_page( int, uint8_t* ); +}; + +inline BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) +{ + return state->code_map [addr >> page_shift] + addr + #if !BLARGG_NONPORTABLE + % (unsigned) page_size + #endif + ; +} + +#endif diff --git a/game-music-emu/gme/Gb_Oscs.cpp b/game-music-emu/gme/Gb_Oscs.cpp new file mode 100644 index 000000000..735653fa9 --- /dev/null +++ b/game-music-emu/gme/Gb_Oscs.cpp @@ -0,0 +1,336 @@ +// Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Gb_Osc + +void Gb_Osc::reset() +{ + delay = 0; + last_amp = 0; + length = 0; + output_select = 3; + output = outputs [output_select]; +} + +void Gb_Osc::clock_length() +{ + if ( (regs [4] & len_enabled_mask) && length ) + length--; +} + +// Gb_Env + +void Gb_Env::clock_envelope() +{ + if ( env_delay && !--env_delay ) + { + env_delay = regs [2] & 7; + int v = volume - 1 + (regs [2] >> 2 & 2); + if ( (unsigned) v < 15 ) + volume = v; + } +} + +bool Gb_Env::write_register( int reg, int data ) +{ + switch ( reg ) + { + case 1: + length = 64 - (regs [1] & 0x3F); + break; + + case 2: + if ( !(data >> 4) ) + enabled = false; + break; + + case 4: + if ( data & trigger ) + { + env_delay = regs [2] & 7; + volume = regs [2] >> 4; + enabled = true; + if ( length == 0 ) + length = 64; + return true; + } + } + return false; +} + +// Gb_Square + +void Gb_Square::reset() +{ + phase = 0; + sweep_freq = 0; + sweep_delay = 0; + Gb_Env::reset(); +} + +void Gb_Square::clock_sweep() +{ + int sweep_period = (regs [0] & period_mask) >> 4; + if ( sweep_period && sweep_delay && !--sweep_delay ) + { + sweep_delay = sweep_period; + regs [3] = sweep_freq & 0xFF; + regs [4] = (regs [4] & ~0x07) | (sweep_freq >> 8 & 0x07); + + int offset = sweep_freq >> (regs [0] & shift_mask); + if ( regs [0] & 0x08 ) + offset = -offset; + sweep_freq += offset; + + if ( sweep_freq < 0 ) + { + sweep_freq = 0; + } + else if ( sweep_freq >= 2048 ) + { + sweep_delay = 0; // don't modify channel frequency any further + sweep_freq = 2048; // silence sound immediately + } + } +} + +void Gb_Square::run( blip_time_t time, blip_time_t end_time, int playing ) +{ + if ( sweep_freq == 2048 ) + playing = false; + + static unsigned char const table [4] = { 1, 2, 4, 6 }; + int const duty = table [regs [1] >> 6]; + int amp = volume & playing; + if ( phase >= duty ) + amp = -amp; + + int frequency = this->frequency(); + if ( unsigned (frequency - 1) > 2040 ) // frequency < 1 || frequency > 2041 + { + // really high frequency results in DC at half volume + amp = volume >> 1; + playing = false; + } + + { + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + } + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) + { + int const period = (2048 - frequency) * 4; + Blip_Buffer* const output = this->output; + int phase = this->phase; + int delta = amp * 2; + do + { + phase = (phase + 1) & 7; + if ( phase == 0 || phase == duty ) + { + delta = -delta; + synth->offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->phase = phase; + last_amp = delta >> 1; + } + delay = time - end_time; +} + +// Gb_Noise + +void Gb_Noise::run( blip_time_t time, blip_time_t end_time, int playing ) +{ + int amp = volume & playing; + int tap = 13 - (regs [3] & 8); + if ( bits >> tap & 2 ) + amp = -amp; + + { + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + } + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) + { + static unsigned char const table [8] = { 8, 16, 32, 48, 64, 80, 96, 112 }; + int period = table [regs [3] & 7] << (regs [3] >> 4); + + // keep parallel resampled time to eliminate time conversion in the loop + Blip_Buffer* const output = this->output; + const blip_resampled_time_t resampled_period = + output->resampled_duration( period ); + blip_resampled_time_t resampled_time = output->resampled_time( time ); + unsigned bits = this->bits; + int delta = amp * 2; + + do + { + unsigned changed = (bits >> tap) + 1; + time += period; + bits <<= 1; + if ( changed & 2 ) + { + delta = -delta; + bits |= 1; + synth->offset_resampled( resampled_time, delta, output ); + } + resampled_time += resampled_period; + } + while ( time < end_time ); + + this->bits = bits; + last_amp = delta >> 1; + } + delay = time - end_time; +} + +// Gb_Wave + +inline void Gb_Wave::write_register( int reg, int data ) +{ + switch ( reg ) + { + case 0: + if ( !(data & 0x80) ) + enabled = false; + break; + + case 1: + length = 256 - regs [1]; + break; + + case 2: + volume = data >> 5 & 3; + break; + + case 4: + if ( data & trigger & regs [0] ) + { + wave_pos = 0; + enabled = true; + if ( length == 0 ) + length = 256; + } + } +} + +void Gb_Wave::run( blip_time_t time, blip_time_t end_time, int playing ) +{ + int volume_shift = (volume - 1) & 7; // volume = 0 causes shift = 7 + int frequency; + { + int amp = (wave [wave_pos] >> volume_shift & playing) * 2; + + frequency = this->frequency(); + if ( unsigned (frequency - 1) > 2044 ) // frequency < 1 || frequency > 2045 + { + amp = 30 >> volume_shift & playing; + playing = false; + } + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + } + + time += delay; + if ( !playing ) + time = end_time; + + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + int const period = (2048 - frequency) * 2; + int wave_pos = (this->wave_pos + 1) & (wave_size - 1); + + do + { + int amp = (wave [wave_pos] >> volume_shift) * 2; + wave_pos = (wave_pos + 1) & (wave_size - 1); + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->wave_pos = (wave_pos - 1) & (wave_size - 1); + } + delay = time - end_time; +} + +// Gb_Apu::write_osc + +void Gb_Apu::write_osc( int index, int reg, int data ) +{ + reg -= index * 5; + Gb_Square* sq = &square2; + switch ( index ) + { + case 0: + sq = &square1; + case 1: + if ( sq->write_register( reg, data ) && index == 0 ) + { + square1.sweep_freq = square1.frequency(); + if ( (regs [0] & sq->period_mask) && (regs [0] & sq->shift_mask) ) + { + square1.sweep_delay = 1; // cause sweep to recalculate now + square1.clock_sweep(); + } + } + break; + + case 2: + wave.write_register( reg, data ); + break; + + case 3: + if ( noise.write_register( reg, data ) ) + noise.bits = 0x7FFF; + } +} diff --git a/game-music-emu/gme/Gb_Oscs.h b/game-music-emu/gme/Gb_Oscs.h new file mode 100644 index 000000000..d7f88ea14 --- /dev/null +++ b/game-music-emu/gme/Gb_Oscs.h @@ -0,0 +1,83 @@ +// Private oscillators used by Gb_Apu + +// Gb_Snd_Emu 0.1.5 +#ifndef GB_OSCS_H +#define GB_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct Gb_Osc +{ + enum { trigger = 0x80 }; + enum { len_enabled_mask = 0x40 }; + + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + BOOST::uint8_t* regs; // osc's 5 registers + + int delay; + int last_amp; + int volume; + int length; + int enabled; + + void reset(); + void clock_length(); + int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; } +}; + +struct Gb_Env : Gb_Osc +{ + int env_delay; + + void reset(); + void clock_envelope(); + bool write_register( int, int ); +}; + +struct Gb_Square : Gb_Env +{ + enum { period_mask = 0x70 }; + enum { shift_mask = 0x07 }; + + typedef Blip_Synth Synth; + Synth const* synth; + int sweep_delay; + int sweep_freq; + int phase; + + void reset(); + void clock_sweep(); + void run( blip_time_t, blip_time_t, int playing ); +}; + +struct Gb_Noise : Gb_Env +{ + typedef Blip_Synth Synth; + Synth const* synth; + unsigned bits; + + void run( blip_time_t, blip_time_t, int playing ); +}; + +struct Gb_Wave : Gb_Osc +{ + typedef Blip_Synth Synth; + Synth const* synth; + int wave_pos; + enum { wave_size = 32 }; + BOOST::uint8_t wave [wave_size]; + + void write_register( int, int ); + void run( blip_time_t, blip_time_t, int playing ); +}; + +inline void Gb_Env::reset() +{ + env_delay = 0; + Gb_Osc::reset(); +} + +#endif diff --git a/game-music-emu/gme/Gbs_Emu.cpp b/game-music-emu/gme/Gbs_Emu.cpp new file mode 100644 index 000000000..a2b1d613f --- /dev/null +++ b/game-music-emu/gme/Gbs_Emu.cpp @@ -0,0 +1,289 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Gbs_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000 }; +Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 300 }; + +Gbs_Emu::Gbs_Emu() +{ + set_type( gme_gbs_type ); + + static const char* const names [Gb_Apu::osc_count] = { + "Square 1", "Square 2", "Wave", "Noise" + }; + set_voice_names( names ); + + static int const types [Gb_Apu::osc_count] = { + wave_type | 1, wave_type | 2, wave_type | 0, mixed_type | 0 + }; + set_voice_types( types ); + + set_silence_lookahead( 6 ); + set_max_initial_silence( 21 ); + set_gain( 1.2 ); + + static equalizer_t const eq = { -1.0, 120 }; + set_equalizer( eq ); +} + +Gbs_Emu::~Gbs_Emu() { } + +void Gbs_Emu::unload() +{ + rom.clear(); + Music_Emu::unload(); +} + +// Track info + +static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out ) +{ + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, author ); + GME_COPY_FIELD( h, out, copyright ); +} + +blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const +{ + copy_gbs_fields( header_, out ); + return 0; +} + +static blargg_err_t check_gbs_header( void const* header ) +{ + if ( memcmp( header, "GBS", 3 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Gbs_File : Gme_Info_ +{ + Gbs_Emu::header_t h; + + Gbs_File() { set_type( gme_gbs_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + blargg_err_t err = in.read( &h, Gbs_Emu::header_size ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + + set_track_count( h.track_count ); + return check_gbs_header( &h ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_gbs_fields( h, out ); + return 0; + } +}; + +static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; } +static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; } + +static gme_type_t_ const gme_gbs_type_ = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }; +gme_type_t const gme_gbs_type = &gme_gbs_type_; + +// Setup + +blargg_err_t Gbs_Emu::load_( Data_Reader& in ) +{ + assert( offsetof (header_t,copyright [32]) == header_size ); + RETURN_ERR( rom.load( in, header_size, &header_, 0 ) ); + + set_track_count( header_.track_count ); + RETURN_ERR( check_gbs_header( &header_ ) ); + + if ( header_.vers != 1 ) + set_warning( "Unknown file version" ); + + if ( header_.timer_mode & 0x78 ) + set_warning( "Invalid timer mode" ); + + unsigned load_addr = get_le16( header_.load_addr ); + if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F || + load_addr < 0x400 ) + set_warning( "Invalid load/init/play address" ); + + set_voice_count( Gb_Apu::osc_count ); + + apu.volume( gain() ); + + return setup_buffer( 4194304 ); +} + +void Gbs_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + apu.osc_output( i, c, l, r ); +} + +// Emulation + +// see gb_cpu_io.h for read/write functions + +void Gbs_Emu::set_bank( int n ) +{ + blargg_long addr = rom.mask_addr( n * (blargg_long) bank_size ); + if ( addr == 0 && rom.size() > bank_size ) + { + // TODO: what is the correct behavior? Current Game & Watch Gallery + // rip requires that this have no effect or set to bank 1. + //dprintf( "Selected ROM bank 0\n" ); + return; + //n = 1; + } + cpu::map_code( bank_size, bank_size, rom.at_addr( addr ) ); +} + +void Gbs_Emu::update_timer() +{ + if ( header_.timer_mode & 0x04 ) + { + static byte const rates [4] = { 10, 4, 6, 8 }; + int shift = rates [ram [hi_page + 7] & 3] - (header_.timer_mode >> 7); + play_period = (256L - ram [hi_page + 6]) << shift; + } + else + { + play_period = 70224; // 59.73 Hz + } + if ( tempo() != 1.0 ) + play_period = blip_time_t (play_period / tempo()); +} + +static BOOST::uint8_t const sound_data [Gb_Apu::register_count] = { + 0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1 + 0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2 + 0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave + 0x00, 0xFF, 0x00, 0x00, 0xBF, // noise + 0x77, 0xF3, 0xF1, // vin/volume, status, power mode + 0, 0, 0, 0, 0, 0, 0, 0, 0, // unused + 0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, // waveform data + 0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48 +}; + +void Gbs_Emu::cpu_jsr( gb_addr_t addr ) +{ + check( cpu::r.sp == get_le16( header_.stack_ptr ) ); + cpu::r.pc = addr; + cpu_write( --cpu::r.sp, idle_addr >> 8 ); + cpu_write( --cpu::r.sp, idle_addr&0xFF ); +} + +void Gbs_Emu::set_tempo_( double t ) +{ + apu.set_tempo( t ); + update_timer(); +} + +blargg_err_t Gbs_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( ram, 0, 0x4000 ); + memset( ram + 0x4000, 0xFF, 0x1F80 ); + memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 ); + ram [hi_page] = 0; // joypad reads back as 0 + + apu.reset(); + for ( int i = 0; i < (int) sizeof sound_data; i++ ) + apu.write_register( 0, i + apu.start_addr, sound_data [i] ); + + cpu::reset( rom.unmapped() ); + + unsigned load_addr = get_le16( header_.load_addr ); + cpu::rst_base = load_addr; + rom.set_addr( load_addr ); + + cpu::map_code( ram_addr, 0x10000 - ram_addr, ram ); + cpu::map_code( 0, bank_size, rom.at_addr( 0 ) ); + set_bank( rom.size() > bank_size ); + + ram [hi_page + 6] = header_.timer_modulo; + ram [hi_page + 7] = header_.timer_mode; + update_timer(); + next_play = play_period; + + cpu::r.a = track; + cpu::r.pc = idle_addr; + cpu::r.sp = get_le16( header_.stack_ptr ); + cpu_time = 0; + cpu_jsr( get_le16( header_.init_addr ) ); + + return 0; +} + +blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int ) +{ + cpu_time = 0; + while ( cpu_time < duration ) + { + long count = duration - cpu_time; + cpu_time = duration; + bool result = cpu::run( count ); + cpu_time -= cpu::remain(); + + if ( result ) + { + if ( cpu::r.pc == idle_addr ) + { + if ( next_play > duration ) + { + cpu_time = duration; + break; + } + + if ( cpu_time < next_play ) + cpu_time = next_play; + next_play += play_period; + cpu_jsr( get_le16( header_.play_addr ) ); + GME_FRAME_HOOK( this ); + // TODO: handle timer rates different than 60 Hz + } + else if ( cpu::r.pc > 0xFFFF ) + { + dprintf( "PC wrapped around\n" ); + cpu::r.pc &= 0xFFFF; + } + else + { + set_warning( "Emulation error (illegal/unsupported instruction)" ); + dprintf( "Bad opcode $%.2x at $%.4x\n", + (int) *cpu::get_code( cpu::r.pc ), (int) cpu::r.pc ); + cpu::r.pc = (cpu::r.pc + 1) & 0xFFFF; + cpu_time += 6; + } + } + } + + duration = cpu_time; + next_play -= cpu_time; + if ( next_play < 0 ) // could go negative if routine is taking too long to return + next_play = 0; + apu.end_frame( cpu_time ); + + return 0; +} diff --git a/game-music-emu/gme/Gbs_Emu.h b/game-music-emu/gme/Gbs_Emu.h new file mode 100644 index 000000000..93fe691e5 --- /dev/null +++ b/game-music-emu/gme/Gbs_Emu.h @@ -0,0 +1,88 @@ +// Nintendo Game Boy GBS music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef GBS_EMU_H +#define GBS_EMU_H + +#include "Classic_Emu.h" +#include "Gb_Apu.h" +#include "Gb_Cpu.h" + +class Gbs_Emu : private Gb_Cpu, public Classic_Emu { + typedef Gb_Cpu cpu; +public: + // Equalizer profiles for Game Boy Color speaker and headphones + static equalizer_t const handheld_eq; + static equalizer_t const headphones_eq; + + // GBS file header + enum { header_size = 112 }; + struct header_t + { + char tag [3]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte stack_ptr [2]; + byte timer_modulo; + byte timer_mode; + char game [32]; + char author [32]; + char copyright [32]; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_gbs_type; } + +public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + +public: + Gbs_Emu(); + ~Gbs_Emu(); +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + void unload(); +private: + // rom + enum { bank_size = 0x4000 }; + Rom_Data rom; + void set_bank( int ); + + // timer + blip_time_t cpu_time; + blip_time_t play_period; + blip_time_t next_play; + void update_timer(); + + header_t header_; + void cpu_jsr( gb_addr_t ); + +public: private: friend class Gb_Cpu; + blip_time_t clock() const { return cpu_time - cpu::remain(); } + + enum { joypad_addr = 0xFF00 }; + enum { ram_addr = 0xA000 }; + enum { hi_page = 0xFF00 - ram_addr }; + byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding]; + Gb_Apu apu; + + int cpu_read( gb_addr_t ); + void cpu_write( gb_addr_t, int ); +}; + +#endif diff --git a/game-music-emu/gme/Gme_File.cpp b/game-music-emu/gme/Gme_File.cpp new file mode 100644 index 000000000..2d3a140e0 --- /dev/null +++ b/game-music-emu/gme/Gme_File.cpp @@ -0,0 +1,216 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Gme_File.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +const char* const gme_wrong_file_type = "Wrong file type for this emulator"; + +void Gme_File::clear_playlist() +{ + playlist.clear(); + clear_playlist_(); + track_count_ = raw_track_count_; +} + +void Gme_File::unload() +{ + clear_playlist(); // *before* clearing track count + track_count_ = 0; + raw_track_count_ = 0; + file_data.clear(); +} + +Gme_File::Gme_File() +{ + type_ = 0; + user_data_ = 0; + user_cleanup_ = 0; + unload(); // clears fields + blargg_verify_byte_order(); // used by most emulator types, so save them the trouble +} + +Gme_File::~Gme_File() +{ + if ( user_cleanup_ ) + user_cleanup_( user_data_ ); +} + +blargg_err_t Gme_File::load_mem_( byte const* data, long size ) +{ + require( data != file_data.begin() ); // load_mem_() or load_() must be overridden + Mem_File_Reader in( data, size ); + return load_( in ); +} + +blargg_err_t Gme_File::load_( Data_Reader& in ) +{ + RETURN_ERR( file_data.resize( in.remain() ) ); + RETURN_ERR( in.read( file_data.begin(), (long)file_data.size() ) ); + return load_mem_( file_data.begin(), (long)file_data.size() ); +} + +// public load functions call this at beginning +void Gme_File::pre_load() { unload(); } + +void Gme_File::post_load_() { } + +// public load functions call this at end +blargg_err_t Gme_File::post_load( blargg_err_t err ) +{ + if ( !track_count() ) + set_track_count( type()->track_count ); + if ( !err ) + post_load_(); + else + unload(); + + return err; +} + +// Public load functions + +blargg_err_t Gme_File::load_mem( void const* in, long size ) +{ + pre_load(); + return post_load( load_mem_( (byte const*) in, size ) ); +} + +blargg_err_t Gme_File::load( Data_Reader& in ) +{ + pre_load(); + return post_load( load_( in ) ); +} + +blargg_err_t Gme_File::load_file( const char* path ) +{ + pre_load(); + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + return post_load( load_( in ) ); +} + +blargg_err_t Gme_File::load_remaining_( void const* h, long s, Data_Reader& in ) +{ + Remaining_Reader rem( h, s, &in ); + return load( rem ); +} + +// Track info + +void Gme_File::copy_field_( char* out, const char* in, int in_size ) +{ + if ( !in || !*in ) + return; + + // remove spaces/junk from beginning + while ( in_size && unsigned (*in - 1) <= ' ' - 1 ) + { + in++; + in_size--; + } + + // truncate + if ( in_size > max_field_ ) + in_size = max_field_; + + // find terminator + int len = 0; + while ( len < in_size && in [len] ) + len++; + + // remove spaces/junk from end + while ( len && unsigned (in [len - 1]) <= ' ' ) + len--; + + // copy + out [len] = 0; + memcpy( out, in, len ); + + // strip out stupid fields that should have been left blank + if ( !strcmp( out, "?" ) || !strcmp( out, "" ) || !strcmp( out, "< ? >" ) ) + out [0] = 0; +} + +void Gme_File::copy_field_( char* out, const char* in ) +{ + copy_field_( out, in, max_field_ ); +} + +blargg_err_t Gme_File::remap_track_( int* track_io ) const +{ + if ( (unsigned) *track_io >= (unsigned) track_count() ) + return "Invalid track"; + + if ( (unsigned) *track_io < (unsigned) playlist.size() ) + { + M3u_Playlist::entry_t const& e = playlist [*track_io]; + *track_io = 0; + if ( e.track >= 0 ) + { + *track_io = e.track; + if ( !(type_->flags_ & 0x02) ) + *track_io -= e.decimal_track; + } + if ( *track_io >= raw_track_count_ ) + return "Invalid track in m3u playlist"; + } + else + { + check( !playlist.size() ); + } + return 0; +} + +blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const +{ + out->track_count = track_count(); + out->length = -1; + out->loop_length = -1; + out->intro_length = -1; + out->song [0] = 0; + + out->game [0] = 0; + out->author [0] = 0; + out->copyright [0] = 0; + out->comment [0] = 0; + out->dumper [0] = 0; + out->system [0] = 0; + + copy_field_( out->system, type()->system ); + + int remapped = track; + RETURN_ERR( remap_track_( &remapped ) ); + RETURN_ERR( track_info_( out, remapped ) ); + + // override with m3u info + if ( playlist.size() ) + { + M3u_Playlist::info_t const& i = playlist.info(); + copy_field_( out->game , i.title ); + copy_field_( out->author, i.engineer ); + copy_field_( out->author, i.composer ); + copy_field_( out->dumper, i.ripping ); + + M3u_Playlist::entry_t const& e = playlist [track]; + copy_field_( out->song, e.name ); + if ( e.length >= 0 ) out->length = e.length * 1000L; + if ( e.intro >= 0 ) out->intro_length = e.intro * 1000L; + if ( e.loop >= 0 ) out->loop_length = e.loop * 1000L; + } + return 0; +} diff --git a/game-music-emu/gme/Gme_File.h b/game-music-emu/gme/Gme_File.h new file mode 100644 index 000000000..f0e94302c --- /dev/null +++ b/game-music-emu/gme/Gme_File.h @@ -0,0 +1,177 @@ +// Common interface to game music file loading and information + +// Game_Music_Emu 0.5.2 +#ifndef GME_FILE_H +#define GME_FILE_H + +#include "gme.h" +#include "blargg_common.h" +#include "Data_Reader.h" +#include "M3u_Playlist.h" + +// Error returned if file is wrong type +//extern const char gme_wrong_file_type []; // declared in gme.h + +struct gme_type_t_ +{ + const char* system; /* name of system this music file type is generally for */ + int track_count; /* non-zero for formats with a fixed number of tracks */ + Music_Emu* (*new_emu)(); /* Create new emulator for this type (useful in C++ only) */ + Music_Emu* (*new_info)(); /* Create new info reader for this type */ + + /* internal */ + const char* extension_; + int flags_; +}; + +struct track_info_t +{ + long track_count; + + /* times in milliseconds; -1 if unknown */ + long length; + long intro_length; + long loop_length; + + /* empty string if not available */ + char system [256]; + char game [256]; + char song [256]; + char author [256]; + char copyright [256]; + char comment [256]; + char dumper [256]; +}; +enum { gme_max_field = 255 }; + +struct Gme_File { +public: +// File loading + + // Each loads game music data from a file and returns an error if + // file is wrong type or is seriously corrupt. They also set warning + // string for minor problems. + + // Load from file on disk + blargg_err_t load_file( const char* path ); + + // Load from custom data source (see Data_Reader.h) + blargg_err_t load( Data_Reader& ); + + // Load from file already read into memory. Keeps pointer to data, so you + // must not free it until you're done with the file. + blargg_err_t load_mem( void const* data, long size ); + + // Load an m3u playlist. Must be done after loading main music file. + blargg_err_t load_m3u( const char* path ); + blargg_err_t load_m3u( Data_Reader& in ); + + // Clears any loaded m3u playlist and any internal playlist that the music + // format supports (NSFE for example). + void clear_playlist(); + +// Informational + + // Type of emulator. For example if this returns gme_nsfe_type, this object + // is an NSFE emulator, and you can cast to an Nsfe_Emu* if necessary. + gme_type_t type() const; + + // Most recent warning string, or NULL if none. Clears current warning after + // returning. + const char* warning(); + + // Number of tracks or 0 if no file has been loaded + int track_count() const; + + // Get information for a track (length, name, author, etc.) + // See gme.h for definition of struct track_info_t. + blargg_err_t track_info( track_info_t* out, int track ) const; + +// User data/cleanup + + // Set/get pointer to data you want to associate with this emulator. + // You can use this for whatever you want. + void set_user_data( void* p ) { user_data_ = p; } + void* user_data() const { return user_data_; } + + // Register cleanup function to be called when deleting emulator, or NULL to + // clear it. Passes user_data to cleanup function. + void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; } + +public: + // deprecated + int error_count() const; // use warning() +public: + Gme_File(); + virtual ~Gme_File(); + BLARGG_DISABLE_NOTHROW + typedef BOOST::uint8_t byte; +protected: + // Services + void set_track_count( int n ) { track_count_ = raw_track_count_ = n; } + void set_warning( const char* s ) { warning_ = s; } + void set_type( gme_type_t t ) { type_ = t; } + blargg_err_t load_remaining_( void const* header, long header_size, Data_Reader& remaining ); + + // Overridable + virtual void unload(); // called before loading file and if loading fails + virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_() + virtual blargg_err_t load_mem_( byte const* data, long size ); // use data in memory + virtual blargg_err_t track_info_( track_info_t* out, int track ) const = 0; + virtual void pre_load(); + virtual void post_load_(); + virtual void clear_playlist_() { } + +public: + blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu +private: + // noncopyable + Gme_File( const Gme_File& ); + Gme_File& operator = ( const Gme_File& ); + + gme_type_t type_; + int track_count_; + int raw_track_count_; + const char* warning_; + void* user_data_; + gme_user_cleanup_t user_cleanup_; + M3u_Playlist playlist; + char playlist_warning [64]; + blargg_vector file_data; // only if loaded into memory using default load + + blargg_err_t load_m3u_( blargg_err_t ); + blargg_err_t post_load( blargg_err_t err ); +public: + // track_info field copying + enum { max_field_ = 255 }; + static void copy_field_( char* out, const char* in ); + static void copy_field_( char* out, const char* in, int len ); +}; + +Music_Emu* gme_new_( Music_Emu*, long sample_rate ); + +#define GME_COPY_FIELD( in, out, name ) \ + { Gme_File::copy_field_( out->name, in.name, sizeof in.name ); } + +#ifndef GME_FILE_READER + #ifdef HAVE_ZLIB_H + #define GME_FILE_READER Gzip_File_Reader + #else + #define GME_FILE_READER Std_File_Reader + #endif +#elif defined (GME_FILE_READER_INCLUDE) + #include GME_FILE_READER_INCLUDE +#endif + +inline gme_type_t Gme_File::type() const { return type_; } +inline int Gme_File::error_count() const { return warning_ != 0; } +inline int Gme_File::track_count() const { return track_count_; } + +inline const char* Gme_File::warning() +{ + const char* s = warning_; + warning_ = 0; + return s; +} + +#endif diff --git a/game-music-emu/gme/Gym_Emu.cpp b/game-music-emu/gme/Gym_Emu.cpp new file mode 100644 index 000000000..de34f64eb --- /dev/null +++ b/game-music-emu/gme/Gym_Emu.cpp @@ -0,0 +1,380 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Gym_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +double const min_tempo = 0.25; +double const oversample_factor = 5 / 3.0; +double const fm_gain = 3.0; + +const long base_clock = 53700300; +const long clock_rate = base_clock / 15; + +Gym_Emu::Gym_Emu() +{ + data = 0; + pos = 0; + set_type( gme_gym_type ); + + static const char* const names [] = { + "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" + }; + set_voice_names( names ); + set_silence_lookahead( 1 ); // tracks should already be trimmed +} + +Gym_Emu::~Gym_Emu() { } + +// Track info + +static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out ) +{ + if ( !memcmp( h.tag, "GYMX", 4 ) ) + { + length = length * 50 / 3; // 1000 / 60 + long loop = get_le32( h.loop_start ); + if ( loop ) + { + out->intro_length = loop * 50 / 3; + out->loop_length = length - out->intro_length; + } + else + { + out->length = length; + out->intro_length = length; // make it clear that track is no longer than length + out->loop_length = 0; + } + + // more stupidity where the field should have been left + if ( strcmp( h.song, "Unknown Song" ) ) + GME_COPY_FIELD( h, out, song ); + + if ( strcmp( h.game, "Unknown Game" ) ) + GME_COPY_FIELD( h, out, game ); + + if ( strcmp( h.copyright, "Unknown Publisher" ) ) + GME_COPY_FIELD( h, out, copyright ); + + if ( strcmp( h.dumper, "Unknown Person" ) ) + GME_COPY_FIELD( h, out, dumper ); + + if ( strcmp( h.comment, "Header added by YMAMP" ) ) + GME_COPY_FIELD( h, out, comment ); + } +} + +blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const +{ + get_gym_info( header_, track_length(), out ); + return 0; +} + +static long gym_track_length( byte const* p, byte const* end ) +{ + long time = 0; + while ( p < end ) + { + switch ( *p++ ) + { + case 0: + time++; + break; + + case 1: + case 2: + p += 2; + break; + + case 3: + p += 1; + break; + } + } + return time; +} + +long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); } + +static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 ) +{ + if ( size < 4 ) + return gme_wrong_file_type; + + if ( memcmp( in, "GYMX", 4 ) == 0 ) + { + if ( size < Gym_Emu::header_size + 1 ) + return gme_wrong_file_type; + + if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 ) + return "Packed GYM file not supported"; + + if ( data_offset ) + *data_offset = Gym_Emu::header_size; + } + else if ( *in > 3 ) + { + return gme_wrong_file_type; + } + + return 0; +} + +struct Gym_File : Gme_Info_ +{ + byte const* file_begin; + byte const* file_end; + int data_offset; + + Gym_File() { set_type( gme_gym_type ); } + + blargg_err_t load_mem_( byte const* in, long size ) + { + file_begin = in; + file_end = in + size; + data_offset = 0; + return check_header( in, size, &data_offset ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + long length = gym_track_length( &file_begin [data_offset], file_end ); + get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out ); + return 0; + } +}; + +static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; } +static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; } + +static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 }; +gme_type_t const gme_gym_type = &gme_gym_type_; + +// Setup + +blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate ) +{ + blip_eq_t eq( -32, 8000, sample_rate ); + apu.treble_eq( eq ); + dac_synth.treble_eq( eq ); + apu.volume( 0.135 * fm_gain * gain() ); + dac_synth.volume( 0.125 / 256 * fm_gain * gain() ); + double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() ); + fm_sample_rate = sample_rate * factor; + + RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) ); + blip_buf.clock_rate( clock_rate ); + + RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) ); + RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) ); + + return 0; +} + +void Gym_Emu::set_tempo_( double t ) +{ + if ( t < min_tempo ) + { + set_tempo( min_tempo ); + return; + } + + if ( blip_buf.sample_rate() ) + { + clocks_per_frame = long (clock_rate / 60 / tempo()); + Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) ); + } +} + +void Gym_Emu::mute_voices_( int mask ) +{ + Music_Emu::mute_voices_( mask ); + fm.mute_voices( mask ); + dac_muted = (mask & 0x40) != 0; + apu.output( (mask & 0x80) ? 0 : &blip_buf ); +} + +blargg_err_t Gym_Emu::load_mem_( byte const* in, long size ) +{ + assert( offsetof (header_t,packed [4]) == header_size ); + int offset = 0; + RETURN_ERR( check_header( in, size, &offset ) ); + set_voice_count( 8 ); + + data = in + offset; + data_end = in + size; + loop_begin = 0; + + if ( offset ) + header_ = *(header_t const*) in; + else + memset( &header_, 0, sizeof header_ ); + + return 0; +} + +// Emulation + +blargg_err_t Gym_Emu::start_track_( int track ) +{ + RETURN_ERR( Music_Emu::start_track_( track ) ); + + pos = data; + loop_remain = get_le32( header_.loop_start ); + + prev_dac_count = 0; + dac_enabled = false; + dac_amp = -1; + + fm.reset(); + apu.reset(); + blip_buf.clear(); + Dual_Resampler::clear(); + return 0; +} + +void Gym_Emu::run_dac( int dac_count ) +{ + // Guess beginning and end of sample and adjust rate and buffer position accordingly. + + // count dac samples in next frame + int next_dac_count = 0; + const byte* p = this->pos; + int cmd; + while ( (cmd = *p++) != 0 ) + { + int data = *p++; + if ( cmd <= 2 ) + ++p; + if ( cmd == 1 && data == 0x2A ) + next_dac_count++; + } + + // detect beginning and end of sample + int rate_count = dac_count; + int start = 0; + if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count ) + { + rate_count = next_dac_count; + start = next_dac_count - dac_count; + } + else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count ) + { + rate_count = prev_dac_count; + } + + // Evenly space samples within buffer section being used + blip_resampled_time_t period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count; + + blip_resampled_time_t time = blip_buf.resampled_time( 0 ) + + period * start + (period >> 1); + + int dac_amp = this->dac_amp; + if ( dac_amp < 0 ) + dac_amp = dac_buf [0]; + + for ( int i = 0; i < dac_count; i++ ) + { + int delta = dac_buf [i] - dac_amp; + dac_amp += delta; + dac_synth.offset_resampled( time, delta, &blip_buf ); + time += period; + } + this->dac_amp = dac_amp; +} + +void Gym_Emu::parse_frame() +{ + int dac_count = 0; + const byte* pos = this->pos; + + if ( loop_remain && !--loop_remain ) + loop_begin = pos; // find loop on first time through sequence + + int cmd; + while ( (cmd = *pos++) != 0 ) + { + int data = *pos++; + if ( cmd == 1 ) + { + int data2 = *pos++; + if ( data != 0x2A ) + { + if ( data == 0x2B ) + dac_enabled = (data2 & 0x80) != 0; + + fm.write0( data, data2 ); + } + else if ( dac_count < (int) sizeof dac_buf ) + { + dac_buf [dac_count] = data2; + dac_count += dac_enabled; + } + } + else if ( cmd == 2 ) + { + fm.write1( data, *pos++ ); + } + else if ( cmd == 3 ) + { + apu.write_data( 0, data ); + } + else + { + // to do: many GYM streams are full of errors, and error count should + // reflect cases where music is really having problems + //log_error(); + --pos; // put data back + } + } + + // loop + if ( pos >= data_end ) + { + check( pos == data_end ); + + if ( loop_begin ) + pos = loop_begin; + else + set_track_ended(); + } + this->pos = pos; + + // dac + if ( dac_count && !dac_muted ) + run_dac( dac_count ); + prev_dac_count = dac_count; +} + +int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ) +{ + if ( !track_ended() ) + parse_frame(); + + apu.end_frame( blip_time ); + + memset( buf, 0, sample_count * sizeof *buf ); + fm.run( sample_count >> 1, buf ); + + return sample_count; +} + +blargg_err_t Gym_Emu::play_( long count, sample_t* out ) +{ + Dual_Resampler::dual_play( count, out, blip_buf ); + return 0; +} diff --git a/game-music-emu/gme/Gym_Emu.h b/game-music-emu/gme/Gym_Emu.h new file mode 100644 index 000000000..05419ea20 --- /dev/null +++ b/game-music-emu/gme/Gym_Emu.h @@ -0,0 +1,82 @@ +// Sega Genesis/Mega Drive GYM music file emulator +// Includes with PCM timing recovery to improve sample quality. + +// Game_Music_Emu 0.5.2 +#ifndef GYM_EMU_H +#define GYM_EMU_H + +#include "Dual_Resampler.h" +#include "Ym2612_Emu.h" +#include "Music_Emu.h" +#include "Sms_Apu.h" + +class Gym_Emu : public Music_Emu, private Dual_Resampler { +public: + // GYM file header + enum { header_size = 428 }; + struct header_t + { + char tag [4]; + char song [32]; + char game [32]; + char copyright [32]; + char emulator [32]; + char dumper [32]; + char comment [256]; + byte loop_start [4]; // in 1/60 seconds, 0 if not looped + byte packed [4]; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_gym_type; } + +public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + enum { gym_rate = 60 }; + long track_length() const; // use track_info() + +public: + Gym_Emu(); + ~Gym_Emu(); +protected: + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t set_sample_rate_( long sample_rate ); + blargg_err_t start_track_( int ); + blargg_err_t play_( long count, sample_t* ); + void mute_voices_( int ); + void set_tempo_( double ); + int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ); +private: + // sequence data begin, loop begin, current position, end + const byte* data; + const byte* loop_begin; + const byte* pos; + const byte* data_end; + blargg_long loop_remain; // frames remaining until loop beginning has been located + header_t header_; + double fm_sample_rate; + blargg_long clocks_per_frame; + void parse_frame(); + + // dac (pcm) + int dac_amp; + int prev_dac_count; + bool dac_enabled; + bool dac_muted; + void run_dac( int ); + + // sound + Blip_Buffer blip_buf; + Ym2612_Emu fm; + Blip_Synth dac_synth; + Sms_Apu apu; + byte dac_buf [1024]; +}; + +#endif diff --git a/game-music-emu/gme/Hes_Apu.cpp b/game-music-emu/gme/Hes_Apu.cpp new file mode 100644 index 000000000..cfb171d5b --- /dev/null +++ b/game-music-emu/gme/Hes_Apu.cpp @@ -0,0 +1,315 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Hes_Apu.h" + +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +bool const center_waves = true; // reduces asymmetry and clamping when starting notes + +Hes_Apu::Hes_Apu() +{ + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + osc->outputs [0] = 0; + osc->outputs [1] = 0; + osc->chans [0] = 0; + osc->chans [1] = 0; + osc->chans [2] = 0; + } + while ( osc != oscs ); + + reset(); +} + +void Hes_Apu::reset() +{ + latch = 0; + balance = 0xFF; + + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + memset( osc, 0, offsetof (Hes_Osc,outputs) ); + osc->noise_lfsr = 1; + osc->control = 0x40; + osc->balance = 0xFF; + } + while ( osc != oscs ); +} + +void Hes_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + oscs [index].chans [0] = center; + oscs [index].chans [1] = left; + oscs [index].chans [2] = right; + + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + balance_changed( *osc ); + } + while ( osc != oscs ); +} + +void Hes_Osc::run_until( synth_t& synth_, blip_time_t end_time ) +{ + Blip_Buffer* const osc_outputs_0 = outputs [0]; // cache often-used values + if ( osc_outputs_0 && control & 0x80 ) + { + int dac = this->dac; + + int const volume_0 = volume [0]; + { + int delta = dac * volume_0 - last_amp [0]; + if ( delta ) + synth_.offset( last_time, delta, osc_outputs_0 ); + osc_outputs_0->set_modified(); + } + + Blip_Buffer* const osc_outputs_1 = outputs [1]; + int const volume_1 = volume [1]; + if ( osc_outputs_1 ) + { + int delta = dac * volume_1 - last_amp [1]; + if ( delta ) + synth_.offset( last_time, delta, osc_outputs_1 ); + osc_outputs_1->set_modified(); + } + + blip_time_t time = last_time + delay; + if ( time < end_time ) + { + if ( noise & 0x80 ) + { + if ( volume_0 | volume_1 ) + { + // noise + int const period = (32 - (noise & 0x1F)) * 64; // TODO: correct? + unsigned noise_lfsr = this->noise_lfsr; + do + { + int new_dac = 0x1F & (unsigned)-(signed)(noise_lfsr >> 1 & 1); + // Implemented using "Galios configuration" + // TODO: find correct LFSR algorithm + noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & (unsigned)-(signed)(noise_lfsr & 1)); + //noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1)); + int delta = new_dac - dac; + if ( delta ) + { + dac = new_dac; + synth_.offset( time, delta * volume_0, osc_outputs_0 ); + if ( osc_outputs_1 ) + synth_.offset( time, delta * volume_1, osc_outputs_1 ); + } + time += period; + } + while ( time < end_time ); + + this->noise_lfsr = noise_lfsr; + assert( noise_lfsr ); + } + } + else if ( !(control & 0x40) ) + { + // wave + int phase = (this->phase + 1) & 0x1F; // pre-advance for optimal inner loop + int period = this->period * 2; + if ( period >= 14 && (volume_0 | volume_1) ) + { + do + { + int new_dac = wave [phase]; + phase = (phase + 1) & 0x1F; + int delta = new_dac - dac; + if ( delta ) + { + dac = new_dac; + synth_.offset( time, delta * volume_0, osc_outputs_0 ); + if ( osc_outputs_1 ) + synth_.offset( time, delta * volume_1, osc_outputs_1 ); + } + time += period; + } + while ( time < end_time ); + } + else + { + if ( !period ) + { + // TODO: Gekisha Boy assumes that period = 0 silences wave + //period = 0x1000 * 2; + period = 1; + //if ( !(volume_0 | volume_1) ) + // dprintf( "Used period 0\n" ); + } + + // maintain phase when silent + blargg_long count = (end_time - time + period - 1) / period; + phase += count; // phase will be masked below + time += count * period; + } + this->phase = (phase - 1) & 0x1F; // undo pre-advance + } + } + time -= end_time; + if ( time < 0 ) + time = 0; + delay = time; + + this->dac = dac; + last_amp [0] = dac * volume_0; + last_amp [1] = dac * volume_1; + } + last_time = end_time; +} + +void Hes_Apu::balance_changed( Hes_Osc& osc ) +{ + static short const log_table [32] = { // ~1.5 db per step + #define ENTRY( factor ) short (factor * Hes_Osc::amp_range / 31.0 + 0.5) + ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ), + ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ), + ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ), + ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ), + ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ), + ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ), + ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ), + ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ), + #undef ENTRY + }; + + int vol = (osc.control & 0x1F) - 0x1E * 2; + + int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E); + if ( left < 0 ) left = 0; + + int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E); + if ( right < 0 ) right = 0; + + left = log_table [left ]; + right = log_table [right]; + + // optimizing for the common case of being centered also allows easy + // panning using Effects_Buffer + osc.outputs [0] = osc.chans [0]; // center + osc.outputs [1] = 0; + if ( left != right ) + { + osc.outputs [0] = osc.chans [1]; // left + osc.outputs [1] = osc.chans [2]; // right + } + + if ( center_waves ) + { + osc.last_amp [0] += (left - osc.volume [0]) * 16; + osc.last_amp [1] += (right - osc.volume [1]) * 16; + } + + osc.volume [0] = left; + osc.volume [1] = right; +} + +void Hes_Apu::write_data( blip_time_t time, int addr, int data ) +{ + if ( addr == 0x800 ) + { + latch = data & 7; + } + else if ( addr == 0x801 ) + { + if ( balance != data ) + { + balance = data; + + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + osc->run_until( synth, time ); + balance_changed( *oscs ); + } + while ( osc != oscs ); + } + } + else if ( latch < osc_count ) + { + Hes_Osc& osc = oscs [latch]; + osc.run_until( synth, time ); + switch ( addr ) + { + case 0x802: + osc.period = (osc.period & 0xF00) | data; + break; + + case 0x803: + osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8); + break; + + case 0x804: + if ( osc.control & 0x40 & ~data ) + osc.phase = 0; + osc.control = data; + balance_changed( osc ); + break; + + case 0x805: + osc.balance = data; + balance_changed( osc ); + break; + + case 0x806: + data &= 0x1F; + if ( !(osc.control & 0x40) ) + { + osc.wave [osc.phase] = data; + osc.phase = (osc.phase + 1) & 0x1F; + } + else if ( osc.control & 0x80 ) + { + osc.dac = data; + } + break; + + case 0x807: + if ( &osc >= &oscs [4] ) + osc.noise = data; + break; + + case 0x809: + if ( !(data & 0x80) && (data & 0x03) != 0 ) + dprintf( "HES LFO not supported\n" ); + } + } +} + +void Hes_Apu::end_frame( blip_time_t end_time ) +{ + Hes_Osc* osc = &oscs [osc_count]; + do + { + osc--; + if ( end_time > osc->last_time ) + osc->run_until( synth, end_time ); + assert( osc->last_time >= end_time ); + osc->last_time -= end_time; + } + while ( osc != oscs ); +} diff --git a/game-music-emu/gme/Hes_Apu.h b/game-music-emu/gme/Hes_Apu.h new file mode 100644 index 000000000..ca0c932fd --- /dev/null +++ b/game-music-emu/gme/Hes_Apu.h @@ -0,0 +1,66 @@ +// Turbo Grafx 16 (PC Engine) PSG sound chip emulator + +// Game_Music_Emu 0.5.2 +#ifndef HES_APU_H +#define HES_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct Hes_Osc +{ + unsigned char wave [32]; + short volume [2]; + int last_amp [2]; + int delay; + int period; + unsigned char noise; + unsigned char phase; + unsigned char balance; + unsigned char dac; + blip_time_t last_time; + + Blip_Buffer* outputs [2]; + Blip_Buffer* chans [3]; + unsigned noise_lfsr; + unsigned char control; + + enum { amp_range = 0x8000 }; + typedef Blip_Synth synth_t; + + void run_until( synth_t& synth, blip_time_t ); +}; + +class Hes_Apu { +public: + void treble_eq( blip_eq_t const& ); + void volume( double ); + + enum { osc_count = 6 }; + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + void reset(); + + enum { start_addr = 0x0800 }; + enum { end_addr = 0x0809 }; + void write_data( blip_time_t, int addr, int data ); + + void end_frame( blip_time_t ); + +public: + Hes_Apu(); +private: + Hes_Osc oscs [osc_count]; + int latch; + int balance; + Hes_Osc::synth_t synth; + + void balance_changed( Hes_Osc& ); + void recalc_chans(); +}; + +inline void Hes_Apu::volume( double v ) { synth.volume( 1.8 / osc_count / Hes_Osc::amp_range * v ); } + +inline void Hes_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + +#endif diff --git a/game-music-emu/gme/Hes_Cpu.cpp b/game-music-emu/gme/Hes_Cpu.cpp new file mode 100644 index 000000000..2615a0bb9 --- /dev/null +++ b/game-music-emu/gme/Hes_Cpu.cpp @@ -0,0 +1,1303 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Hes_Cpu.h" + +#include "blargg_endian.h" + +//#include "hes_cpu_log.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// TODO: support T flag, including clearing it at appropriate times? + +// all zero-page should really use whatever is at page 1, but that would +// reduce efficiency quite a bit +int const ram_addr = 0x2000; + +#define FLUSH_TIME() (void) (s.time = s_time) +#define CACHE_TIME() (void) (s_time = s.time) + +#include "hes_cpu_io.h" + +#include "blargg_source.h" + +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +// status flags +int const st_n = 0x80; +int const st_v = 0x40; +int const st_t = 0x20; +int const st_b = 0x10; +int const st_d = 0x08; +int const st_i = 0x04; +int const st_z = 0x02; +int const st_c = 0x01; + +void Hes_Cpu::reset() +{ + check( state == &state_ ); + state = &state_; + + state_.time = 0; + state_.base = 0; + irq_time_ = future_hes_time; + end_time_ = future_hes_time; + + r.status = st_i; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + blargg_verify_byte_order(); +} + +void Hes_Cpu::set_mmr( int reg, int bank ) +{ + assert( (unsigned) reg <= page_count ); // allow page past end to be set + assert( (unsigned) bank < 0x100 ); + mmr [reg] = bank; + uint8_t const* code = CPU_SET_MMR( this, reg, bank ); + state->code_map [reg] = code - PAGE_OFFSET( reg << page_shift ); +} + +#define TIME (s_time + s.base) + +#define READ( addr ) CPU_READ( this, (addr), TIME ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );} +#define READ_LOW( addr ) (ram [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) +#define READ_PROG( addr ) (s.code_map [(addr) >> page_shift] [PAGE_OFFSET( addr )]) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; +typedef blargg_long fint32; + +bool Hes_Cpu::run( hes_time_t end_time ) +{ + bool illegal_encountered = false; + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + // even on x86, using s.time in place of s_time was slower + fint16 s_time = s.time; + + // registers + fuint16 pc = r.pc; + fuint8 a = r.a; + fuint8 x = r.x; + fuint8 y = r.y; + fuint16 sp; + SET_SP( r.sp ); + + #define IS_NEG (nz & 0x8080) + + #define CALC_STATUS( out ) do {\ + out = status & (st_v | st_d | st_i);\ + out |= ((nz >> 8) | nz) & st_n;\ + out |= c >> 8 & st_c;\ + if ( !(nz & 0xFF) ) out |= st_z;\ + } while ( 0 ) + + #define SET_STATUS( in ) do {\ + status = in & (st_v | st_d | st_i);\ + nz = in << 8;\ + c = nz;\ + nz |= ~in & st_z;\ + } while ( 0 ) + + fuint8 status; + fuint16 c; // carry set if (c & 0x100) != 0 + fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + fuint8 temp = r.status; + SET_STATUS( temp ); + } + + goto loop; +branch_not_taken: + s_time -= 2; +loop: + + #ifndef NDEBUG + { + hes_time_t correct = end_time_; + if ( !(status & st_i) && correct > irq_time_ ) + correct = irq_time_; + check( s.base == correct ); + /* + static long count; + if ( count == 1844 ) Debugger(); + if ( s.base != correct ) dprintf( "%ld\n", count ); + count++; + */ + } + #endif + + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + + uint8_t const* instr = s.code_map [pc >> page_shift]; + fuint8 opcode; + + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + opcode = instr [pc]; + pc++; + instr += pc; + #else + instr += PAGE_OFFSET( pc ); + opcode = *instr++; + pc++; + #endif + + // TODO: each reference lists slightly different timing values, ugh + static uint8_t const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,7,3, 4,6,4,6,7,3,2,2,2,7,5,7,6,// 0 + 4,7,7, 4,6,4,6,7,2,5,2,2,7,5,7,6,// 1 + 7,7,3, 4,4,4,6,7,4,2,2,2,5,5,7,6,// 2 + 4,7,7, 2,4,4,6,7,2,5,2,2,5,5,7,6,// 3 + 7,7,3, 4,8,4,6,7,3,2,2,2,4,5,7,6,// 4 + 4,7,7, 5,2,4,6,7,2,5,3,2,2,5,7,6,// 5 + 7,7,2, 2,4,4,6,7,4,2,2,2,7,5,7,6,// 6 + 4,7,7,17,4,4,6,7,2,5,4,2,7,5,7,6,// 7 + 4,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// 8 + 4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// 9 + 2,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,6,// A + 4,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,6,// B + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// C + 4,7,7,17,2,4,6,7,2,5,3,2,2,5,7,6,// D + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,6,// E + 4,7,7,17,2,4,6,7,2,5,4,2,2,5,7,6 // F + }; // 0x00 was 8 + + fuint16 data; + data = clock_table [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = *instr; + + #ifdef HES_CPU_LOG_H + log_cpu( "new", pc - 1, opcode, instr [0], instr [1], instr [2], + instr [3], instr [4], instr [5] ); + //log_opcode( opcode ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()); +#define GET_ADDR() GET_LE16( instr ) + +// TODO: is the penalty really always added? the original 6502 was much better +//#define PAGE_CROSS_PENALTY( lsb ) (void) (s_time += (lsb) >> 8) +#define PAGE_CROSS_PENALTY( lsb ) + +// Branch + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + fint16 offset = (BOOST::int8_t) data;\ + pc++;\ + if ( !(cond) ) goto branch_not_taken;\ + pc = BOOST::uint16_t (pc + offset);\ + goto loop;\ +} + + case 0xF0: // BEQ + BRANCH( !((uint8_t) nz) ); + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x10: // BPL + BRANCH( !IS_NEG ); + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x80: // BRA + branch_taken: + BRANCH( true ); + + case 0xFF: + if ( pc == idle_addr + 1 ) + goto idle_done; + case 0x0F: // BBRn + case 0x1F: + case 0x2F: + case 0x3F: + case 0x4F: + case 0x5F: + case 0x6F: + case 0x7F: + case 0x8F: // BBSn + case 0x9F: + case 0xAF: + case 0xBF: + case 0xCF: + case 0xDF: + case 0xEF: { + fuint16 t = 0x101 * READ_LOW( data ); + t ^= 0xFF; + pc++; + data = GET_MSB(); + BRANCH( t & (1 << (opcode >> 4)) ) + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0x7C: // JMP (ind+X) + data += x; + case 0x6C:{// JMP (ind) + data += 0x100 * GET_MSB(); + pc = GET_LE16( &READ_PROG( data ) ); + goto loop; + } + +// Subroutine + + case 0x44: // BSR + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, pc ); + goto branch_taken; + + case 0x20: { // JSR + fuint16 temp = pc + 1; + pc = GET_ADDR(); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x60: // RTS + pc = 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); + pc += 1 + READ_LOW( sp ); + sp = (sp - 0xFE) | 0x100; + goto loop; + + case 0x00: // BRK + goto handle_brk; + +// Common + + case 0xBD:{// LDA abs,X + PAGE_CROSS_PENALTY( data + x ); + fuint16 addr = GET_ADDR() + x; + pc += 2; + CPU_READ_FAST( this, addr, TIME, nz ); + a = nz; + goto loop; + } + + case 0x9D:{// STA abs,X + fuint16 addr = GET_ADDR() + x; + pc += 2; + CPU_WRITE_FAST( this, addr, a, TIME ); + goto loop; + } + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xAE:{// LDX abs + fuint16 addr = GET_ADDR(); + pc += 2; + CPU_READ_FAST( this, addr, TIME, nz ); + x = nz; + goto loop; + } + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + +// Load/store + + { + fuint16 addr; + case 0x91: // STA (ind),Y + addr = 0x100 * READ_LOW( uint8_t (data + 1) ); + addr += READ_LOW( data ) + y; + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + data = uint8_t (data + x); + case 0x92: // STA (ind) + addr = 0x100 * READ_LOW( uint8_t (data + 1) ); + addr += READ_LOW( data ); + pc++; + goto sta_ptr; + + case 0x99: // STA abs,Y + data += y; + case 0x8D: // STA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + sta_ptr: + CPU_WRITE_FAST( this, addr, a, TIME ); + goto loop; + } + + { + fuint16 addr; + case 0xA1: // LDA (ind,X) + data = uint8_t (data + x); + case 0xB2: // LDA (ind) + addr = 0x100 * READ_LOW( uint8_t (data + 1) ); + addr += READ_LOW( data ); + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + PAGE_CROSS_PENALTY( addr ); + addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); + pc++; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + data += y; + PAGE_CROSS_PENALTY( data ); + case 0xAD: // LDA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + a_nz_read_addr: + CPU_READ_FAST( this, addr, TIME, nz ); + a = nz; + goto loop; + } + + case 0xBE:{// LDX abs,y + PAGE_CROSS_PENALTY( data + y ); + fuint16 addr = GET_ADDR() + y; + pc += 2; + FLUSH_TIME(); + x = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( uint8_t (data + x) ); + pc++; + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +// Bit operations + + case 0x3C: // BIT abs,x + data += x; + case 0x2C:{// BIT abs + fuint16 addr; + ADD_PAGE( addr ); + FLUSH_TIME(); + nz = READ( addr ); + CACHE_TIME(); + goto bit_common; + } + case 0x34: // BIT zp,x + data = uint8_t (data + x); + case 0x24: // BIT zp + data = READ_LOW( data ); + case 0x89: // BIT imm + nz = data; + bit_common: + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( nz & a ) + goto loop; // Z should be clear, and nz must be non-zero if nz & a is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + fuint16 addr; + + case 0xB3: // TST abs,x + addr = GET_MSB() + x; + goto tst_abs; + + case 0x93: // TST abs + addr = GET_MSB(); + tst_abs: + addr += 0x100 * instr [2]; + pc++; + FLUSH_TIME(); + nz = READ( addr ); + CACHE_TIME(); + goto tst_common; + } + + case 0xA3: // TST zp,x + nz = READ_LOW( uint8_t (GET_MSB() + x) ); + goto tst_common; + + case 0x83: // TST zp + nz = READ_LOW( GET_MSB() ); + tst_common: + pc += 2; + status &= ~st_v; + status |= nz & st_v; + if ( nz & data ) + goto loop; // Z should be clear, and nz must be non-zero if nz & data is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + fuint16 addr; + case 0x0C: // TSB abs + case 0x1C: // TRB abs + addr = GET_ADDR(); + pc++; + goto txb_addr; + + // TODO: everyone lists different behaviors for the status flags, ugh + case 0x04: // TSB zp + case 0x14: // TRB zp + addr = data + ram_addr; + txb_addr: + FLUSH_TIME(); + nz = a | READ( addr ); + if ( opcode & 0x10 ) + nz ^= a; // bits from a will already be set, so this clears them + status &= ~st_v; + status |= nz & st_v; + pc++; + WRITE( addr, nz ); + CACHE_TIME(); + goto loop; + } + + case 0x07: // RMBn + case 0x17: + case 0x27: + case 0x37: + case 0x47: + case 0x57: + case 0x67: + case 0x77: + pc++; + READ_LOW( data ) &= ~(1 << (opcode >> 4)); + goto loop; + + case 0x87: // SMBn + case 0x97: + case 0xA7: + case 0xB7: + case 0xC7: + case 0xD7: + case 0xE7: + case 0xF7: + pc++; + READ_LOW( data ) |= 1 << ((opcode >> 4) - 8); + goto loop; + +// Load/store + + case 0x9E: // STZ abs,x + data += x; + case 0x9C: // STZ abs + ADD_PAGE( data ); + pc++; + FLUSH_TIME(); + WRITE( data, 0 ); + CACHE_TIME(); + goto loop; + + case 0x74: // STZ zp,x + data = uint8_t (data + x); + case 0x64: // STZ zp + pc++; + WRITE_LOW( data, 0 ); + goto loop; + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + PAGE_CROSS_PENALTY( data ); + case 0xAC:{// LDY abs + fuint16 addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + { + fuint8 temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + fuint16 addr = GET_ADDR(); + pc += 2; + FLUSH_TIME(); + WRITE( addr, temp ); + CACHE_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + fuint16 addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + fuint16 addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + +#define ARITH_ADDR_MODES( op )\ + case op - 0x04: /* (ind,x) */\ + data = uint8_t (data + x);\ + case op + 0x0D: /* (ind) */\ + data = 0x100 * READ_LOW( uint8_t (data + 1) ) + READ_LOW( data );\ + goto ptr##op;\ + case op + 0x0C:{/* (ind),y */\ + fuint16 temp = READ_LOW( data ) + y;\ + PAGE_CROSS_PENALTY( temp );\ + data = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\ + goto ptr##op;\ + }\ + case op + 0x10: /* zp,X */\ + data = uint8_t (data + x);\ + case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ + case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ + case op + 0x18: /* abs,X */\ + data += x;\ + ind##op:\ + PAGE_CROSS_PENALTY( data );\ + case op + 0x08: /* abs */\ + ADD_PAGE( data );\ + ptr##op:\ + FLUSH_TIME();\ + data = READ( data );\ + CACHE_TIME();\ + case op + 0x04: /* imm */\ + imm##op: + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + if ( status & st_d ) + dprintf( "Decimal mode not supported\n" ); + fint16 carry = c >> 8 & 1; + fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + status &= ~st_v; + status |= ov >> 2 & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + fint16 temp = c >> 8 & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE( data ); + FLUSH_TIME(); + int temp = READ( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE( data ); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz |= (c = READ( data ) << 1); + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + +#define INC_DEC_AXY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + + case 0x1A: // INA + INC_DEC_AXY( a, +1 ) + + case 0xE8: // INX + INC_DEC_AXY( x, +1 ) + + case 0xC8: // INY + INC_DEC_AXY( y, +1 ) + + case 0x3A: // DEA + INC_DEC_AXY( a, -1 ) + + case 0xCA: // DEX + INC_DEC_AXY( x, -1 ) + + case 0x88: // DEY + INC_DEC_AXY( y, -1 ) + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = (unsigned) -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = (unsigned) -1; + inc_common: + FLUSH_TIME(); + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAA: // TAX + x = a; + nz = a; + goto loop; + + case 0x8A: // TXA + a = x; + nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + + #define SWAP_REGS( r1, r2 ) {\ + fuint8 t = r1;\ + r1 = r2;\ + r2 = t;\ + goto loop;\ + } + + case 0x02: // SXY + SWAP_REGS( x, y ); + + case 0x22: // SAX + SWAP_REGS( a, x ); + + case 0x42: // SAY + SWAP_REGS( a, y ); + + case 0x62: // CLA + a = 0; + goto loop; + + case 0x82: // CLX + x = 0; + goto loop; + + case 0xC2: // CLY + y = 0; + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); + goto loop; + + case 0xDA: // PHX + PUSH( x ); + goto loop; + + case 0x5A: // PHY + PUSH( y ); + goto loop; + + case 0x40:{// RTI + fuint8 temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xFF) ); + pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; + sp = (sp - 0xFD) | 0x100; + data = status; + SET_STATUS( temp ); + this->r.status = status; // update externally-visible I flag + if ( (data ^ status) & st_i ) + { + hes_time_t new_time = end_time_; + if ( !(status & st_i) && new_time > irq_time_ ) + new_time = irq_time_; + blargg_long delta = s.base - new_time; + s.base = new_time; + s_time += delta; + } + goto loop; + } + + #define POP() READ_LOW( sp ); sp = (sp - 0xFF) | 0x100 + + case 0x68: // PLA + a = nz = POP(); + goto loop; + + case 0xFA: // PLX + x = nz = POP(); + goto loop; + + case 0x7A: // PLY + y = nz = POP(); + goto loop; + + case 0x28:{// PLP + fuint8 temp = POP(); + fuint8 changed = status ^ temp; + SET_STATUS( temp ); + if ( !(changed & st_i) ) + goto loop; // I flag didn't change + if ( status & st_i ) + goto handle_sei; + goto handle_cli; + } + #undef POP + + case 0x08: { // PHP + fuint8 temp; + CALC_STATUS( temp ); + PUSH( temp | st_b ); + goto loop; + } + +// Flags + + case 0x38: // SEC + c = (unsigned) ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) + { + if ( TIME < irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + irq_time_ = s.base; // TODO: remove, as only to satisfy debug check in loop + goto loop; + } + delayed_cli: + dprintf( "Delayed CLI not supported\n" ); // TODO: implement + goto loop; + } + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + dprintf( "Delayed SEI not supported\n" ); // TODO: implement + goto loop; + } + +// Special + + case 0x53:{// TAM + fuint8 const bits = data; // avoid using data across function call + pc++; + for ( int i = 0; i < 8; i++ ) + if ( bits & (1 << i) ) + set_mmr( i, a ); + goto loop; + } + + case 0x43:{// TMA + pc++; + byte const* in = mmr; + do + { + if ( data & 1 ) + a = *in; + in++; + } + while ( (data >>= 1) != 0 ); + goto loop; + } + + case 0x03: // ST0 + case 0x13: // ST1 + case 0x23:{// ST2 + fuint16 addr = opcode >> 4; + if ( addr ) + addr++; + pc++; + FLUSH_TIME(); + CPU_WRITE_VDP( this, addr, data, TIME ); + CACHE_TIME(); + goto loop; + } + + case 0xEA: // NOP + goto loop; + + case 0x54: // CSL + dprintf( "CSL not supported\n" ); + illegal_encountered = true; + goto loop; + + case 0xD4: // CSH + goto loop; + + case 0xF4: { // SET + //fuint16 operand = GET_MSB(); + dprintf( "SET not handled\n" ); + //switch ( data ) + //{ + //} + illegal_encountered = true; + goto loop; + } + +// Block transfer + + { + fuint16 in_alt; + fint16 in_inc; + fuint16 out_alt; + fint16 out_inc; + + case 0xE3: // TIA + in_alt = 0; + goto bxfer_alt; + + case 0xF3: // TAI + in_alt = 1; + bxfer_alt: + in_inc = in_alt ^ 1; + out_alt = in_inc; + out_inc = in_alt; + goto bxfer; + + case 0xD3: // TIN + in_inc = 1; + out_inc = 0; + goto bxfer_no_alt; + + case 0xC3: // TDD + in_inc = -1; + out_inc = -1; + goto bxfer_no_alt; + + case 0x73: // TII + in_inc = 1; + out_inc = 1; + bxfer_no_alt: + in_alt = 0; + out_alt = 0; + bxfer: + fuint16 in = GET_LE16( instr + 0 ); + fuint16 out = GET_LE16( instr + 2 ); + int count = GET_LE16( instr + 4 ); + if ( !count ) + count = 0x10000; + pc += 6; + WRITE_LOW( 0x100 | (sp - 1), y ); + WRITE_LOW( 0x100 | (sp - 2), a ); + WRITE_LOW( 0x100 | (sp - 3), x ); + FLUSH_TIME(); + do + { + // TODO: reads from $0800-$1400 in I/O page return 0 and don't access I/O + fuint8 t = READ( in ); + in += in_inc; + in &= 0xFFFF; + s.time += 6; + if ( in_alt ) + in_inc = -in_inc; + WRITE( out, t ); + out += out_inc; + out &= 0xFFFF; + if ( out_alt ) + out_inc = -out_inc; + } + while ( --count ); + CACHE_TIME(); + goto loop; + } + +// Illegal + + default: + assert( (unsigned) opcode <= 0xFF ); + dprintf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 ); + illegal_encountered = true; + goto loop; + } + assert( false ); + + int result_; +handle_brk: + pc++; + result_ = 6; + +interrupt: + { + s_time += 7; + + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + pc = GET_LE16( &READ_PROG( 0xFFF0 ) + result_ ); + + sp = (sp - 3) | 0x100; + fuint8 temp; + CALC_STATUS( temp ); + if ( result_ == 6 ) + temp |= st_b; + WRITE_LOW( sp, temp ); + + status &= ~st_d; + status |= st_i; + this->r.status = status; // update externally-visible I flag + + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + goto loop; + } + +idle_done: + s_time = 0; +out_of_time: + pc--; + FLUSH_TIME(); + CPU_DONE( this, TIME, result_ ); + CACHE_TIME(); + if ( result_ > 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + + s.time = s_time; + + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + + { + fuint8 temp; + CALC_STATUS( temp ); + r.status = temp; + } + + this->state_ = s; + this->state = &this->state_; + + return illegal_encountered; +} + diff --git a/game-music-emu/gme/Hes_Cpu.h b/game-music-emu/gme/Hes_Cpu.h new file mode 100644 index 000000000..536e732f8 --- /dev/null +++ b/game-music-emu/gme/Hes_Cpu.h @@ -0,0 +1,124 @@ +// PC Engine CPU emulator for use with HES music files + +// Game_Music_Emu 0.5.2 +#ifndef HES_CPU_H +#define HES_CPU_H + +#include "blargg_common.h" + +typedef blargg_long hes_time_t; // clock cycle count +typedef unsigned hes_addr_t; // 16-bit address +enum { future_hes_time = INT_MAX / 2 + 1 }; + +class Hes_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + void reset(); + + enum { page_size = 0x2000 }; + enum { page_shift = 13 }; + enum { page_count = 8 }; + void set_mmr( int reg, int bank ); + + uint8_t const* get_code( hes_addr_t ); + + uint8_t ram [page_size]; + + // not kept updated during a call to run() + struct registers_t { + BOOST::uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + }; + registers_t r; + + // page mapping registers + uint8_t mmr [page_count + 1]; + + // Set end_time and run CPU from current time. Returns true if any illegal + // instructions were encountered. + bool run( hes_time_t end_time ); + + // Time of beginning of next instruction to be executed + hes_time_t time() const { return state->time + state->base; } + void set_time( hes_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + hes_time_t irq_time() const { return irq_time_; } + void set_irq_time( hes_time_t ); + + hes_time_t end_time() const { return end_time_; } + void set_end_time( hes_time_t ); + + void end_frame( hes_time_t ); + + // Attempt to execute instruction here results in CPU advancing time to + // lesser of irq_time() and end_time() (or end_time() if IRQs are + // disabled) + enum { idle_addr = 0x1FFF }; + + // Can read this many bytes past end of a page + enum { cpu_padding = 8 }; + +public: + Hes_Cpu() { state = &state_; } + enum { irq_inhibit = 0x04 }; +private: + // noncopyable + Hes_Cpu( const Hes_Cpu& ); + Hes_Cpu& operator = ( const Hes_Cpu& ); + + struct state_t { + uint8_t const* code_map [page_count + 1]; + hes_time_t base; + blargg_long time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + hes_time_t irq_time_; + hes_time_t end_time_; + + void set_code_page( int, void const* ); + inline int update_end_time( hes_time_t end, hes_time_t irq ); +}; + +inline BOOST::uint8_t const* Hes_Cpu::get_code( hes_addr_t addr ) +{ + return state->code_map [addr >> page_shift] + addr + #if !BLARGG_NONPORTABLE + % (unsigned) page_size + #endif + ; +} + +inline int Hes_Cpu::update_end_time( hes_time_t t, hes_time_t irq ) +{ + if ( irq < t && !(r.status & irq_inhibit) ) t = irq; + int delta = state->base - t; + state->base = t; + return delta; +} + +inline void Hes_Cpu::set_irq_time( hes_time_t t ) +{ + state->time += update_end_time( end_time_, (irq_time_ = t) ); +} + +inline void Hes_Cpu::set_end_time( hes_time_t t ) +{ + state->time += update_end_time( (end_time_ = t), irq_time_ ); +} + +inline void Hes_Cpu::end_frame( hes_time_t t ) +{ + assert( state == &state_ ); + state_.base -= t; + if ( irq_time_ < future_hes_time ) irq_time_ -= t; + if ( end_time_ < future_hes_time ) end_time_ -= t; +} + +#endif diff --git a/game-music-emu/gme/Hes_Emu.cpp b/game-music-emu/gme/Hes_Emu.cpp new file mode 100644 index 000000000..2306e705d --- /dev/null +++ b/game-music-emu/gme/Hes_Emu.cpp @@ -0,0 +1,531 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Hes_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const timer_mask = 0x04; +int const vdp_mask = 0x02; +int const i_flag_mask = 0x04; +int const unmapped = 0xFF; + +long const period_60hz = 262 * 455L; // scanlines * clocks per scanline + +Hes_Emu::Hes_Emu() +{ + timer.raw_load = 0; + set_type( gme_hes_type ); + + static const char* const names [Hes_Apu::osc_count] = { + "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2" + }; + set_voice_names( names ); + + static int const types [Hes_Apu::osc_count] = { + wave_type | 0, wave_type | 1, wave_type | 2, wave_type | 3, + mixed_type | 0, mixed_type | 1 + }; + set_voice_types( types ); + set_silence_lookahead( 6 ); + set_gain( 1.11 ); +} + +Hes_Emu::~Hes_Emu() { } + +void Hes_Emu::unload() +{ + rom.clear(); + Music_Emu::unload(); +} + +// Track info + +static byte const* copy_field( byte const* in, char* out ) +{ + if ( in ) + { + int len = 0x20; + if ( in [0x1F] && !in [0x2F] ) + len = 0x30; // fields are sometimes 16 bytes longer (ugh) + + // since text fields are where any data could be, detect non-text + // and fields with data after zero byte terminator + + int i = 0; + for ( i = 0; i < len && in [i]; i++ ) + if ( ((in [i] + 1) & 0xFF) < ' ' + 1 ) // also treat 0xFF as non-text + return 0; // non-ASCII found + + for ( ; i < len; i++ ) + if ( in [i] ) + return 0; // data after terminator + + Gme_File::copy_field_( out, (char const*) in, len ); + in += len; + } + return in; +} + +static void copy_hes_fields( byte const* in, track_info_t* out ) +{ + if ( *in >= ' ' ) + { + in = copy_field( in, out->game ); + in = copy_field( in, out->author ); + in = copy_field( in, out->copyright ); + } +} + +blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const +{ + copy_hes_fields( rom.begin() + 0x20, out ); + return 0; +} + +static blargg_err_t check_hes_header( void const* header ) +{ + if ( memcmp( header, "HESM", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Hes_File : Gme_Info_ +{ + struct header_t { + char header [Hes_Emu::header_size]; + char unused [0x20]; + byte fields [0x30 * 3]; + } h; + + Hes_File() { set_type( gme_hes_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + assert( offsetof (header_t,fields) == Hes_Emu::header_size + 0x20 ); + blargg_err_t err = in.read( &h, sizeof h ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + return check_hes_header( &h ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_hes_fields( h.fields, out ); + return 0; + } +}; + +static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; } +static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; } + +static gme_type_t_ const gme_hes_type_ = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 }; +gme_type_t const gme_hes_type = &gme_hes_type_; + + +// Setup + +blargg_err_t Hes_Emu::load_( Data_Reader& in ) +{ + assert( offsetof (header_t,unused [4]) == header_size ); + RETURN_ERR( rom.load( in, header_size, &header_, unmapped ) ); + + RETURN_ERR( check_hes_header( header_.tag ) ); + + if ( header_.vers != 0 ) + set_warning( "Unknown file version" ); + + if ( memcmp( header_.data_tag, "DATA", 4 ) ) + set_warning( "Data header missing" ); + + if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) + set_warning( "Unknown header data" ); + + // File spec supports multiple blocks, but I haven't found any, and + // many files have bad sizes in the only block, so it's simpler to + // just try to load the damn data as best as possible. + + long addr = get_le32( header_.addr ); + long size = get_le32( header_.size ); + long const rom_max = 0x100000; + if ( addr & ~(rom_max - 1) ) + { + set_warning( "Invalid address" ); + addr &= rom_max - 1; + } + if ( (unsigned long) (addr + size) > (unsigned long) rom_max ) + set_warning( "Invalid size" ); + + if ( size != rom.file_size() ) + { + if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) + set_warning( "Multiple DATA not supported" ); + else if ( size < rom.file_size() ) + set_warning( "Extra file data" ); + else + set_warning( "Missing file data" ); + } + + rom.set_addr( addr ); + + set_voice_count( apu.osc_count ); + + apu.volume( gain() ); + + return setup_buffer( 7159091 ); +} + +void Hes_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +void Hes_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + apu.osc_output( i, center, left, right ); +} + +// Emulation + +void Hes_Emu::recalc_timer_load() +{ + timer.load = timer.raw_load * timer_base + 1; +} + +void Hes_Emu::set_tempo_( double t ) +{ + play_period = hes_time_t (period_60hz / t); + timer_base = int (1024 / t); + recalc_timer_load(); +} + +blargg_err_t Hes_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( ram, 0, sizeof ram ); // some HES music relies on zero fill + memset( sgx, 0, sizeof sgx ); + + apu.reset(); + cpu::reset(); + + for ( unsigned i = 0; i < sizeof header_.banks; i++ ) + set_mmr( i, header_.banks [i] ); + set_mmr( page_count, 0xFF ); // unmapped beyond end of address space + + irq.disables = timer_mask | vdp_mask; + irq.timer = future_hes_time; + irq.vdp = future_hes_time; + + timer.enabled = false; + timer.raw_load= 0x80; + timer.count = timer.load; + timer.fired = false; + timer.last_time = 0; + + vdp.latch = 0; + vdp.control = 0; + vdp.next_vbl = 0; + + ram [0x1FF] = (idle_addr - 1) >> 8; + ram [0x1FE] = (idle_addr - 1) & 0xFF; + r.sp = 0xFD; + r.pc = get_le16( header_.init_addr ); + r.a = track; + + recalc_timer_load(); + last_frame_hook = 0; + + return 0; +} + +// Hardware + +void Hes_Emu::cpu_write_vdp( int addr, int data ) +{ + switch ( addr ) + { + case 0: + vdp.latch = data & 0x1F; + break; + + case 2: + if ( vdp.latch == 5 ) + { + if ( data & 0x04 ) + set_warning( "Scanline interrupt unsupported" ); + run_until( time() ); + vdp.control = data; + irq_changed(); + } + else + { + dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); + } + break; + + case 3: + dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); + break; + } +} + +void Hes_Emu::cpu_write_( hes_addr_t addr, int data ) +{ + if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr ) + { + GME_APU_HOOK( this, addr - apu.start_addr, data ); + // avoid going way past end when a long block xfer is writing to I/O space + hes_time_t t = min( time(), end_time() + 8 ); + apu.write_data( t, addr, data ); + return; + } + + hes_time_t time = this->time(); + switch ( addr ) + { + case 0x0000: + case 0x0002: + case 0x0003: + cpu_write_vdp( addr, data ); + return; + + case 0x0C00: { + run_until( time ); + timer.raw_load = (data & 0x7F) + 1; + recalc_timer_load(); + timer.count = timer.load; + break; + } + + case 0x0C01: + data &= 1; + if ( timer.enabled == data ) + return; + run_until( time ); + timer.enabled = data; + if ( data ) + timer.count = timer.load; + break; + + case 0x1402: + run_until( time ); + irq.disables = data; + if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values + dprintf( "Int mask: $%02X\n", data ); + break; + + case 0x1403: + run_until( time ); + if ( timer.enabled ) + timer.count = timer.load; + timer.fired = false; + break; + +#ifndef NDEBUG + case 0x1000: // I/O port + case 0x0402: // palette + case 0x0403: + case 0x0404: + case 0x0405: + return; + + default: + dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); + return; +#endif + } + + irq_changed(); +} + +int Hes_Emu::cpu_read_( hes_addr_t addr ) +{ + hes_time_t time = this->time(); + addr &= page_size - 1; + switch ( addr ) + { + case 0x0000: + if ( irq.vdp > time ) + return 0; + irq.vdp = future_hes_time; + run_until( time ); + irq_changed(); + return 0x20; + + case 0x0002: + case 0x0003: + dprintf( "VDP read not supported: %d\n", addr ); + return 0; + + case 0x0C01: + //return timer.enabled; // TODO: remove? + case 0x0C00: + run_until( time ); + dprintf( "Timer count read\n" ); + return (unsigned) (timer.count - 1) / timer_base; + + case 0x1402: + return irq.disables; + + case 0x1403: + { + int status = 0; + if ( irq.timer <= time ) status |= timer_mask; + if ( irq.vdp <= time ) status |= vdp_mask; + return status; + } + + #ifndef NDEBUG + case 0x1000: // I/O port + case 0x180C: // CD-ROM + case 0x180D: + break; + + default: + dprintf( "unmapped read $%04X\n", addr ); + #endif + } + + return unmapped; +} + +// see hes_cpu_io.h for core read/write functions + +// Emulation + +void Hes_Emu::run_until( hes_time_t present ) +{ + while ( vdp.next_vbl < present ) + vdp.next_vbl += play_period; + + hes_time_t elapsed = present - timer.last_time; + if ( elapsed > 0 ) + { + if ( timer.enabled ) + { + timer.count -= elapsed; + if ( timer.count <= 0 ) + timer.count += timer.load; + } + timer.last_time = present; + } +} + +void Hes_Emu::irq_changed() +{ + hes_time_t present = time(); + + if ( irq.timer > present ) + { + irq.timer = future_hes_time; + if ( timer.enabled && !timer.fired ) + irq.timer = present + timer.count; + } + + if ( irq.vdp > present ) + { + irq.vdp = future_hes_time; + if ( vdp.control & 0x08 ) + irq.vdp = vdp.next_vbl; + } + + hes_time_t time = future_hes_time; + if ( !(irq.disables & timer_mask) ) time = irq.timer; + if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp ); + + set_irq_time( time ); +} + +int Hes_Emu::cpu_done() +{ + check( time() >= end_time() || + (!(r.status & i_flag_mask) && time() >= irq_time()) ); + + if ( !(r.status & i_flag_mask) ) + { + hes_time_t present = time(); + + if ( irq.timer <= present && !(irq.disables & timer_mask) ) + { + timer.fired = true; + irq.timer = future_hes_time; + irq_changed(); // overkill, but not worth writing custom code + #if GME_FRAME_HOOK_DEFINED + { + unsigned const threshold = period_60hz / 30; + unsigned long elapsed = present - last_frame_hook; + if ( elapsed - period_60hz + threshold / 2 < threshold ) + { + last_frame_hook = present; + GME_FRAME_HOOK( this ); + } + } + #endif + return 0x0A; + } + + if ( irq.vdp <= present && !(irq.disables & vdp_mask) ) + { + // work around for bugs with music not acknowledging VDP + //run_until( present ); + //irq.vdp = future_hes_time; + //irq_changed(); + #if GME_FRAME_HOOK_DEFINED + last_frame_hook = present; + GME_FRAME_HOOK( this ); + #endif + return 0x08; + } + } + return 0; +} + +static void adjust_time( blargg_long& time, hes_time_t delta ) +{ + if ( time < future_hes_time ) + { + time -= delta; + if ( time < 0 ) + time = 0; + } +} + +blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int ) +{ + blip_time_t const duration = duration_; // cache + + if ( cpu::run( duration ) ) + set_warning( "Emulation error (illegal instruction)" ); + + check( time() >= duration ); + //check( time() - duration < 20 ); // Txx instruction could cause going way over + + run_until( duration ); + + // end time frame + timer.last_time -= duration; + vdp.next_vbl -= duration; + #if GME_FRAME_HOOK_DEFINED + last_frame_hook -= duration; + #endif + cpu::end_frame( duration ); + ::adjust_time( irq.timer, duration ); + ::adjust_time( irq.vdp, duration ); + apu.end_frame( duration ); + + return 0; +} diff --git a/game-music-emu/gme/Hes_Emu.h b/game-music-emu/gme/Hes_Emu.h new file mode 100644 index 000000000..9951eb6a2 --- /dev/null +++ b/game-music-emu/gme/Hes_Emu.h @@ -0,0 +1,94 @@ +// TurboGrafx-16/PC Engine HES music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef HES_EMU_H +#define HES_EMU_H + +#include "Classic_Emu.h" +#include "Hes_Apu.h" +#include "Hes_Cpu.h" + +class Hes_Emu : private Hes_Cpu, public Classic_Emu { + typedef Hes_Cpu cpu; +public: + // HES file header + enum { header_size = 0x20 }; + struct header_t + { + byte tag [4]; + byte vers; + byte first_track; + byte init_addr [2]; + byte banks [8]; + byte data_tag [4]; + byte size [4]; + byte addr [4]; + byte unused [4]; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_hes_type; } + +public: + Hes_Emu(); + ~Hes_Emu(); +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + void unload(); +public: private: friend class Hes_Cpu; + byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space + + int cpu_read_( hes_addr_t ); + int cpu_read( hes_addr_t ); + void cpu_write_( hes_addr_t, int data ); + void cpu_write( hes_addr_t, int ); + void cpu_write_vdp( int addr, int data ); + byte const* cpu_set_mmr( int page, int bank ); + int cpu_done(); +private: + Rom_Data rom; + header_t header_; + hes_time_t play_period; + hes_time_t last_frame_hook; + int timer_base; + + struct { + hes_time_t last_time; + blargg_long count; + blargg_long load; + int raw_load; + byte enabled; + byte fired; + } timer; + + struct { + hes_time_t next_vbl; + byte latch; + byte control; + } vdp; + + struct { + hes_time_t timer; + hes_time_t vdp; + byte disables; + } irq; + + void recalc_timer_load(); + + // large items + Hes_Apu apu; + byte sgx [3 * page_size + cpu_padding]; + + void irq_changed(); + void run_until( hes_time_t ); +}; + +#endif diff --git a/game-music-emu/gme/Kss_Cpu.cpp b/game-music-emu/gme/Kss_Cpu.cpp new file mode 100644 index 000000000..c943a01fe --- /dev/null +++ b/game-music-emu/gme/Kss_Cpu.cpp @@ -0,0 +1,1706 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +/* +Last validated with zexall 2006.11.14 2:19 PM +* Doesn't implement the R register or immediate interrupt after EI. +* Address wrap-around isn't completely correct, but is prevented from crashing emulator. +*/ + +#include "Kss_Cpu.h" + +#include "blargg_endian.h" +#include + +//#include "z80_cpu_log.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define SYNC_TIME() (void) (s.time = s_time) +#define RELOAD_TIME() (void) (s_time = s.time) + +// Callbacks to emulator + +#define CPU_OUT( cpu, addr, data, time )\ + kss_cpu_out( this, time, addr, data ) + +#define CPU_IN( cpu, addr, time )\ + kss_cpu_in( this, time, addr ) + +#define CPU_WRITE( cpu, addr, data, time )\ + (SYNC_TIME(), kss_cpu_write( this, addr, data )) + +#include "blargg_source.h" + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +#define SZ28P( n ) szpc [n] +#define SZ28PC( n ) szpc [n] +#define SZ28C( n ) (szpc [n] & ~P04) +#define SZ28( n ) SZ28C( n ) + +#define SET_R( n ) (void) (r.r = n) +#define GET_R() (r.r) + +Kss_Cpu::Kss_Cpu() +{ + state = &state_; + + for ( int i = 0x100; --i >= 0; ) + { + int even = 1; + for ( int p = i; p; p >>= 1 ) + even ^= p; + int n = (i & (S80 | F20 | F08)) | ((even & 1) * P04); + szpc [i] = n; + szpc [i + 0x100] = n | C01; + } + szpc [0x000] |= Z40; + szpc [0x100] |= Z40; +} + +inline void Kss_Cpu::set_page( int i, void* write, void const* read ) +{ + blargg_long offset = KSS_CPU_PAGE_OFFSET( i * (blargg_long) page_size ); + state->write [i] = (byte *) write - offset; + state->read [i] = (byte const*) read - offset; +} + +void Kss_Cpu::reset( void* unmapped_write, void const* unmapped_read ) +{ + check( state == &state_ ); + state = &state_; + state_.time = 0; + state_.base = 0; + end_time_ = 0; + + for ( int i = 0; i < page_count + 1; i++ ) + set_page( i, unmapped_write, unmapped_read ); + + memset( &r, 0, sizeof r ); +} + +void Kss_Cpu::map_mem( unsigned addr, blargg_ulong size, void* write, void const* read ) +{ + // address range must begin and end on page boundaries + require( addr % page_size == 0 ); + require( size % page_size == 0 ); + + unsigned first_page = addr / page_size; + for ( unsigned i = size / page_size; i--; ) + { + blargg_long offset = i * (blargg_long) page_size; + set_page( first_page + i, (byte*) write + offset, (byte const*) read + offset ); + } +} + +#define TIME (s_time + s.base) +#define RW_MEM( addr, rw ) (s.rw [(addr) >> page_shift] [KSS_CPU_PAGE_OFFSET( addr )]) +#define READ_PROG( addr ) RW_MEM( addr, read ) +#define READ( addr ) READ_PROG( addr ) +//#define WRITE( addr, data ) (void) (RW_MEM( addr, write ) = data) +#define WRITE( addr, data ) CPU_WRITE( this, addr, data, TIME ) +#define READ_WORD( addr ) GET_LE16( &READ( addr ) ) +#define WRITE_WORD( addr, data ) SET_LE16( &RW_MEM( addr, write ), data ) +#define IN( addr ) CPU_IN( this, addr, TIME ) +#define OUT( addr, data ) CPU_OUT( this, addr, data, TIME ) + +#if BLARGG_BIG_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n, offset ) ((r8_ - offset) [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + +//#define R16( n, shift, offset ) (r16_ [((n) >> shift) - (offset >> shift)]) + +// help compiler see that it can just adjust stack offset, saving an extra instruction +#define R16( n, shift, offset )\ + (*(uint16_t*) ((char*) r16_ - (offset >> (shift - 1)) + ((n) >> (shift - 1)))) + +#define CASE5( a, b, c, d, e ) case 0x##a:case 0x##b:case 0x##c:case 0x##d:case 0x##e +#define CASE6( a, b, c, d, e, f ) CASE5( a, b, c, d, e ): case 0x##f +#define CASE7( a, b, c, d, e, f, g ) CASE6( a, b, c, d, e, f ): case 0x##g +#define CASE8( a, b, c, d, e, f, g, h ) CASE7( a, b, c, d, e, f, g ): case 0x##h + +// high four bits are $ED time - 8, low four bits are $DD/$FD time - 8 +static byte const ed_dd_timing [0x100] = { +//0 1 2 3 4 5 6 7 8 9 A B C D E F +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x0C,0x02,0x00,0x00,0x03,0x00,0x00,0x07,0x0C,0x02,0x00,0x00,0x03,0x00, +0x00,0x00,0x00,0x00,0x0F,0x0F,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, +0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0, +0x4B,0x4B,0x7B,0xCB,0x0B,0x6B,0x00,0x0B,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, +0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00, +0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00,0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x00,0x0F,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, +}; + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; + +bool Kss_Cpu::run( cpu_time_t end_time ) +{ + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + bool warning = false; + + typedef BOOST::int8_t int8_t; + + union { + regs_t rg; + pairs_t rp; + uint8_t r8_ [8]; // indexed + uint16_t r16_ [4]; + }; + rg = this->r.b; + + cpu_time_t s_time = s.time; + fuint16 pc = r.pc; + fuint16 sp = r.sp; + fuint16 ix = r.ix; // TODO: keep in memory for direct access? + fuint16 iy = r.iy; + int flags = r.b.flags; + + goto loop; +jr_not_taken: + s_time -= 5; + goto loop; +call_not_taken: + s_time -= 7; +jp_not_taken: + pc += 2; +loop: + + check( (unsigned long) pc < 0x10000 ); + check( (unsigned long) sp < 0x10000 ); + check( (unsigned) flags < 0x100 ); + check( (unsigned) ix < 0x10000 ); + check( (unsigned) iy < 0x10000 ); + + uint8_t const* instr = s.read [pc >> page_shift]; +#define GET_ADDR() GET_LE16( instr ) + + fuint8 opcode; + + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + opcode = instr [pc]; + pc++; + instr += pc; + #else + instr += KSS_CPU_PAGE_OFFSET( pc ); + opcode = *instr++; + pc++; + #endif + + static byte const base_timing [0x100] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,10, 7, 6, 4, 4, 7, 4, 4,11, 7, 6, 4, 4, 7, 4, // 0 + 13,10, 7, 6, 4, 4, 7, 4,12,11, 7, 6, 4, 4, 7, 4, // 1 + 12,10,16, 6, 4, 4, 7, 4,12,11,16, 6, 4, 4, 7, 4, // 2 + 12,10,13, 6,11,11,10, 4,12,11,13, 6, 4, 4, 7, 4, // 3 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 4 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 5 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 6 + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, // 7 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 8 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 9 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // A + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // B + 11,10,10,10,17,11, 7,11,11,10,10, 8,17,17, 7,11, // C + 11,10,10,11,17,11, 7,11,11, 4,10,11,17, 8, 7,11, // D + 11,10,10,19,17,11, 7,11,11, 4,10, 4,17, 8, 7,11, // E + 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F + }; + + fuint16 data; + data = base_timing [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = READ_PROG( pc ); + + #ifdef Z80_CPU_LOG_H + //log_opcode( opcode, READ_PROG( pc ) ); + z80_log_regs( rg.a, rp.bc, rp.de, rp.hl, sp, ix, iy ); + z80_cpu_log( "new", pc - 1, opcode, READ_PROG( pc ), + READ_PROG( pc + 1 ), READ_PROG( pc + 2 ) ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Common + + case 0x00: // NOP + CASE7( 40, 49, 52, 5B, 64, 6D, 7F ): // LD B,B etc. + goto loop; + + case 0x08:{// EX AF,AF' + int temp = r.alt.b.a; + r.alt.b.a = rg.a; + rg.a = temp; + + temp = r.alt.b.flags; + r.alt.b.flags = flags; + flags = temp; + goto loop; + } + + case 0xD3: // OUT (imm),A + pc++; + OUT( data + rg.a * 0x100, rg.a ); + goto loop; + + case 0x2E: // LD L,imm + pc++; + rg.l = data; + goto loop; + + case 0x3E: // LD A,imm + pc++; + rg.a = data; + goto loop; + + case 0x3A:{// LD A,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rg.a = READ( addr ); + goto loop; + } + +// Conditional + +#define ZERO (flags & Z40) +#define CARRY (flags & C01) +#define EVEN (flags & P04) +#define MINUS (flags & S80) + +// JR +// TODO: more efficient way to handle negative branch that wraps PC around +#define JR( cond ) {\ + int offset = (BOOST::int8_t) data;\ + pc++;\ + if ( !(cond) )\ + goto jr_not_taken;\ + pc = uint16_t (pc + offset);\ + goto loop;\ +} + + case 0x20: JR( !ZERO ) // JR NZ,disp + case 0x28: JR( ZERO ) // JR Z,disp + case 0x30: JR( !CARRY ) // JR NC,disp + case 0x38: JR( CARRY ) // JR C,disp + case 0x18: JR( true ) // JR disp + + case 0x10:{// DJNZ disp + int temp = rg.b - 1; + rg.b = temp; + JR( temp ) + } + +// JP +#define JP( cond ) if ( !(cond) ) goto jp_not_taken; pc = GET_ADDR(); goto loop; + + case 0xC2: JP( !ZERO ) // JP NZ,addr + case 0xCA: JP( ZERO ) // JP Z,addr + case 0xD2: JP( !CARRY ) // JP NC,addr + case 0xDA: JP( CARRY ) // JP C,addr + case 0xE2: JP( !EVEN ) // JP PO,addr + case 0xEA: JP( EVEN ) // JP PE,addr + case 0xF2: JP( !MINUS ) // JP P,addr + case 0xFA: JP( MINUS ) // JP M,addr + + case 0xC3: // JP addr + pc = GET_ADDR(); + goto loop; + + case 0xE9: // JP HL + pc = rp.hl; + goto loop; + +// RET +#define RET( cond ) if ( cond ) goto ret_taken; s_time -= 6; goto loop; + + case 0xC0: RET( !ZERO ) // RET NZ + case 0xC8: RET( ZERO ) // RET Z + case 0xD0: RET( !CARRY ) // RET NC + case 0xD8: RET( CARRY ) // RET C + case 0xE0: RET( !EVEN ) // RET PO + case 0xE8: RET( EVEN ) // RET PE + case 0xF0: RET( !MINUS ) // RET P + case 0xF8: RET( MINUS ) // RET M + + case 0xC9: // RET + ret_taken: + pc = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// CALL +#define CALL( cond ) if ( cond ) goto call_taken; goto call_not_taken; + + case 0xC4: CALL( !ZERO ) // CALL NZ,addr + case 0xCC: CALL( ZERO ) // CALL Z,addr + case 0xD4: CALL( !CARRY ) // CALL NC,addr + case 0xDC: CALL( CARRY ) // CALL C,addr + case 0xE4: CALL( !EVEN ) // CALL PO,addr + case 0xEC: CALL( EVEN ) // CALL PE,addr + case 0xF4: CALL( !MINUS ) // CALL P,addr + case 0xFC: CALL( MINUS ) // CALL M,addr + + case 0xCD:{// CALL addr + call_taken: + fuint16 addr = pc + 2; + pc = GET_ADDR(); + sp = uint16_t (sp - 2); + WRITE_WORD( sp, addr ); + goto loop; + } + + case 0xFF: // RST + if ( pc > idle_addr ) + goto hit_idle_addr; + CASE7( C7, CF, D7, DF, E7, EF, F7 ): + data = pc; + pc = opcode & 0x38; + goto push_data; + +// PUSH/POP + case 0xF5: // PUSH AF + data = rg.a * 0x100u + flags; + goto push_data; + + case 0xC5: // PUSH BC + case 0xD5: // PUSH DE + case 0xE5: // PUSH HL + data = R16( opcode, 4, 0xC5 ); + push_data: + sp = uint16_t (sp - 2); + WRITE_WORD( sp, data ); + goto loop; + + case 0xF1: // POP AF + flags = READ( sp ); + rg.a = READ( sp + 1 ); + sp = uint16_t (sp + 2); + goto loop; + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL + R16( opcode, 4, 0xC1 ) = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto loop; + +// ADC/ADD/SBC/SUB + case 0x96: // SUB (HL) + case 0x86: // ADD (HL) + flags &= ~C01; + case 0x9E: // SBC (HL) + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_data; + + case 0xD6: // SUB A,imm + case 0xC6: // ADD imm + flags &= ~C01; + case 0xDE: // SBC A,imm + case 0xCE: // ADC imm + pc++; + goto adc_data; + + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // SUB r + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // ADD r + flags &= ~C01; + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // SBC r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // ADC r + data = R8( opcode & 7, 0 ); + adc_data: { + int result = data + (flags & C01); + data ^= rg.a; + flags = opcode >> 3 & N02; // bit 4 is set in subtract opcodes + if ( flags ) + result = -result; + result += rg.a; + data ^= result; + flags |=(data & H10) | + ((data - -0x80) >> 6 & V04) | + SZ28C( result & 0x1FF ); + rg.a = result; + goto loop; + } + +// CP + case 0xBE: // CP (HL) + data = READ( rp.hl ); + goto cp_data; + + case 0xFE: // CP imm + pc++; + goto cp_data; + + CASE7( B8, B9, BA, BB, BC, BD, BF ): // CP r + data = R8( opcode, 0xB8 ); + cp_data: { + int result = rg.a - data; + flags = N02 | (data & (F20 | F08)) | (result >> 8 & C01); + data ^= rg.a; + flags |=(((result ^ rg.a) & data) >> 5 & V04) | + (((data & H10) ^ result) & (S80 | H10)); + if ( (uint8_t) result ) + goto loop; + flags |= Z40; + goto loop; + } + +// ADD HL,rp + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_data; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( opcode, 4, 0x09 ); + add_hl_data: { + blargg_ulong sum = rp.hl + data; + data ^= rp.hl; + rp.hl = sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((data ^ sum) >> 8 & H10); + goto loop; + } + + case 0x27:{// DAA + int a = rg.a; + if ( a > 0x99 ) + flags |= C01; + + int adjust = 0x60 & -(flags & C01); + + if ( flags & H10 || (a & 0x0F) > 9 ) + adjust |= 0x06; + + if ( flags & N02 ) + adjust = -adjust; + a += adjust; + + flags = (flags & (C01 | N02)) | + ((rg.a ^ a) & H10) | + SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + /* + case 0x27:{// DAA + // more optimized, but probably not worth the obscurity + int f = (rg.a + (0xFF - 0x99)) >> 8 | flags; // (a > 0x99 ? C01 : 0) | flags + int adjust = 0x60 & -(f & C01); // f & C01 ? 0x60 : 0 + + if ( (((rg.a + (0x0F - 9)) ^ rg.a) | f) & H10 ) // flags & H10 || (rg.a & 0x0F) > 9 + adjust |= 0x06; + + if ( f & N02 ) + adjust = -adjust; + int a = rg.a + adjust; + + flags = (f & (N02 | C01)) | ((rg.a ^ a) & H10) | SZ28P( (uint8_t) a ); + rg.a = a; + goto loop; + } + */ + +// INC/DEC + case 0x34: // INC (HL) + data = READ( rp.hl ) + 1; + WRITE( rp.hl, data ); + goto inc_set_flags; + + CASE7( 04, 0C, 14, 1C, 24, 2C, 3C ): // INC r + data = ++R8( opcode >> 3, 0 ); + inc_set_flags: + flags = (flags & C01) | + (((data & 0x0F) - 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x80 ) + goto loop; + flags |= V04; + goto loop; + + case 0x35: // DEC (HL) + data = READ( rp.hl ) - 1; + WRITE( rp.hl, data ); + goto dec_set_flags; + + CASE7( 05, 0D, 15, 1D, 25, 2D, 3D ): // DEC r + data = --R8( opcode >> 3, 0 ); + dec_set_flags: + flags = (flags & C01) | N02 | + (((data & 0x0F) + 1) & H10) | + SZ28( (uint8_t) data ); + if ( data != 0x7F ) + goto loop; + flags |= V04; + goto loop; + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( opcode, 4, 0x03 )++; + goto loop; + + case 0x33: // INC SP + sp = uint16_t (sp + 1); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( opcode, 4, 0x0B )--; + goto loop; + + case 0x3B: // DEC SP + sp = uint16_t (sp - 1); + goto loop; + +// AND + case 0xA6: // AND (HL) + data = READ( rp.hl ); + goto and_data; + + case 0xE6: // AND imm + pc++; + goto and_data; + + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // AND r + data = R8( opcode, 0xA0 ); + and_data: + rg.a &= data; + flags = SZ28P( rg.a ) | H10; + goto loop; + +// OR + case 0xB6: // OR (HL) + data = READ( rp.hl ); + goto or_data; + + case 0xF6: // OR imm + pc++; + goto or_data; + + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // OR r + data = R8( opcode, 0xB0 ); + or_data: + rg.a |= data; + flags = SZ28P( rg.a ); + goto loop; + +// XOR + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + goto xor_data; + + case 0xEE: // XOR imm + pc++; + goto xor_data; + + CASE7( A8, A9, AA, AB, AC, AD, AF ): // XOR r + data = R8( opcode, 0xA8 ); + xor_data: + rg.a ^= data; + flags = SZ28P( rg.a ); + goto loop; + +// LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (HL),r + WRITE( rp.hl, R8( opcode, 0x70 ) ); + goto loop; + + CASE6( 41, 42, 43, 44, 45, 47 ): // LD B,r + CASE6( 48, 4A, 4B, 4C, 4D, 4F ): // LD C,r + CASE6( 50, 51, 53, 54, 55, 57 ): // LD D,r + CASE6( 58, 59, 5A, 5C, 5D, 5F ): // LD E,r + CASE6( 60, 61, 62, 63, 65, 67 ): // LD H,r + CASE6( 68, 69, 6A, 6B, 6C, 6F ): // LD L,r + CASE6( 78, 79, 7A, 7B, 7C, 7D ): // LD A,r + R8( opcode >> 3 & 7, 0 ) = R8( opcode & 7, 0 ); + goto loop; + + CASE5( 06, 0E, 16, 1E, 26 ): // LD r,imm + R8( opcode >> 3, 0 ) = data; + pc++; + goto loop; + + case 0x36: // LD (HL),imm + pc++; + WRITE( rp.hl, data ); + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(HL) + R8( opcode >> 3, 8 ) = READ( rp.hl ); + goto loop; + + case 0x01: // LD rp,imm + case 0x11: + case 0x21: + R16( opcode, 4, 0x01 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0x31: // LD sp,imm + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x2A:{// LD HL,(addr) + fuint16 addr = GET_ADDR(); + pc += 2; + rp.hl = READ_WORD( addr ); + goto loop; + } + + case 0x32:{// LD (addr),A + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE( addr, rg.a ); + goto loop; + } + + case 0x22:{// LD (addr),HL + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, rp.hl ); + goto loop; + } + + case 0x02: // LD (BC),A + case 0x12: // LD (DE),A + WRITE( R16( opcode, 4, 0x02 ), rg.a ); + goto loop; + + case 0x0A: // LD A,(BC) + case 0x1A: // LD A,(DE) + rg.a = READ( R16( opcode, 4, 0x0A ) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + +// Rotate + + case 0x07:{// RLCA + fuint16 temp = rg.a; + temp = (temp << 1) | (temp >> 7); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08 | C01)); + rg.a = temp; + goto loop; + } + + case 0x0F:{// RRCA + fuint16 temp = rg.a; + flags = (flags & (S80 | Z40 | P04)) | + (temp & C01); + temp = (temp << 7) | (temp >> 1); + flags |= temp & (F20 | F08); + rg.a = temp; + goto loop; + } + + case 0x17:{// RLA + blargg_ulong temp = (rg.a << 1) | (flags & C01); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (temp >> 8); + rg.a = temp; + goto loop; + } + + case 0x1F:{// RRA + fuint16 temp = (flags << 7) | (rg.a >> 1); + flags = (flags & (S80 | Z40 | P04)) | + (temp & (F20 | F08)) | + (rg.a & C01); + rg.a = temp; + goto loop; + } + +// Misc + case 0x2F:{// CPL + fuint16 temp = ~rg.a; + flags = (flags & (S80 | Z40 | P04 | C01)) | + (temp & (F20 | F08)) | + (H10 | N02); + rg.a = temp; + goto loop; + } + + case 0x3F:{// CCF + flags = ((flags & (S80 | Z40 | P04 | C01)) ^ C01) | + (flags << 4 & H10) | + (rg.a & (F20 | F08)); + goto loop; + } + + case 0x37: // SCF + flags = (flags & (S80 | Z40 | P04)) | C01 | + (rg.a & (F20 | F08)); + goto loop; + + case 0xDB: // IN A,(imm) + pc++; + rg.a = IN( data + rg.a * 0x100 ); + goto loop; + + case 0xE3:{// EX (SP),HL + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, rp.hl ); + rp.hl = temp; + goto loop; + } + + case 0xEB:{// EX DE,HL + fuint16 temp = rp.hl; + rp.hl = rp.de; + rp.de = temp; + goto loop; + } + + case 0xD9:{// EXX DE,HL + fuint16 temp = r.alt.w.bc; + r.alt.w.bc = rp.bc; + rp.bc = temp; + + temp = r.alt.w.de; + r.alt.w.de = rp.de; + rp.de = temp; + + temp = r.alt.w.hl; + r.alt.w.hl = rp.hl; + rp.hl = temp; + goto loop; + } + + case 0xF3: // DI + r.iff1 = 0; + r.iff2 = 0; + goto loop; + + case 0xFB: // EI + r.iff1 = 1; + r.iff2 = 1; + // TODO: delayed effect + goto loop; + + case 0x76: // HALT + goto halt; + +//////////////////////////////////////// CB prefix + { + case 0xCB: + unsigned data2; + data2 = instr [1]; + pc++; + switch ( data ) + { + + // Rotate left + + #define RLC( read, write ) {\ + fuint8 result = read;\ + result = uint8_t (result << 1) | (result >> 7);\ + flags = SZ28P( result ) | (result & C01);\ + write;\ + goto loop;\ + } + + case 0x06: // RLC (HL) + s_time += 7; + data = rp.hl; + rlc_data_addr: + RLC( READ( data ), WRITE( data, result ) ) + + CASE7( 00, 01, 02, 03, 04, 05, 07 ):{// RLC r + uint8_t& reg = R8( data, 0 ); + RLC( reg, reg = result ) + } + + #define RL( read, write ) {\ + fuint16 result = (read << 1) | (flags & C01);\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x16: // RL (HL) + s_time += 7; + data = rp.hl; + rl_data_addr: + RL( READ( data ), WRITE( data, result ) ) + + CASE7( 10, 11, 12, 13, 14, 15, 17 ):{// RL r + uint8_t& reg = R8( data, 0x10 ); + RL( reg, reg = result ) + } + + #define SLA( read, add, write ) {\ + fuint16 result = (read << 1) | add;\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x26: // SLA (HL) + s_time += 7; + data = rp.hl; + sla_data_addr: + SLA( READ( data ), 0, WRITE( data, result ) ) + + CASE7( 20, 21, 22, 23, 24, 25, 27 ):{// SLA r + uint8_t& reg = R8( data, 0x20 ); + SLA( reg, 0, reg = result ) + } + + case 0x36: // SLL (HL) + s_time += 7; + data = rp.hl; + sll_data_addr: + SLA( READ( data ), 1, WRITE( data, result ) ) + + CASE7( 30, 31, 32, 33, 34, 35, 37 ):{// SLL r + uint8_t& reg = R8( data, 0x30 ); + SLA( reg, 1, reg = result ) + } + + // Rotate right + + #define RRC( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = uint8_t (result << 7) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x0E: // RRC (HL) + s_time += 7; + data = rp.hl; + rrc_data_addr: + RRC( READ( data ), WRITE( data, result ) ) + + CASE7( 08, 09, 0A, 0B, 0C, 0D, 0F ):{// RRC r + uint8_t& reg = R8( data, 0x08 ); + RRC( reg, reg = result ) + } + + #define RR( read, write ) {\ + fuint8 result = read;\ + fuint8 temp = result & C01;\ + result = uint8_t (flags << 7) | (result >> 1);\ + flags = SZ28P( result ) | temp;\ + write;\ + goto loop;\ + } + + case 0x1E: // RR (HL) + s_time += 7; + data = rp.hl; + rr_data_addr: + RR( READ( data ), WRITE( data, result ) ) + + CASE7( 18, 19, 1A, 1B, 1C, 1D, 1F ):{// RR r + uint8_t& reg = R8( data, 0x18 ); + RR( reg, reg = result ) + } + + #define SRA( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result = (result & 0x80) | (result >> 1);\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x2E: // SRA (HL) + data = rp.hl; + s_time += 7; + sra_data_addr: + SRA( READ( data ), WRITE( data, result ) ) + + CASE7( 28, 29, 2A, 2B, 2C, 2D, 2F ):{// SRA r + uint8_t& reg = R8( data, 0x28 ); + SRA( reg, reg = result ) + } + + #define SRL( read, write ) {\ + fuint8 result = read;\ + flags = result & C01;\ + result >>= 1;\ + flags |= SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x3E: // SRL (HL) + s_time += 7; + data = rp.hl; + srl_data_addr: + SRL( READ( data ), WRITE( data, result ) ) + + CASE7( 38, 39, 3A, 3B, 3C, 3D, 3F ):{// SRL r + uint8_t& reg = R8( data, 0x38 ); + SRL( reg, reg = result ) + } + + // BIT + { + unsigned temp; + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ): // BIT b,(HL) + s_time += 4; + temp = READ( rp.hl ); + flags &= C01; + goto bit_temp; + CASE7( 40, 41, 42, 43, 44, 45, 47 ): // BIT 0,r + CASE7( 48, 49, 4A, 4B, 4C, 4D, 4F ): // BIT 1,r + CASE7( 50, 51, 52, 53, 54, 55, 57 ): // BIT 2,r + CASE7( 58, 59, 5A, 5B, 5C, 5D, 5F ): // BIT 3,r + CASE7( 60, 61, 62, 63, 64, 65, 67 ): // BIT 4,r + CASE7( 68, 69, 6A, 6B, 6C, 6D, 6F ): // BIT 5,r + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // BIT 6,r + CASE7( 78, 79, 7A, 7B, 7C, 7D, 7F ): // BIT 7,r + temp = R8( data & 7, 0 ); + flags = (flags & C01) | (temp & (F20 | F08)); + bit_temp: + int masked = temp & 1 << (data >> 3 & 7); + flags |=(masked & S80) | H10 | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + // SET/RES + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(HL) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(HL) + s_time += 7; + int temp = READ( rp.hl ); + int bit = 1 << (data >> 3 & 7); + temp |= bit; // SET + if ( !(data & 0x40) ) + temp ^= bit; // RES + WRITE( rp.hl, temp ); + goto loop; + } + + CASE7( C0, C1, C2, C3, C4, C5, C7 ): // SET 0,r + CASE7( C8, C9, CA, CB, CC, CD, CF ): // SET 1,r + CASE7( D0, D1, D2, D3, D4, D5, D7 ): // SET 2,r + CASE7( D8, D9, DA, DB, DC, DD, DF ): // SET 3,r + CASE7( E0, E1, E2, E3, E4, E5, E7 ): // SET 4,r + CASE7( E8, E9, EA, EB, EC, ED, EF ): // SET 5,r + CASE7( F0, F1, F2, F3, F4, F5, F7 ): // SET 6,r + CASE7( F8, F9, FA, FB, FC, FD, FF ): // SET 7,r + R8( data & 7, 0 ) |= 1 << (data >> 3 & 7); + goto loop; + + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // RES 0,r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // RES 1,r + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // RES 2,r + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // RES 3,r + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // RES 4,r + CASE7( A8, A9, AA, AB, AC, AD, AF ): // RES 5,r + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // RES 6,r + CASE7( B8, B9, BA, BB, BC, BD, BF ): // RES 7,r + R8( data & 7, 0 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + } + assert( false ); + } + +#undef GET_ADDR +#define GET_ADDR() GET_LE16( instr + 1 ) + +//////////////////////////////////////// ED prefix + { + case 0xED: + pc++; + s_time += ed_dd_timing [data] >> 4; + switch ( data ) + { + { + blargg_ulong temp; + case 0x72: // SBC HL,SP + case 0x7A: // ADC HL,SP + temp = sp; + if ( 0 ) + case 0x42: // SBC HL,BC + case 0x52: // SBC HL,DE + case 0x62: // SBC HL,HL + case 0x4A: // ADC HL,BC + case 0x5A: // ADC HL,DE + case 0x6A: // ADC HL,HL + temp = R16( data >> 3 & 6, 1, 0 ); + blargg_ulong sum = temp + (flags & C01); + flags = ~data >> 2 & N02; + if ( flags ) + sum = (blargg_ulong)-(blargg_long)sum; + sum += rp.hl; + temp ^= rp.hl; + temp ^= sum; + flags |=(sum >> 16 & C01) | + (temp >> 8 & H10) | + (sum >> 8 & (S80 | F20 | F08)) | + ((temp - -0x8000) >> 14 & V04); + rp.hl = sum; + if ( (uint16_t) sum ) + goto loop; + flags |= Z40; + goto loop; + } + + CASE8( 40, 48, 50, 58, 60, 68, 70, 78 ):{// IN r,(C) + int temp = IN( rp.bc ); + R8( data >> 3, 8 ) = temp; + flags = (flags & C01) | SZ28P( temp ); + goto loop; + } + + case 0x71: // OUT (C),0 + rg.flags = 0; + CASE7( 41, 49, 51, 59, 61, 69, 79 ): // OUT (C),r + OUT( rp.bc, R8( data >> 3, 8 ) ); + goto loop; + + { + unsigned temp; + case 0x73: // LD (ADDR),SP + temp = sp; + if ( 0 ) + case 0x43: // LD (ADDR),BC + case 0x53: // LD (ADDR),DE + temp = R16( data, 4, 0x43 ); + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, temp ); + goto loop; + } + + case 0x4B: // LD BC,(ADDR) + case 0x5B:{// LD DE,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + R16( data, 4, 0x4B ) = READ_WORD( addr ); + goto loop; + } + + case 0x7B:{// LD SP,(ADDR) + fuint16 addr = GET_ADDR(); + pc += 2; + sp = READ_WORD( addr ); + goto loop; + } + + case 0x67:{// RRD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (rg.a << 4) | (temp >> 4) ); + temp = (rg.a & 0xF0) | (temp & 0x0F); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + case 0x6F:{// RLD + fuint8 temp = READ( rp.hl ); + WRITE( rp.hl, (temp << 4) | (rg.a & 0x0F) ); + temp = (rg.a & 0xF0) | (temp >> 4); + flags = (flags & C01) | SZ28P( temp ); + rg.a = temp; + goto loop; + } + + CASE8( 44, 4C, 54, 5C, 64, 6C, 74, 7C ): // NEG + opcode = 0x10; // flag to do SBC instead of ADC + flags &= ~C01; + data = rg.a; + rg.a = 0; + goto adc_data; + + { + int inc; + case 0xA9: // CPD + case 0xB9: // CPDR + inc = -1; + if ( 0 ) + case 0xA1: // CPI + case 0xB1: // CPIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int result = rg.a - temp; + flags = (flags & C01) | N02 | + ((((temp ^ rg.a) & H10) ^ result) & (S80 | H10)); + + if ( !(uint8_t) result ) flags |= Z40; + result -= (flags & H10) >> 4; + flags |= result & F08; + flags |= result << 4 & F20; + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( flags & Z40 || data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xA8: // LDD + case 0xB8: // LDDR + inc = -1; + if ( 0 ) + case 0xA0: // LDI + case 0xB0: // LDIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + addr = rp.de; + rp.de = addr + inc; + WRITE( addr, temp ); + + temp += rg.a; + flags = (flags & (S80 | Z40 | C01)) | + (temp & F08) | (temp << 4 & F20); + if ( !--rp.bc ) + goto loop; + + flags |= V04; + if ( data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xAB: // OUTD + case 0xBB: // OTDR + inc = -1; + if ( 0 ) + case 0xA3: // OUTI + case 0xB3: // OTIR + inc = +1; + fuint16 addr = rp.hl; + rp.hl = addr + inc; + int temp = READ( addr ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + OUT( rp.bc, temp ); + goto loop; + } + + { + int inc; + case 0xAA: // IND + case 0xBA: // INDR + inc = -1; + if ( 0 ) + case 0xA2: // INI + case 0xB2: // INIR + inc = +1; + + fuint16 addr = rp.hl; + rp.hl = addr + inc; + + int temp = IN( rp.bc ); + + int b = --rg.b; + flags = (temp >> 6 & N02) | SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + WRITE( addr, temp ); + goto loop; + } + + case 0x47: // LD I,A + r.i = rg.a; + goto loop; + + case 0x4F: // LD R,A + SET_R( rg.a ); + dprintf( "LD R,A not supported\n" ); + warning = true; + goto loop; + + case 0x57: // LD A,I + rg.a = r.i; + goto ld_ai_common; + + case 0x5F: // LD A,R + rg.a = GET_R(); + dprintf( "LD A,R not supported\n" ); + warning = true; + ld_ai_common: + flags = (flags & C01) | SZ28( rg.a ) | (r.iff2 << 2 & V04); + goto loop; + + CASE8( 45, 4D, 55, 5D, 65, 6D, 75, 7D ): // RETI/RETN + r.iff1 = r.iff2; + goto ret_taken; + + case 0x46: case 0x4E: case 0x66: case 0x6E: // IM 0 + r.im = 0; + goto loop; + + case 0x56: case 0x76: // IM 1 + r.im = 1; + goto loop; + + case 0x5E: case 0x7E: // IM 2 + r.im = 2; + goto loop; + + default: + dprintf( "Opcode $ED $%02X not supported\n", data ); + warning = true; + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// DD/FD prefix + { + fuint16 ixy; + case 0xDD: + ixy = ix; + goto ix_prefix; + case 0xFD: + ixy = iy; + ix_prefix: + pc++; + unsigned data2 = READ_PROG( pc ); + s_time += ed_dd_timing [data] & 0x0F; + switch ( data ) + { + // TODO: more efficient way of avoid negative address + // TODO: avoid using this as argument to READ() since it is evaluated twice + #define IXY_DISP( ixy, disp ) uint16_t ((ixy) + (disp)) + + #define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in; + + // ADD/ADC/SUB/SBC + + case 0x96: // SUB (IXY+disp) + case 0x86: // ADD (IXY+disp) + flags &= ~C01; + case 0x9E: // SBC (IXY+disp) + case 0x8E: // ADC (IXY+disp) + pc++; + opcode = data; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto adc_data; + + case 0x94: // SUB HXY + case 0x84: // ADD HXY + flags &= ~C01; + case 0x9C: // SBC HXY + case 0x8C: // ADC HXY + opcode = data; + data = ixy >> 8; + goto adc_data; + + case 0x95: // SUB LXY + case 0x85: // ADD LXY + flags &= ~C01; + case 0x9D: // SBC LXY + case 0x8D: // ADC LXY + opcode = data; + data = (uint8_t) ixy; + goto adc_data; + + { + unsigned temp; + case 0x39: // ADD IXY,SP + temp = sp; + goto add_ixy_data; + + case 0x29: // ADD IXY,HL + temp = ixy; + goto add_ixy_data; + + case 0x09: // ADD IXY,BC + case 0x19: // ADD IXY,DE + temp = R16( data, 4, 0x09 ); + add_ixy_data: { + blargg_ulong sum = ixy + temp; + temp ^= ixy; + ixy = (uint16_t) sum; + flags = (flags & (S80 | Z40 | V04)) | + (sum >> 16) | + (sum >> 8 & (F20 | F08)) | + ((temp ^ sum) >> 8 & H10); + goto set_ixy; + } + } + + // AND + case 0xA6: // AND (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto and_data; + + case 0xA4: // AND HXY + data = ixy >> 8; + goto and_data; + + case 0xA5: // AND LXY + data = (uint8_t) ixy; + goto and_data; + + // OR + case 0xB6: // OR (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto or_data; + + case 0xB4: // OR HXY + data = ixy >> 8; + goto or_data; + + case 0xB5: // OR LXY + data = (uint8_t) ixy; + goto or_data; + + // XOR + case 0xAE: // XOR (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto xor_data; + + case 0xAC: // XOR HXY + data = ixy >> 8; + goto xor_data; + + case 0xAD: // XOR LXY + data = (uint8_t) ixy; + goto xor_data; + + // CP + case 0xBE: // CP (IXY+disp) + pc++; + data = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto cp_data; + + case 0xBC: // CP HXY + data = ixy >> 8; + goto cp_data; + + case 0xBD: // CP LXY + data = (uint8_t) ixy; + goto cp_data; + + // LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (IXY+disp),r + data = R8( data, 0x70 ); + if ( 0 ) + case 0x36: // LD (IXY+disp),imm + pc++, data = READ_PROG( pc ); + pc++; + WRITE( IXY_DISP( ixy, (int8_t) data2 ), data ); + goto loop; + + CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY + R8( data >> 3, 8 ) = ixy >> 8; + goto loop; + + case 0x64: // LD HXY,HXY + case 0x6D: // LD LXY,LXY + goto loop; + + CASE5( 45, 4D, 55, 5D, 7D ): // LD r,LXY + R8( data >> 3, 8 ) = ixy; + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp) + pc++; + R8( data >> 3, 8 ) = READ( IXY_DISP( ixy, (int8_t) data2 ) ); + goto loop; + + case 0x26: // LD HXY,imm + pc++; + goto ld_hxy_data; + + case 0x65: // LD HXY,LXY + data2 = (uint8_t) ixy; + goto ld_hxy_data; + + CASE5( 60, 61, 62, 63, 67 ): // LD HXY,r + data2 = R8( data, 0x60 ); + ld_hxy_data: + ixy = (uint8_t) ixy | (data2 << 8); + goto set_ixy; + + case 0x2E: // LD LXY,imm + pc++; + goto ld_lxy_data; + + case 0x6C: // LD LXY,HXY + data2 = ixy >> 8; + goto ld_lxy_data; + + CASE5( 68, 69, 6A, 6B, 6F ): // LD LXY,r + data2 = R8( data, 0x68 ); + ld_lxy_data: + ixy = (ixy & 0xFF00) | data2; + set_ixy: + if ( opcode == 0xDD ) + { + ix = ixy; + goto loop; + } + iy = ixy; + goto loop; + + case 0xF9: // LD SP,IXY + sp = ixy; + goto loop; + + case 0x22:{// LD (ADDR),IXY + fuint16 addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, ixy ); + goto loop; + } + + case 0x21: // LD IXY,imm + ixy = GET_ADDR(); + pc += 2; + goto set_ixy; + + case 0x2A:{// LD IXY,(addr) + fuint16 addr = GET_ADDR(); + ixy = READ_WORD( addr ); + pc += 2; + goto set_ixy; + } + + // DD/FD CB prefix + case 0xCB: { + data = IXY_DISP( ixy, (int8_t) data2 ); + pc++; + data2 = READ_PROG( pc ); + pc++; + switch ( data2 ) + { + case 0x06: goto rlc_data_addr; // RLC (IXY) + case 0x16: goto rl_data_addr; // RL (IXY) + case 0x26: goto sla_data_addr; // SLA (IXY) + case 0x36: goto sll_data_addr; // SLL (IXY) + case 0x0E: goto rrc_data_addr; // RRC (IXY) + case 0x1E: goto rr_data_addr; // RR (IXY) + case 0x2E: goto sra_data_addr; // SRA (IXY) + case 0x3E: goto srl_data_addr; // SRL (IXY) + + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) + fuint8 temp = READ( data ); + int masked = temp & 1 << (data2 >> 3 & 7); + flags = (flags & C01) | H10 | + (masked & S80) | + ((masked - 1) >> 8 & (Z40 | P04)); + goto loop; + } + + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(IXY+disp) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(IXY+disp) + int temp = READ( data ); + int bit = 1 << (data2 >> 3 & 7); + temp |= bit; // SET + if ( !(data2 & 0x40) ) + temp ^= bit; // RES + WRITE( data, temp ); + goto loop; + } + + default: + dprintf( "Opcode $%02X $CB $%02X not supported\n", opcode, data2 ); + warning = true; + goto loop; + } + assert( false ); + } + + // INC/DEC + case 0x23: // INC IXY + ixy = uint16_t (ixy + 1); + goto set_ixy; + + case 0x2B: // DEC IXY + ixy = uint16_t (ixy - 1); + goto set_ixy; + + case 0x34: // INC (IXY+disp) + ixy = IXY_DISP( ixy, (int8_t) data2 ); + pc++; + data = READ( ixy ) + 1; + WRITE( ixy, data ); + goto inc_set_flags; + + case 0x35: // DEC (IXY+disp) + ixy = IXY_DISP( ixy, (int8_t) data2 ); + pc++; + data = READ( ixy ) - 1; + WRITE( ixy, data ); + goto dec_set_flags; + + case 0x24: // INC HXY + ixy = uint16_t (ixy + 0x100); + data = ixy >> 8; + goto inc_xy_common; + + case 0x2C: // INC LXY + data = uint8_t (ixy + 1); + ixy = (ixy & 0xFF00) | data; + inc_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto inc_set_flags; + } + iy = ixy; + goto inc_set_flags; + + case 0x25: // DEC HXY + ixy = uint16_t (ixy - 0x100); + data = ixy >> 8; + goto dec_xy_common; + + case 0x2D: // DEC LXY + data = uint8_t (ixy - 1); + ixy = (ixy & 0xFF00) | data; + dec_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto dec_set_flags; + } + iy = ixy; + goto dec_set_flags; + + // PUSH/POP + case 0xE5: // PUSH IXY + data = ixy; + goto push_data; + + case 0xE1:{// POP IXY + ixy = READ_WORD( sp ); + sp = uint16_t (sp + 2); + goto set_ixy; + } + + // Misc + + case 0xE9: // JP (IXY) + pc = ixy; + goto loop; + + case 0xE3:{// EX (SP),IXY + fuint16 temp = READ_WORD( sp ); + WRITE_WORD( sp, ixy ); + ixy = temp; + goto set_ixy; + } + + default: + dprintf( "Unnecessary DD/FD prefix encountered\n" ); + warning = true; + pc--; + goto loop; + } + assert( false ); + } + + } + dprintf( "Unhandled main opcode: $%02X\n", opcode ); + assert( false ); + +hit_idle_addr: + s_time -= 11; + goto out_of_time; +halt: + s_time &= 3; // increment by multiple of 4 +out_of_time: + pc--; + + s.time = s_time; + rg.flags = flags; + r.ix = ix; + r.iy = iy; + r.sp = sp; + r.pc = pc; + this->r.b = rg; + this->state_ = s; + this->state = &this->state_; + + return warning; +} diff --git a/game-music-emu/gme/Kss_Cpu.h b/game-music-emu/gme/Kss_Cpu.h new file mode 100644 index 000000000..6297d1000 --- /dev/null +++ b/game-music-emu/gme/Kss_Cpu.h @@ -0,0 +1,124 @@ +// Z80 CPU emulator + +// Game_Music_Emu 0.5.2 +#ifndef KSS_CPU_H +#define KSS_CPU_H + +#include "blargg_endian.h" + +typedef blargg_long cpu_time_t; + +// must be defined by caller +void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data ); +int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr ); +void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data ); + +class Kss_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + // Clear registers and map all pages to unmapped + void reset( void* unmapped_write, void const* unmapped_read ); + + // Map memory. Start and size must be multiple of page_size. + enum { page_size = 0x2000 }; + void map_mem( unsigned addr, blargg_ulong size, void* write, void const* read ); + + // Map address to page + uint8_t* write( unsigned addr ); + uint8_t const* read( unsigned addr ); + + // Run until specified time is reached. Returns true if suspicious/unsupported + // instruction was encountered at any point during run. + bool run( cpu_time_t end_time ); + + // Time of beginning of next instruction + cpu_time_t time() const { return state->time + state->base; } + + // Alter current time. Not supported during run() call. + void set_time( cpu_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + typedef BOOST::uint16_t uint16_t; + + #if BLARGG_BIG_ENDIAN + struct regs_t { uint8_t b, c, d, e, h, l, flags, a; }; + #else + struct regs_t { uint8_t c, b, e, d, l, h, a, flags; }; + #endif + BOOST_STATIC_ASSERT( sizeof (regs_t) == 8 ); + + struct pairs_t { uint16_t bc, de, hl, fa; }; + + // Registers are not updated until run() returns + struct registers_t { + uint16_t pc; + uint16_t sp; + uint16_t ix; + uint16_t iy; + union { + regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a + pairs_t w; // w.bc, w.de, w.hl. w.fa + }; + union { + regs_t b; + pairs_t w; + } alt; + uint8_t iff1; + uint8_t iff2; + uint8_t r; + uint8_t i; + uint8_t im; + }; + //registers_t r; (below for efficiency) + + enum { idle_addr = 0xFFFF }; + + // can read this far past end of a page + enum { cpu_padding = 0x100 }; + +public: + Kss_Cpu(); + enum { page_shift = 13 }; + enum { page_count = 0x10000 >> page_shift }; +private: + uint8_t szpc [0x200]; + cpu_time_t end_time_; + struct state_t { + uint8_t const* read [page_count + 1]; + uint8_t * write [page_count + 1]; + cpu_time_t base; + cpu_time_t time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + void set_end_time( cpu_time_t t ); + void set_page( int i, void* write, void const* read ); +public: + registers_t r; +}; + +#if BLARGG_NONPORTABLE + #define KSS_CPU_PAGE_OFFSET( addr ) (addr) +#else + #define KSS_CPU_PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +inline BOOST::uint8_t* Kss_Cpu::write( unsigned addr ) +{ + return state->write [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr ); +} + +inline BOOST::uint8_t const* Kss_Cpu::read( unsigned addr ) +{ + return state->read [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr ); +} + +inline void Kss_Cpu::set_end_time( cpu_time_t t ) +{ + cpu_time_t delta = state->base - t; + state->base = t; + state->time += delta; +} + +#endif diff --git a/game-music-emu/gme/Kss_Emu.cpp b/game-music-emu/gme/Kss_Emu.cpp new file mode 100644 index 000000000..3176aece7 --- /dev/null +++ b/game-music-emu/gme/Kss_Emu.cpp @@ -0,0 +1,416 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Kss_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +long const clock_rate = 3579545; +int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count; + +Kss_Emu::Kss_Emu() +{ + sn = 0; + set_type( gme_kss_type ); + set_silence_lookahead( 6 ); + static const char* const names [osc_count] = { + "Square 1", "Square 2", "Square 3", + "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5" + }; + set_voice_names( names ); + + static int const types [osc_count] = { + wave_type | 0, wave_type | 1, wave_type | 2, + wave_type | 3, wave_type | 4, wave_type | 5, wave_type | 6, wave_type | 7 + }; + set_voice_types( types ); + + memset( unmapped_read, 0xFF, sizeof unmapped_read ); +} + +Kss_Emu::~Kss_Emu() { unload(); } + +void Kss_Emu::unload() +{ + delete sn; + sn = 0; + Classic_Emu::unload(); +} + +// Track info + +static void copy_kss_fields( Kss_Emu::header_t const& h, track_info_t* out ) +{ + const char* system = "MSX"; + if ( h.device_flags & 0x02 ) + { + system = "Sega Master System"; + if ( h.device_flags & 0x04 ) + system = "Game Gear"; + } + Gme_File::copy_field_( out->system, system ); +} + +blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const +{ + copy_kss_fields( header_, out ); + return 0; +} + +static blargg_err_t check_kss_header( void const* header ) +{ + if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Kss_File : Gme_Info_ +{ + Kss_Emu::header_t header_; + + Kss_File() { set_type( gme_kss_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + blargg_err_t err = in.read( &header_, Kss_Emu::header_size ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + return check_kss_header( &header_ ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_kss_fields( header_, out ); + return 0; + } +}; + +static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; } +static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; } + +static gme_type_t_ const gme_kss_type_ = { "MSX", 256, &new_kss_emu, &new_kss_file, "KSS", 0x03 }; +gme_type_t const gme_kss_type = &gme_kss_type_; + + +// Setup + +void Kss_Emu::update_gain() +{ + double g = gain() * 1.4; + if ( scc_accessed ) + g *= 1.5; + ay.volume( g ); + scc.volume( g ); + if ( sn ) + sn->volume( g ); +} + +blargg_err_t Kss_Emu::load_( Data_Reader& in ) +{ + memset( &header_, 0, sizeof header_ ); + assert( offsetof (header_t,device_flags) == header_size - 1 ); + assert( offsetof (ext_header_t,msx_audio_vol) == ext_header_size - 1 ); + RETURN_ERR( rom.load( in, header_size, STATIC_CAST(header_t*,&header_), 0 ) ); + + RETURN_ERR( check_kss_header( header_.tag ) ); + + if ( header_.tag [3] == 'C' ) + { + if ( header_.extra_header ) + { + header_.extra_header = 0; + set_warning( "Unknown data in header" ); + } + if ( header_.device_flags & ~0x0F ) + { + header_.device_flags &= 0x0F; + set_warning( "Unknown data in header" ); + } + } + else + { + ext_header_t& ext = header_; + memcpy( &ext, rom.begin(), min( (int) ext_header_size, (int) header_.extra_header ) ); + if ( header_.extra_header > 0x10 ) + set_warning( "Unknown data in header" ); + } + + if ( header_.device_flags & 0x09 ) + set_warning( "FM sound not supported" ); + + scc_enabled = 0xC000; + if ( header_.device_flags & 0x04 ) + scc_enabled = 0; + + if ( header_.device_flags & 0x02 && !sn ) + CHECK_ALLOC( sn = BLARGG_NEW( Sms_Apu ) ); + + set_voice_count( osc_count ); + + return setup_buffer( ::clock_rate ); +} + +void Kss_Emu::update_eq( blip_eq_t const& eq ) +{ + ay.treble_eq( eq ); + scc.treble_eq( eq ); + if ( sn ) + sn->treble_eq( eq ); +} + +void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + int i2 = i - ay.osc_count; + if ( i2 >= 0 ) + scc.osc_output( i2, center ); + else + ay.osc_output( i, center ); + if ( sn && i < sn->osc_count ) + sn->osc_output( i, center, left, right ); +} + +// Emulation + +void Kss_Emu::set_tempo_( double t ) +{ + blip_time_t period = + (header_.device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60); + play_period = blip_time_t (period / t); +} + +blargg_err_t Kss_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( ram, 0xC9, 0x4000 ); + memset( ram + 0x4000, 0, sizeof ram - 0x4000 ); + + // copy driver code to lo RAM + static byte const bios [] = { + 0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG + 0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG + }; + static byte const vectors [] = { + 0xC3, 0x01, 0x00, // $0093: WRTPSG vector + 0xC3, 0x09, 0x00, // $0096: RDPSG vector + }; + memcpy( ram + 0x01, bios, sizeof bios ); + memcpy( ram + 0x93, vectors, sizeof vectors ); + + // copy non-banked data into RAM + unsigned load_addr = get_le16( header_.load_addr ); + long orig_load_size = get_le16( header_.load_size ); + long load_size = min( orig_load_size, rom.file_size() ); + load_size = min( load_size, long (mem_size - load_addr) ); + if ( load_size != orig_load_size ) + set_warning( "Excessive data size" ); + memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size ); + + rom.set_addr( -load_size - header_.extra_header ); + + // check available bank data + blargg_long const bank_size = this->bank_size(); + int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size; + bank_count = header_.bank_mode & 0x7F; + if ( bank_count > max_banks ) + { + bank_count = max_banks; + set_warning( "Bank data missing" ); + } + //dprintf( "load_size : $%X\n", load_size ); + //dprintf( "bank_size : $%X\n", bank_size ); + //dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F ); + + ram [idle_addr] = 0xFF; + cpu::reset( unmapped_write, unmapped_read ); + cpu::map_mem( 0, mem_size, ram, ram ); + + ay.reset(); + scc.reset(); + if ( sn ) + sn->reset(); + r.sp = 0xF380; + ram [--r.sp] = idle_addr >> 8; + ram [--r.sp] = idle_addr & 0xFF; + r.b.a = track; + r.pc = get_le16( header_.init_addr ); + next_play = play_period; + scc_accessed = false; + gain_updated = false; + update_gain(); + ay_latch = 0; + + return 0; +} + +void Kss_Emu::set_bank( int logical, int physical ) +{ + unsigned const bank_size = this->bank_size(); + + unsigned addr = 0x8000; + if ( logical && bank_size == 8 * 1024 ) + addr = 0xA000; + + physical -= header_.first_bank; + if ( (unsigned) physical >= (unsigned) bank_count ) + { + byte* data = ram + addr; + cpu::map_mem( addr, bank_size, data, data ); + } + else + { + long phys = physical * (blargg_long) bank_size; + for ( unsigned offset = 0; offset < bank_size; offset += page_size ) + cpu::map_mem( addr + offset, page_size, + unmapped_write, rom.at_addr( phys + offset ) ); + } +} + +void Kss_Emu::cpu_write( unsigned addr, int data ) +{ + data &= 0xFF; + switch ( addr ) + { + case 0x9000: + set_bank( 0, data ); + return; + + case 0xB000: + set_bank( 1, data ); + return; + } + + int scc_addr = (addr & 0xDFFF) ^ 0x9800; + if ( scc_addr < scc.reg_count ) + { + scc_accessed = true; + scc.write( time(), scc_addr, data ); + return; + } + + dprintf( "LD ($%04X),$%02X\n", addr, data ); +} + +void kss_cpu_write( Kss_Cpu* cpu, unsigned addr, int data ) +{ + *cpu->write( addr ) = data; + if ( (addr & STATIC_CAST(Kss_Emu&,*cpu).scc_enabled) == 0x8000 ) + STATIC_CAST(Kss_Emu&,*cpu).cpu_write( addr, data ); +} + +void kss_cpu_out( Kss_Cpu* cpu, cpu_time_t time, unsigned addr, int data ) +{ + data &= 0xFF; + Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu); + switch ( addr & 0xFF ) + { + case 0xA0: + emu.ay_latch = data & 0x0F; + return; + + case 0xA1: + GME_APU_HOOK( &emu, emu.ay_latch, data ); + emu.ay.write( time, emu.ay_latch, data ); + return; + + case 0x06: + if ( emu.sn && (emu.header_.device_flags & 0x04) ) + { + emu.sn->write_ggstereo( time, data ); + return; + } + break; + + case 0x7E: + case 0x7F: + if ( emu.sn ) + { + GME_APU_HOOK( &emu, 16, data ); + emu.sn->write_data( time, data ); + return; + } + break; + + case 0xFE: + emu.set_bank( 0, data ); + return; + + #ifndef NDEBUG + case 0xF1: // FM data + if ( data ) + break; // trap non-zero data + case 0xF0: // FM addr + case 0xA8: // PPI + return; + #endif + } + + dprintf( "OUT $%04X,$%02X\n", addr, data ); +} + +int kss_cpu_in( Kss_Cpu*, cpu_time_t, unsigned addr ) +{ + //Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu); + //switch ( addr & 0xFF ) + //{ + //} + + dprintf( "IN $%04X\n", addr ); + return 0; +} + +// Emulation + +blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int ) +{ + while ( time() < duration ) + { + blip_time_t end = min( duration, next_play ); + cpu::run( min( duration, next_play ) ); + if ( r.pc == idle_addr ) + set_time( end ); + + if ( time() >= next_play ) + { + next_play += play_period; + if ( r.pc == idle_addr ) + { + if ( !gain_updated ) + { + gain_updated = true; + if ( scc_accessed ) + update_gain(); + } + + ram [--r.sp] = idle_addr >> 8; + ram [--r.sp] = idle_addr & 0xFF; + r.pc = get_le16( header_.play_addr ); + GME_FRAME_HOOK( this ); + } + } + } + + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + adjust_time( -duration ); + ay.end_frame( duration ); + scc.end_frame( duration ); + if ( sn ) + sn->end_frame( duration ); + + return 0; +} diff --git a/game-music-emu/gme/Kss_Emu.h b/game-music-emu/gme/Kss_Emu.h new file mode 100644 index 000000000..4d8463abd --- /dev/null +++ b/game-music-emu/gme/Kss_Emu.h @@ -0,0 +1,96 @@ +// MSX computer KSS music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef KSS_EMU_H +#define KSS_EMU_H + +#include "Classic_Emu.h" +#include "Kss_Scc_Apu.h" +#include "Kss_Cpu.h" +#include "Sms_Apu.h" +#include "Ay_Apu.h" + +class Kss_Emu : private Kss_Cpu, public Classic_Emu { + typedef Kss_Cpu cpu; +public: + // KSS file header + enum { header_size = 0x10 }; + struct header_t + { + byte tag [4]; + byte load_addr [2]; + byte load_size [2]; + byte init_addr [2]; + byte play_addr [2]; + byte first_bank; + byte bank_mode; + byte extra_header; + byte device_flags; + }; + + enum { ext_header_size = 0x10 }; + struct ext_header_t + { + byte data_size [4]; + byte unused [4]; + byte first_track [2]; + byte last_tack [2]; + byte psg_vol; + byte scc_vol; + byte msx_music_vol; + byte msx_audio_vol; + }; + + struct composite_header_t : header_t, ext_header_t { }; + + // Header for currently loaded file + composite_header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_kss_type; } +public: + Kss_Emu(); + ~Kss_Emu(); +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + void unload(); +private: + Rom_Data rom; + composite_header_t header_; + + bool scc_accessed; + bool gain_updated; + void update_gain(); + + unsigned scc_enabled; // 0 or 0xC000 + byte const* bank_data; + int bank_count; + void set_bank( int logical, int physical ); + blargg_long bank_size() const { return (16 * 1024L) >> (header_.bank_mode >> 7 & 1); } + + blip_time_t play_period; + blip_time_t next_play; + int ay_latch; + + friend void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data ); + friend int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr ); + void cpu_write( unsigned addr, int data ); + friend void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data ); + + // large items + enum { mem_size = 0x10000 }; + byte ram [mem_size + cpu_padding]; + + Ay_Apu ay; + Scc_Apu scc; + Sms_Apu* sn; + byte unmapped_read [0x100]; + byte unmapped_write [page_size]; +}; + +#endif diff --git a/game-music-emu/gme/Kss_Scc_Apu.cpp b/game-music-emu/gme/Kss_Scc_Apu.cpp new file mode 100644 index 000000000..1660ac3da --- /dev/null +++ b/game-music-emu/gme/Kss_Scc_Apu.cpp @@ -0,0 +1,97 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Kss_Scc_Apu.h" + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Tones above this frequency are treated as disabled tone at half volume. +// Power of two is more efficient (avoids division). +unsigned const inaudible_freq = 16384; + +int const wave_size = 0x20; + +void Scc_Apu::run_until( blip_time_t end_time ) +{ + for ( int index = 0; index < osc_count; index++ ) + { + osc_t& osc = oscs [index]; + + Blip_Buffer* const output = osc.output; + if ( !output ) + continue; + output->set_modified(); + + blip_time_t period = (regs [0x80 + index * 2 + 1] & 0x0F) * 0x100 + + regs [0x80 + index * 2] + 1; + int volume = 0; + if ( regs [0x8F] & (1 << index) ) + { + blip_time_t inaudible_period = (blargg_ulong) (output->clock_rate() + + inaudible_freq * 32) / (inaudible_freq * 16); + if ( period > inaudible_period ) + volume = (regs [0x8A + index] & 0x0F) * (amp_range / 256 / 15); + } + + BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size; + if ( index == osc_count - 1 ) + wave -= wave_size; // last two oscs share wave + { + int amp = wave [osc.phase] * volume; + int delta = amp - osc.last_amp; + if ( delta ) + { + osc.last_amp = amp; + synth.offset( last_time, delta, output ); + } + } + + blip_time_t time = last_time + osc.delay; + if ( time < end_time ) + { + if ( !volume ) + { + // maintain phase + blargg_long count = (end_time - time + period - 1) / period; + osc.phase = (osc.phase + count) & (wave_size - 1); + time += count * period; + } + else + { + + int phase = osc.phase; + int last_wave = wave [phase]; + phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop + + do + { + int amp = wave [phase]; + phase = (phase + 1) & (wave_size - 1); + int delta = amp - last_wave; + if ( delta ) + { + last_wave = amp; + synth.offset( time, delta * volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase = (phase - 1) & (wave_size - 1); // undo pre-advance + osc.last_amp = wave [phase] * volume; + } + } + osc.delay = time - end_time; + } + last_time = end_time; +} diff --git a/game-music-emu/gme/Kss_Scc_Apu.h b/game-music-emu/gme/Kss_Scc_Apu.h new file mode 100644 index 000000000..03a6a1080 --- /dev/null +++ b/game-music-emu/gme/Kss_Scc_Apu.h @@ -0,0 +1,106 @@ +// Konami SCC sound chip emulator + +// Game_Music_Emu 0.5.2 +#ifndef KSS_SCC_APU_H +#define KSS_SCC_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" +#include + +class Scc_Apu { +public: + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Reset sound chip + void reset(); + + // Write to register at specified time + enum { reg_count = 0x90 }; + void write( blip_time_t time, int reg, int data ); + + // Run sound to specified time, end current time frame, then start a new + // time frame at time 0. Time frames have no effect on emulation and each + // can be whatever length is convenient. + void end_frame( blip_time_t length ); + +// Additional features + + // Set sound output of specific oscillator to buffer, where index is + // 0 to 4. If buffer is NULL, the specified oscillator is muted. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see documentation) + void treble_eq( blip_eq_t const& ); + +public: + Scc_Apu(); +private: + enum { amp_range = 0x8000 }; + struct osc_t + { + int delay; + int phase; + int last_amp; + Blip_Buffer* output; + }; + osc_t oscs [osc_count]; + blip_time_t last_time; + unsigned char regs [reg_count]; + Blip_Synth synth; + + void run_until( blip_time_t ); +}; + +inline void Scc_Apu::volume( double v ) { synth.volume( 0.43 / osc_count / amp_range * v ); } + +inline void Scc_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + +inline void Scc_Apu::osc_output( int index, Blip_Buffer* b ) +{ + assert( (unsigned) index < osc_count ); + oscs [index].output = b; +} + +inline void Scc_Apu::write( blip_time_t time, int addr, int data ) +{ + assert( (unsigned) addr < reg_count ); + run_until( time ); + regs [addr] = data; +} + +inline void Scc_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + last_time -= end_time; + assert( last_time >= 0 ); +} + +inline void Scc_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + oscs [i].output = buf; +} + +inline Scc_Apu::Scc_Apu() +{ + output( 0 ); +} + +inline void Scc_Apu::reset() +{ + last_time = 0; + + for ( int i = 0; i < osc_count; i++ ) + memset( &oscs [i], 0, offsetof (osc_t,output) ); + + memset( regs, 0, sizeof regs ); +} + +#endif diff --git a/game-music-emu/gme/M3u_Playlist.cpp b/game-music-emu/gme/M3u_Playlist.cpp new file mode 100644 index 000000000..f5d38893a --- /dev/null +++ b/game-music-emu/gme/M3u_Playlist.cpp @@ -0,0 +1,428 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#define IN_GME 1 + +#include "M3u_Playlist.h" +#include "Music_Emu.h" + +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// gme functions defined here to avoid linking in m3u code unless it's used + +blargg_err_t Gme_File::load_m3u_( blargg_err_t err ) +{ + require( raw_track_count_ ); // file must be loaded first + + if ( !err ) + { + if ( playlist.size() ) + track_count_ = playlist.size(); + + int line = playlist.first_error(); + if ( line ) + { + // avoid using bloated printf() + char* out = &playlist_warning [sizeof playlist_warning]; + *--out = 0; + do { + *--out = line % 10 + '0'; + } while ( (line /= 10) > 0 ); + + static const char str [] = "Problem in m3u at line "; + out -= sizeof str - 1; + memcpy( out, str, sizeof str - 1 ); + set_warning( out ); + } + } + return err; +} + +blargg_err_t Gme_File::load_m3u( const char* path ) { return load_m3u_( playlist.load( path ) ); } + +blargg_err_t Gme_File::load_m3u( Data_Reader& in ) { return load_m3u_( playlist.load( in ) ); } + +gme_err_t GMEAPI gme_load_m3u( Music_Emu* me, const char* path ) { return me->load_m3u( path ); } + +gme_err_t GMEAPI gme_load_m3u_data( Music_Emu* me, const void* data, long size ) +{ + Mem_File_Reader in( data, size ); + return me->load_m3u( in ); +} + + + +static char* skip_white( char* in ) +{ + while ( *in == ' ' ) + in++; + return in; +} + +inline unsigned from_dec( unsigned n ) { return n - '0'; } + +static char* parse_filename( char* in, M3u_Playlist::entry_t& entry ) +{ + entry.file = in; + entry.type = ""; + char* out = in; + while ( 1 ) + { + int c = *in; + if ( !c ) break; + in++; + + if ( c == ',' ) // commas in filename + { + char* p = skip_white( in ); + if ( *p == '$' || from_dec( *p ) <= 9 ) + { + in = p; + break; + } + } + + if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix + { + entry.type = ++in; + while ( (c = *in) != 0 && c != ',' ) + in++; + if ( c == ',' ) + { + *in++ = 0; // terminate type + in = skip_white( in ); + } + break; + } + + if ( c == '\\' ) // \ prefix for special characters + { + c = *in; + if ( !c ) break; + in++; + } + *out++ = (char) c; + } + *out = 0; // terminate string + return in; +} + +static char* next_field( char* in, int* result ) +{ + while ( 1 ) + { + in = skip_white( in ); + + if ( !*in ) + break; + + if ( *in == ',' ) + { + in++; + break; + } + + *result = 1; + in++; + } + return skip_white( in ); +} + +static char* parse_int_( char* in, int* out ) +{ + int n = 0; + while ( 1 ) + { + unsigned d = from_dec( *in ); + if ( d > 9 ) + break; + in++; + n = n * 10 + d; + *out = n; + } + return in; +} + +static char* parse_int( char* in, int* out, int* result ) +{ + return next_field( parse_int_( in, out ), result ); +} + +// Returns 16 or greater if not hex +inline int from_hex_char( int h ) +{ + h -= 0x30; + if ( (unsigned) h > 9 ) + h = ((h - 0x11) & 0xDF) + 10; + return h; +} + +static char* parse_track( char* in, M3u_Playlist::entry_t& entry, int* result ) +{ + if ( *in == '$' ) + { + in++; + int n = 0; + while ( 1 ) + { + int h = from_hex_char( *in ); + if ( h > 15 ) + break; + in++; + n = n * 16 + h; + entry.track = n; + } + } + else + { + in = parse_int_( in, &entry.track ); + if ( entry.track >= 0 ) + entry.decimal_track = 1; + } + return next_field( in, result ); +} + +static char* parse_time_( char* in, int* out ) +{ + *out = -1; + int n = -1; + in = parse_int_( in, &n ); + if ( n >= 0 ) + { + *out = n; + if ( *in == ':' ) + { + n = -1; + in = parse_int_( in + 1, &n ); + if ( n >= 0 ) + *out = *out * 60 + n; + } + } + return in; +} + +static char* parse_time( char* in, int* out, int* result ) +{ + return next_field( parse_time_( in, out ), result ); +} + +static char* parse_name( char* in ) +{ + char* out = in; + while ( 1 ) + { + int c = *in; + if ( !c ) break; + in++; + + if ( c == ',' ) // commas in string + { + char* p = skip_white( in ); + if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 ) + { + in = p; + break; + } + } + + if ( c == '\\' ) // \ prefix for special characters + { + c = *in; + if ( !c ) break; + in++; + } + *out++ = (char) c; + } + *out = 0; // terminate string + return in; +} + +static int parse_line( char* in, M3u_Playlist::entry_t& entry ) +{ + int result = 0; + + // file + entry.file = in; + entry.type = ""; + in = parse_filename( in, entry ); + + // track + entry.track = -1; + entry.decimal_track = 0; + in = parse_track( in, entry, &result ); + + // name + entry.name = in; + in = parse_name( in ); + + // time + entry.length = -1; + in = parse_time( in, &entry.length, &result ); + + // loop + entry.intro = -1; + entry.loop = -1; + if ( *in == '-' ) + { + entry.loop = entry.length; + in++; + } + else + { + in = parse_time_( in, &entry.loop ); + if ( entry.loop >= 0 ) + { + entry.intro = 0; + if ( *in == '-' ) // trailing '-' means that intro length was specified + { + in++; + entry.intro = entry.loop; + entry.loop = entry.length - entry.intro; + } + } + } + in = next_field( in, &result ); + + // fade + entry.fade = -1; + in = parse_time( in, &entry.fade, &result ); + + // repeat + entry.repeat = -1; + in = parse_int( in, &entry.repeat, &result ); + + return result; +} + +static void parse_comment( char* in, M3u_Playlist::info_t& info, bool first ) +{ + in = skip_white( in + 1 ); + const char* field = in; + while ( *in && *in != ':' ) + in++; + + if ( *in == ':' ) + { + const char* text = skip_white( in + 1 ); + if ( *text ) + { + *in = 0; + if ( !strcmp( "Composer", field ) ) info.composer = text; + else if ( !strcmp( "Engineer", field ) ) info.engineer = text; + else if ( !strcmp( "Ripping" , field ) ) info.ripping = text; + else if ( !strcmp( "Tagging" , field ) ) info.tagging = text; + else + text = 0; + if ( text ) + return; + *in = ':'; + } + } + + if ( first ) + info.title = field; +} + +blargg_err_t M3u_Playlist::parse_() +{ + info_.title = ""; + info_.composer = ""; + info_.engineer = ""; + info_.ripping = ""; + info_.tagging = ""; + + int const CR = 13; + int const LF = 10; + + data.end() [-1] = LF; // terminate input + + first_error_ = 0; + bool first_comment = true; + int line = 0; + int count = 0; + char* in = data.begin(); + while ( in < data.end() ) + { + // find end of line and terminate it + line++; + char* begin = in; + while ( *in != CR && *in != LF ) + { + if ( !*in ) + return "Not an m3u playlist"; + in++; + } + if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line + *in++ = 0; + *in++ = 0; + + // parse line + if ( *begin == '#' ) + { + parse_comment( begin, info_, first_comment ); + first_comment = false; + } + else if ( *begin ) + { + if ( (int) entries.size() <= count ) + RETURN_ERR( entries.resize( count * 2 + 64 ) ); + + if ( !parse_line( begin, entries [count] ) ) + count++; + else if ( !first_error_ ) + first_error_ = line; + first_comment = false; + } + } + if ( count <= 0 ) + return "Not an m3u playlist"; + + if ( !(info_.composer [0] | info_.engineer [0] | info_.ripping [0] | info_.tagging [0]) ) + info_.title = ""; + + return entries.resize( count ); +} + +blargg_err_t M3u_Playlist::parse() +{ + blargg_err_t err = parse_(); + if ( err ) + { + entries.clear(); + data.clear(); + } + return err; +} + +blargg_err_t M3u_Playlist::load( Data_Reader& in ) +{ + RETURN_ERR( data.resize( in.remain() + 1 ) ); + RETURN_ERR( in.read( data.begin(), long(data.size() - 1) ) ); + return parse(); +} + +blargg_err_t M3u_Playlist::load( const char* path ) +{ + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + return load( in ); +} + +blargg_err_t M3u_Playlist::load( void const* in, long size ) +{ + RETURN_ERR( data.resize( size + 1 ) ); + memcpy( data.begin(), in, size ); + return parse(); +} diff --git a/game-music-emu/gme/M3u_Playlist.h b/game-music-emu/gme/M3u_Playlist.h new file mode 100644 index 000000000..a13387945 --- /dev/null +++ b/game-music-emu/gme/M3u_Playlist.h @@ -0,0 +1,67 @@ +// M3U playlist file parser, with support for subtrack information + +// Game_Music_Emu 0.5.2 +#ifndef M3U_PLAYLIST_H +#define M3U_PLAYLIST_H + +#include "blargg_common.h" +#include "Data_Reader.h" + +class M3u_Playlist { +public: + // Load playlist data + blargg_err_t load( const char* path ); + blargg_err_t load( Data_Reader& in ); + blargg_err_t load( void const* data, long size ); + + // Line number of first parse error, 0 if no error. Any lines with parse + // errors are ignored. + int first_error() const { return first_error_; } + + struct info_t + { + const char* title; + const char* composer; + const char* engineer; + const char* ripping; + const char* tagging; + }; + info_t const& info() const { return info_; } + + struct entry_t + { + const char* file; // filename without stupid ::TYPE suffix + const char* type; // if filename has ::TYPE suffix, this will be "TYPE". "" if none. + const char* name; + bool decimal_track; // true if track was specified in hex + // integers are -1 if not present + int track; // 1-based + int length; // seconds + int intro; + int loop; + int fade; + int repeat; // count + }; + entry_t const& operator [] ( int i ) const { return entries [i]; } + int size() const { return (int)entries.size(); } + + void clear(); + +private: + blargg_vector entries; + blargg_vector data; + int first_error_; + info_t info_; + + blargg_err_t parse(); + blargg_err_t parse_(); +}; + +inline void M3u_Playlist::clear() +{ + first_error_ = 0; + entries.clear(); + data.clear(); +} + +#endif diff --git a/game-music-emu/gme/Multi_Buffer.cpp b/game-music-emu/gme/Multi_Buffer.cpp new file mode 100644 index 000000000..ecd8f8add --- /dev/null +++ b/game-music-emu/gme/Multi_Buffer.cpp @@ -0,0 +1,232 @@ +// Blip_Buffer 0.4.1. http://www.slack.net/~ant/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; +} + +blargg_err_t Multi_Buffer::set_channel_count( int ) { return 0; } + +// Silent_Buffer + +Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +{ + // TODO: better to use empty Blip_Buffer so caller never has to check for NULL? + chan.left = 0; + chan.center = 0; + chan.right = 0; +} + +// Mono_Buffer + +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +{ + chan.center = &buf; + chan.left = &buf; + chan.right = &buf; +} + +Mono_Buffer::~Mono_Buffer() { } + +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) +{ + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +// Stereo_Buffer + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +{ + chan.center = &bufs [0]; + chan.left = &bufs [1]; + chan.right = &bufs [2]; +} + +Stereo_Buffer::~Stereo_Buffer() { } + +blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + stereo_added = 0; + was_stereo = false; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t clock_count ) +{ + stereo_added = 0; + for ( unsigned i = 0; i < buf_count; i++ ) + { + stereo_added |= bufs [i].clear_modified() << i; + bufs [i].end_frame( clock_count ); + } +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +{ + require( !(count & 1) ); // count must be even + count = (unsigned) count / 2; + + long avail = bufs [0].samples_avail(); + if ( count > avail ) + count = avail; + if ( count ) + { + int bufs_used = stereo_added | was_stereo; + //dprintf( "%X\n", bufs_used ); + if ( bufs_used <= 1 ) + { + mix_mono( out, count ); + bufs [0].remove_samples( count ); + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + else if ( bufs_used & 1 ) + { + mix_stereo( out, count ); + bufs [0].remove_samples( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + else + { + mix_stereo_no_center( out, count ); + bufs [0].remove_silence( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + + // to do: this might miss opportunities for optimization + if ( !bufs [0].samples_avail() ) + { + was_stereo = stereo_added; + stereo_added = 0; + } + } + + return count * 2; +} + +void Stereo_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [1] ); + BLIP_READER_BEGIN( left, bufs [1] ); + BLIP_READER_BEGIN( right, bufs [2] ); + BLIP_READER_BEGIN( center, bufs [0] ); + + for ( ; count; --count ) + { + int c = BLIP_READER_READ( center ); + blargg_long l = c + BLIP_READER_READ( left ); + blargg_long r = c + BLIP_READER_READ( right ); + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + BLIP_READER_NEXT( center, bass ); + if ( (BOOST::int16_t) r != r ) + r = 0x7FFF - (r >> 24); + + BLIP_READER_NEXT( left, bass ); + BLIP_READER_NEXT( right, bass ); + + out [0] = l; + out [1] = r; + out += 2; + } + + BLIP_READER_END( center, bufs [0] ); + BLIP_READER_END( right, bufs [2] ); + BLIP_READER_END( left, bufs [1] ); +} + +void Stereo_Buffer::mix_stereo_no_center( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [1] ); + BLIP_READER_BEGIN( left, bufs [1] ); + BLIP_READER_BEGIN( right, bufs [2] ); + + for ( ; count; --count ) + { + blargg_long l = BLIP_READER_READ( left ); + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + blargg_long r = BLIP_READER_READ( right ); + if ( (BOOST::int16_t) r != r ) + r = 0x7FFF - (r >> 24); + + BLIP_READER_NEXT( left, bass ); + BLIP_READER_NEXT( right, bass ); + + out [0] = l; + out [1] = r; + out += 2; + } + + BLIP_READER_END( right, bufs [2] ); + BLIP_READER_END( left, bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out_, blargg_long count ) +{ + blip_sample_t* BLIP_RESTRICT out = out_; + int const bass = BLIP_READER_BASS( bufs [0] ); + BLIP_READER_BEGIN( center, bufs [0] ); + + for ( ; count; --count ) + { + blargg_long s = BLIP_READER_READ( center ); + if ( (BOOST::int16_t) s != s ) + s = 0x7FFF - (s >> 24); + + BLIP_READER_NEXT( center, bass ); + out [0] = s; + out [1] = s; + out += 2; + } + + BLIP_READER_END( center, bufs [0] ); +} diff --git a/game-music-emu/gme/Multi_Buffer.h b/game-music-emu/gme/Multi_Buffer.h new file mode 100644 index 000000000..82c8b3ab5 --- /dev/null +++ b/game-music-emu/gme/Multi_Buffer.h @@ -0,0 +1,158 @@ +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.4.1 +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer( int samples_per_frame ); + virtual ~Multi_Buffer() { } + + // Set the number of channels available + virtual blargg_err_t set_channel_count( int ); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + enum { type_index_mask = 0xFF }; + enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; + virtual channel_t channel( int index, int type ) = 0; + + // See Blip_Buffer.h + virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0; + virtual void clock_rate( long ) = 0; + virtual void bass_freq( int ) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h + virtual void end_frame( blip_time_t ) = 0; + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) = 0; + virtual long samples_avail() const = 0; + +public: + BLARGG_DISABLE_NOTHROW +protected: + void channels_changed() { channels_changed_count_++; } +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int const samples_per_frame_; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; + channel_t chan; +public: + // Buffer used for all channels + Blip_Buffer* center() { return &buf; } + +public: + Mono_Buffer(); + ~Mono_Buffer(); + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long rate ) { buf.clock_rate( rate ); } + void bass_freq( int freq ) { buf.bass_freq( freq ); } + void clear() { buf.clear(); } + long samples_avail() const { return buf.samples_avail(); } + long read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + channel_t channel( int, int ) { return chan; } + void end_frame( blip_time_t t ) { buf.end_frame( t ); } +}; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + + // Buffers used for all channels + Blip_Buffer* center() { return &bufs [0]; } + Blip_Buffer* left() { return &bufs [1]; } + Blip_Buffer* right() { return &bufs [2]; } + +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + blargg_err_t set_sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int, int ) { return chan; } + void end_frame( blip_time_t ); + + long samples_avail() const { return bufs [0].samples_avail() * 2; } + long read_samples( blip_sample_t*, long ); + +private: + enum { buf_count = 3 }; + Blip_Buffer bufs [buf_count]; + channel_t chan; + int stereo_added; + int was_stereo; + + void mix_stereo_no_center( blip_sample_t*, blargg_long ); + void mix_stereo( blip_sample_t*, blargg_long ); + void mix_mono( blip_sample_t*, blargg_long ); +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer { + channel_t chan; +public: + Silent_Buffer(); + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ) { } + void bass_freq( int ) { } + void clear() { } + channel_t channel( int, int ) { return chan; } + void end_frame( blip_time_t ) { } + long samples_avail() const { return 0; } + long read_samples( blip_sample_t*, long ) { return 0; } +}; + + +inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return 0; +} + +inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec ) +{ + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +#endif diff --git a/game-music-emu/gme/Music_Emu.cpp b/game-music-emu/gme/Music_Emu.cpp new file mode 100644 index 000000000..f01024ce3 --- /dev/null +++ b/game-music-emu/gme/Music_Emu.cpp @@ -0,0 +1,410 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Music_Emu.h" + +#include "Multi_Buffer.h" +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const stereo = 2; // number of channels for stereo +int const silence_max = 6; // seconds +int const silence_threshold = 0x10; +long const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) + +Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180 }; + +void Music_Emu::clear_track_vars() +{ + current_track_ = -1; + out_time = 0; + emu_time = 0; + emu_track_ended_ = true; + track_ended_ = true; + fade_start = INT_MAX / 2 + 1; + fade_step = 1; + silence_time = 0; + silence_count = 0; + buf_remain = 0; + warning(); // clear warning +} + +void Music_Emu::unload() +{ + voice_count_ = 0; + clear_track_vars(); + Gme_File::unload(); +} + +Music_Emu::Music_Emu() +{ + effects_buffer = 0; + + sample_rate_ = 0; + mute_mask_ = 0; + tempo_ = 1.0; + gain_ = 1.0; + + // defaults + max_initial_silence = 2; + silence_lookahead = 3; + ignore_silence_ = false; + equalizer_.treble = -1.0; + equalizer_.bass = 60; + + static const char* const names [] = { + "Voice 1", "Voice 2", "Voice 3", "Voice 4", + "Voice 5", "Voice 6", "Voice 7", "Voice 8" + }; + set_voice_names( names ); + Music_Emu::unload(); // non-virtual +} + +Music_Emu::~Music_Emu() { delete effects_buffer; } + +blargg_err_t Music_Emu::set_sample_rate( long rate ) +{ + require( !sample_rate() ); // sample rate can't be changed once set + RETURN_ERR( set_sample_rate_( rate ) ); + RETURN_ERR( buf.resize( buf_size ) ); + sample_rate_ = rate; + return 0; +} + +void Music_Emu::pre_load() +{ + require( sample_rate() ); // set_sample_rate() must be called before loading a file + Gme_File::pre_load(); +} + +void Music_Emu::set_equalizer( equalizer_t const& eq ) +{ + equalizer_ = eq; + set_equalizer_( eq ); +} + +void Music_Emu::mute_voice( int index, bool mute ) +{ + require( (unsigned) index < (unsigned) voice_count() ); + int bit = 1 << index; + int mask = mute_mask_ | bit; + if ( !mute ) + mask ^= bit; + mute_voices( mask ); +} + +void Music_Emu::mute_voices( int mask ) +{ + require( sample_rate() ); // sample rate must be set first + mute_mask_ = mask; + mute_voices_( mask ); +} + +void Music_Emu::set_tempo( double t ) +{ + require( sample_rate() ); // sample rate must be set first + double const min = 0.02; + double const max = 4.00; + if ( t < min ) t = min; + if ( t > max ) t = max; + tempo_ = t; + set_tempo_( t ); +} + +void Music_Emu::post_load_() +{ + set_tempo( tempo_ ); + remute_voices(); +} + +blargg_err_t Music_Emu::start_track( int track ) +{ + clear_track_vars(); + + int remapped = track; + RETURN_ERR( remap_track_( &remapped ) ); + current_track_ = track; + RETURN_ERR( start_track_( remapped ) ); + + emu_track_ended_ = false; + track_ended_ = false; + + if ( !ignore_silence_ ) + { + // play until non-silence or end of track + for ( long end = max_initial_silence * stereo * sample_rate(); emu_time < end; ) + { + fill_buf(); + if ( buf_remain | (int) emu_track_ended_ ) + break; + } + + emu_time = buf_remain; + out_time = 0; + silence_time = 0; + silence_count = 0; + } + return track_ended() ? warning() : 0; +} + +void Music_Emu::end_track_if_error( blargg_err_t err ) +{ + if ( err ) + { + emu_track_ended_ = true; + set_warning( err ); + } +} + +// Tell/Seek + +blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const +{ + blargg_long sec = msec / 1000; + msec -= sec * 1000; + return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo; +} + +long Music_Emu::tell() const +{ + blargg_long rate = sample_rate() * stereo; + blargg_long sec = out_time / rate; + return sec * 1000 + (out_time - sec * rate) * 1000 / rate; +} + +blargg_err_t Music_Emu::seek( long msec ) +{ + blargg_long time = msec_to_samples( msec ); + if ( time < out_time ) + RETURN_ERR( start_track( current_track_ ) ); + return skip( time - out_time ); +} + +blargg_err_t Music_Emu::skip( long count ) +{ + require( current_track() >= 0 ); // start_track() must have been called already + out_time += count; + + // remove from silence and buf first + { + long n = min( count, silence_count ); + silence_count -= n; + count -= n; + + n = min( count, buf_remain ); + buf_remain -= n; + count -= n; + } + + if ( count && !emu_track_ended_ ) + { + emu_time += count; + end_track_if_error( skip_( count ) ); + } + + if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended + track_ended_ |= emu_track_ended_; + + return 0; +} + +blargg_err_t Music_Emu::skip_( long count ) +{ + // for long skip, mute sound + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = mute_mask_; + mute_voices( ~0 ); + + while ( count > threshold / 2 && !emu_track_ended_ ) + { + RETURN_ERR( play_( buf_size, buf.begin() ) ); + count -= buf_size; + } + + mute_voices( saved_mute ); + } + + while ( count && !emu_track_ended_ ) + { + long n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( play_( n, buf.begin() ) ); + } + return 0; +} + +// Fading + +void Music_Emu::set_fade( long start_msec, long length_msec ) +{ + fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / stereo); + fade_start = msec_to_samples( start_msec ); +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( blargg_long x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void Music_Emu::handle_fade( long out_count, sample_t* out ) +{ + for ( int i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (out_time + i - fade_start) / fade_block_size, + fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + track_ended_ = emu_track_ended_ = true; + + sample_t* io = &out [i]; + for ( int count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = sample_t ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void Music_Emu::emu_play( long count, sample_t* out ) +{ + check( current_track_ >= 0 ); + emu_time += count; + if ( current_track_ >= 0 && !emu_track_ended_ ) + end_track_if_error( play_( count, out ) ); + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static long count_silence( Music_Emu::sample_t* begin, long size ) +{ + Music_Emu::sample_t first = *begin; + *begin = silence_threshold; // sentinel + Music_Emu::sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { } + *begin = first; + return size - long(p - begin); +} + +// fill internal buffer and check it for silence +void Music_Emu::fill_buf() +{ + assert( !buf_remain ); + if ( !emu_track_ended_ ) + { + emu_play( buf_size, buf.begin() ); + long silence = count_silence( buf.begin(), buf_size ); + if ( silence < buf_size ) + { + silence_time = emu_time - silence; + buf_remain = buf_size; + return; + } + } + silence_count += buf_size; +} + +blargg_err_t Music_Emu::play( long out_count, sample_t* out ) +{ + if ( track_ended_ ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + require( current_track() >= 0 ); + require( out_count % stereo == 0 ); + + assert( emu_time >= out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //dprintf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" ); + + long pos = 0; + if ( silence_count ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + long ahead_time = silence_lookahead * (out_time + out_count - silence_time) + silence_time; + while ( emu_time < ahead_time && !(buf_remain || emu_track_ended_) ) + fill_buf(); + + // fill with silence + pos = min( silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + silence_count -= pos; + + if ( emu_time - silence_time > silence_max * stereo * sample_rate() ) + { + track_ended_ = emu_track_ended_ = true; + silence_count = 0; + buf_remain = 0; + } + } + + if ( buf_remain ) + { + // empty silence buf + long n = min( buf_remain, out_count - pos ); + memcpy( &out [pos], buf.begin() + (buf_size - buf_remain), n * sizeof *out ); + buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + long remain = out_count - pos; + if ( remain ) + { + emu_play( remain, out + pos ); + track_ended_ |= emu_track_ended_; + + if ( !ignore_silence_ || out_time > fade_start ) + { + // check end for a new run of silence + long silence = count_silence( out + pos, remain ); + if ( silence < remain ) + silence_time = emu_time - silence; + + if ( emu_time - silence_time >= buf_size ) + fill_buf(); // cause silence detection on next play() + } + } + + if ( out_time > fade_start ) + handle_fade( out_count, out ); + } + out_time += out_count; + return 0; +} + +// Gme_Info_ + +blargg_err_t Gme_Info_::set_sample_rate_( long ) { return 0; } +void Gme_Info_::pre_load() { Gme_File::pre_load(); } // skip Music_Emu +void Gme_Info_::post_load_() { Gme_File::post_load_(); } // skip Music_Emu +void Gme_Info_::set_equalizer_( equalizer_t const& ){ check( false ); } +void Gme_Info_::mute_voices_( int ) { check( false ); } +void Gme_Info_::set_tempo_( double ) { } +blargg_err_t Gme_Info_::start_track_( int ) { return "Use full emulator for playback"; } +blargg_err_t Gme_Info_::play_( long, sample_t* ) { return "Use full emulator for playback"; } diff --git a/game-music-emu/gme/Music_Emu.h b/game-music-emu/gme/Music_Emu.h new file mode 100644 index 000000000..81e1e67cc --- /dev/null +++ b/game-music-emu/gme/Music_Emu.h @@ -0,0 +1,211 @@ +// Common interface to game music file emulators + +// Game_Music_Emu 0.5.2 +#ifndef MUSIC_EMU_H +#define MUSIC_EMU_H + +#include "Gme_File.h" +class Multi_Buffer; + +struct Music_Emu : public Gme_File { +public: +// Basic functionality (see Gme_File.h for file loading/track info functions) + + // Set output sample rate. Must be called only once before loading file. + blargg_err_t set_sample_rate( long sample_rate ); + + // Start a track, where 0 is the first track. Also clears warning string. + blargg_err_t start_track( int ); + + // Generate 'count' samples info 'buf'. Output is in stereo. Any emulation + // errors set warning string, and major errors also end track. + typedef short sample_t; + blargg_err_t play( long count, sample_t* buf ); + +// Informational + + // Sample rate sound is generated at + long sample_rate() const; + + // Index of current track or -1 if one hasn't been started + int current_track() const; + + // Number of voices used by currently loaded file + int voice_count() const; + + // Names of voices + const char** voice_names() const; + +// Track status/control + + // Number of milliseconds (1000 msec = 1 second) played since beginning of track + long tell() const; + + // Seek to new time in track. Seeking backwards or far forward can take a while. + blargg_err_t seek( long msec ); + + // Skip n samples + blargg_err_t skip( long n ); + + // True if a track has reached its end + bool track_ended() const; + + // Set start time and length of track fade out. Once fade ends track_ended() returns + // true. Fade time can be changed while track is playing. + void set_fade( long start_msec, long length_msec = 8000 ); + + // Disable automatic end-of-track detection and skipping of silence at beginning + void ignore_silence( bool disable = true ); + + // Info for current track + Gme_File::track_info; + blargg_err_t track_info( track_info_t* out ) const; + +// Sound customization + + // Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. + // Track length as returned by track_info() assumes a tempo of 1.0. + void set_tempo( double ); + + // Mute/unmute voice i, where voice 0 is first voice + void mute_voice( int index, bool mute = true ); + + // Set muting state of all voices at once using a bit mask, where -1 mutes them all, + // 0 unmutes them all, 0x01 mutes just the first voice, etc. + void mute_voices( int mask ); + + // Change overall output amplitude, where 1.0 results in minimal clamping. + // Must be called before set_sample_rate(). + void set_gain( double ); + + // Request use of custom multichannel buffer. Only supported by "classic" emulators; + // on others this has no effect. Should be called only once *before* set_sample_rate(). + virtual void set_buffer( Multi_Buffer* ) { } + +// Sound equalization (treble/bass) + + // Frequency equalizer parameters (see gme.txt) + // See gme.h for definition of struct gme_equalizer_t. + typedef gme_equalizer_t equalizer_t; + + // Current frequency equalizater parameters + equalizer_t const& equalizer() const; + + // Set frequency equalizer parameters + void set_equalizer( equalizer_t const& ); + + // Equalizer settings for TV speaker + static equalizer_t const tv_eq; + +public: + Music_Emu(); + ~Music_Emu(); +protected: + void set_max_initial_silence( int n ) { max_initial_silence = n; } + void set_silence_lookahead( int n ) { silence_lookahead = n; } + void set_voice_count( int n ) { voice_count_ = n; } + void set_voice_names( const char* const* names ); + void set_track_ended() { emu_track_ended_ = true; } + double gain() const { return gain_; } + double tempo() const { return tempo_; } + void remute_voices(); + + virtual blargg_err_t set_sample_rate_( long sample_rate ) = 0; + virtual void set_equalizer_( equalizer_t const& ) { }; + virtual void mute_voices_( int mask ) = 0; + virtual void set_tempo_( double ) = 0; + virtual blargg_err_t start_track_( int ) = 0; // tempo is set before this + virtual blargg_err_t play_( long count, sample_t* out ) = 0; + virtual blargg_err_t skip_( long count ); +protected: + virtual void unload(); + virtual void pre_load(); + virtual void post_load_(); +private: + // general + equalizer_t equalizer_; + int max_initial_silence; + const char** voice_names_; + int voice_count_; + int mute_mask_; + double tempo_; + double gain_; + + long sample_rate_; + blargg_long msec_to_samples( blargg_long msec ) const; + + // track-specific + int current_track_; + blargg_long out_time; // number of samples played since start of track + blargg_long emu_time; // number of samples emulator has generated since start of track + bool emu_track_ended_; // emulator has reached end of track + volatile bool track_ended_; + void clear_track_vars(); + void end_track_if_error( blargg_err_t ); + + // fading + blargg_long fade_start; + int fade_step; + void handle_fade( long count, sample_t* out ); + + // silence detection + int silence_lookahead; // speed to run emulator when looking ahead for silence + bool ignore_silence_; + long silence_time; // number of samples where most recent silence began + long silence_count; // number of samples of silence to play before using buf + long buf_remain; // number of samples left in silence buffer + enum { buf_size = 2048 }; + blargg_vector buf; + void fill_buf(); + void emu_play( long count, sample_t* out ); + + Multi_Buffer* effects_buffer; + friend GMEDLL Music_Emu* GMEAPI gme_new_emu( gme_type_t, int ); + friend GMEDLL void GMEAPI gme_set_stereo_depth( Music_Emu*, double ); +}; + +// base class for info-only derivations +struct Gme_Info_ : Music_Emu +{ + virtual blargg_err_t set_sample_rate_( long sample_rate ); + virtual void set_equalizer_( equalizer_t const& ); + virtual void mute_voices_( int mask ); + virtual void set_tempo_( double ); + virtual blargg_err_t start_track_( int ); + virtual blargg_err_t play_( long count, sample_t* out ); + virtual void pre_load(); + virtual void post_load_(); +}; + +inline blargg_err_t Music_Emu::track_info( track_info_t* out ) const +{ + return track_info( out, current_track_ ); +} + +inline long Music_Emu::sample_rate() const { return sample_rate_; } +inline const char** Music_Emu::voice_names() const { return voice_names_; } +inline int Music_Emu::voice_count() const { return voice_count_; } +inline int Music_Emu::current_track() const { return current_track_; } +inline bool Music_Emu::track_ended() const { return track_ended_; } +inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; } + +inline void Music_Emu::set_tempo_( double t ) { tempo_ = t; } +inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); } +inline void Music_Emu::ignore_silence( bool b ) { ignore_silence_ = b; } +inline blargg_err_t Music_Emu::start_track_( int ) { return 0; } + +inline void Music_Emu::set_voice_names( const char* const* names ) +{ + // Intentional removal of const, so users don't have to remember obscure const in middle + voice_names_ = const_cast (names); +} + +inline void Music_Emu::mute_voices_( int ) { } + +inline void Music_Emu::set_gain( double g ) +{ + assert( !sample_rate() ); // you must set gain before setting sample rate + gain_ = g; +} + +#endif diff --git a/game-music-emu/gme/Nes_Apu.cpp b/game-music-emu/gme/Nes_Apu.cpp new file mode 100644 index 000000000..8daf5d0e1 --- /dev/null +++ b/game-music-emu/gme/Nes_Apu.cpp @@ -0,0 +1,391 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const amp_range = 15; + +Nes_Apu::Nes_Apu() : + square1( &square_synth ), + square2( &square_synth ) +{ + tempo_ = 1.0; + dmc.apu = this; + dmc.prg_reader = NULL; + irq_notifier_ = NULL; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = ▵ + oscs [3] = &noise; + oscs [4] = &dmc; + + output( NULL ); + volume( 1.0 ); + reset( false ); +} + +void Nes_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + triangle.synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); + dmc.synth.treble_eq( eq ); +} + +void Nes_Apu::enable_nonlinear( double v ) +{ + dmc.nonlinear = true; + square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v ); + + const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); + triangle.synth.volume( 3.0 * tnd ); + noise.synth.volume( 2.0 * tnd ); + dmc.synth.volume( tnd ); + + square1 .last_amp = 0; + square2 .last_amp = 0; + triangle.last_amp = 0; + noise .last_amp = 0; + dmc .last_amp = 0; +} + +void Nes_Apu::volume( double v ) +{ + dmc.nonlinear = false; + square_synth.volume( 0.1128 / amp_range * v ); + triangle.synth.volume( 0.12765 / amp_range * v ); + noise.synth.volume( 0.0741 / amp_range * v ); + dmc.synth.volume( 0.42545 / 127 * v ); +} + +void Nes_Apu::output( Blip_Buffer* buffer ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buffer ); +} + +void Nes_Apu::set_tempo( double t ) +{ + tempo_ = t; + frame_period = (dmc.pal_mode ? 8314 : 7458); + if ( t != 1.0 ) + frame_period = (int) (frame_period / t) & ~1; // must be even +} + +void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) +{ + dmc.pal_mode = pal_mode; + set_tempo( tempo_ ); + + square1.reset(); + square2.reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); + + last_time = 0; + last_dmc_time = 0; + osc_enables = 0; + irq_flag = false; + earliest_irq_ = no_irq; + frame_delay = 1; + write_register( 0, 0x4017, 0x00 ); + write_register( 0, 0x4015, 0x00 ); + + for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ ) + write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); + + dmc.dac = initial_dmc_dac; + if ( !dmc.nonlinear ) + triangle.last_amp = 15; + if ( !dmc.nonlinear ) // TODO: remove? + dmc.last_amp = initial_dmc_dac; // prevent output transition +} + +void Nes_Apu::irq_changed() +{ + nes_time_t new_irq = dmc.next_irq; + if ( dmc.irq_flag | irq_flag ) { + new_irq = 0; + } + else if ( new_irq > next_irq ) { + new_irq = next_irq; + } + + if ( new_irq != earliest_irq_ ) { + earliest_irq_ = new_irq; + if ( irq_notifier_ ) + irq_notifier_( irq_data ); + } +} + +// frames + +void Nes_Apu::run_until( nes_time_t end_time ) +{ + require( end_time >= last_dmc_time ); + if ( end_time > next_dmc_read_time() ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } +} + +void Nes_Apu::run_until_( nes_time_t end_time ) +{ + require( end_time >= last_time ); + + if ( end_time == last_time ) + return; + + if ( last_dmc_time < end_time ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } + + while ( true ) + { + // earlier of next frame time or end time + nes_time_t time = last_time + frame_delay; + if ( time > end_time ) + time = end_time; + frame_delay -= time - last_time; + + // run oscs to present + square1.run( last_time, time ); + square2.run( last_time, time ); + triangle.run( last_time, time ); + noise.run( last_time, time ); + last_time = time; + + if ( time == end_time ) + break; // no more frames to run + + // take frame-specific actions + frame_delay = frame_period; + switch ( frame++ ) + { + case 0: + if ( !(frame_mode & 0xC0) ) { + next_irq = time + frame_period * 4 + 2; + irq_flag = true; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + square1.clock_length( 0x20 ); + square2.clock_length( 0x20 ); + noise.clock_length( 0x20 ); + triangle.clock_length( 0x80 ); // different bit for halt flag on triangle + + square1.clock_sweep( -1 ); + square2.clock_sweep( 0 ); + + // frame 2 is slightly shorter in mode 1 + if ( dmc.pal_mode && frame == 3 ) + frame_delay -= 2; + break; + + case 1: + // frame 1 is slightly shorter in mode 0 + if ( !dmc.pal_mode ) + frame_delay -= 2; + break; + + case 3: + frame = 0; + + // frame 3 is almost twice as long in mode 1 + if ( frame_mode & 0x80 ) + frame_delay += frame_period - (dmc.pal_mode ? 2 : 6); + break; + } + + // clock envelopes and linear counter every frame + triangle.clock_linear_counter(); + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } +} + +template +inline void zero_apu_osc( T* osc, nes_time_t time ) +{ + Blip_Buffer* output = osc->output; + int last_amp = osc->last_amp; + osc->last_amp = 0; + if ( output && last_amp ) + osc->synth.offset( time, -last_amp, output ); +} + +void Nes_Apu::end_frame( nes_time_t end_time ) +{ + if ( end_time > last_time ) + run_until_( end_time ); + + if ( dmc.nonlinear ) + { + zero_apu_osc( &square1, last_time ); + zero_apu_osc( &square2, last_time ); + zero_apu_osc( &triangle, last_time ); + zero_apu_osc( &noise, last_time ); + zero_apu_osc( &dmc, last_time ); + } + + // make times relative to new frame + last_time -= end_time; + require( last_time >= 0 ); + + last_dmc_time -= end_time; + require( last_dmc_time >= 0 ); + + if ( next_irq != no_irq ) { + next_irq -= end_time; + check( next_irq >= 0 ); + } + if ( dmc.next_irq != no_irq ) { + dmc.next_irq -= end_time; + check( dmc.next_irq >= 0 ); + } + if ( earliest_irq_ != no_irq ) { + earliest_irq_ -= end_time; + if ( earliest_irq_ < 0 ) + earliest_irq_ = 0; + } +} + +// registers + +static const unsigned char length_table [0x20] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) +{ + require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) + require( (unsigned) data <= 0xFF ); + + // Ignore addresses outside range + if ( unsigned (addr - start_addr) > end_addr - start_addr ) + return; + + run_until_( time ); + + if ( addr < 0x4014 ) + { + // Write to channel + int osc_index = (addr - start_addr) >> 2; + Nes_Osc* osc = oscs [osc_index]; + + int reg = addr & 3; + osc->regs [reg] = data; + osc->reg_written [reg] = true; + + if ( osc_index == 4 ) + { + // handle DMC specially + dmc.write_register( reg, data ); + } + else if ( reg == 3 ) + { + // load length counter + if ( (osc_enables >> osc_index) & 1 ) + osc->length_counter = length_table [(data >> 3) & 0x1F]; + + // reset square phase + if ( osc_index < 2 ) + ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; + } + } + else if ( addr == 0x4015 ) + { + // Channel enables + for ( int i = osc_count; i--; ) + if ( !((data >> i) & 1) ) + oscs [i]->length_counter = 0; + + bool recalc_irq = dmc.irq_flag; + dmc.irq_flag = false; + + int old_enables = osc_enables; + osc_enables = data; + if ( !(data & 0x10) ) { + dmc.next_irq = no_irq; + recalc_irq = true; + } + else if ( !(old_enables & 0x10) ) { + dmc.start(); // dmc just enabled + } + + if ( recalc_irq ) + irq_changed(); + } + else if ( addr == 0x4017 ) + { + // Frame mode + frame_mode = data; + + bool irq_enabled = !(data & 0x40); + irq_flag &= irq_enabled; + next_irq = no_irq; + + // mode 1 + frame_delay = (frame_delay & 1); + frame = 0; + + if ( !(data & 0x80) ) + { + // mode 0 + frame = 1; + frame_delay += frame_period; + if ( irq_enabled ) + next_irq = time + frame_delay + frame_period * 3 + 1; + } + + irq_changed(); + } +} + +int Nes_Apu::read_status( nes_time_t time ) +{ + run_until_( time - 1 ); + + int result = (dmc.irq_flag << 7) | (irq_flag << 6); + + for ( int i = 0; i < osc_count; i++ ) + if ( oscs [i]->length_counter ) + result |= 1 << i; + + run_until_( time ); + + if ( irq_flag ) + { + result |= 0x40; + irq_flag = false; + irq_changed(); + } + + //dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result ); + + return result; +} diff --git a/game-music-emu/gme/Nes_Apu.h b/game-music-emu/gme/Nes_Apu.h new file mode 100644 index 000000000..5e722248f --- /dev/null +++ b/game-music-emu/gme/Nes_Apu.h @@ -0,0 +1,179 @@ +// NES 2A03 APU sound chip emulator + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_APU_H +#define NES_APU_H + +#include "blargg_common.h" + +typedef blargg_long nes_time_t; // CPU clock cycle count +typedef unsigned nes_addr_t; // 16-bit memory address + +#include "Nes_Oscs.h" + +struct apu_state_t; +class Nes_Buffer; + +class Nes_Apu { +public: + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Set memory reader callback used by DMC oscillator to fetch samples. + // When callback is invoked, 'user_data' is passed unchanged as the + // first parameter. + void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL ); + + // All time values are the number of CPU clock cycles relative to the + // beginning of the current time frame. Before resetting the CPU clock + // count, call end_frame( last_cpu_time ). + + // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) + enum { start_addr = 0x4000 }; + enum { end_addr = 0x4017 }; + void write_register( nes_time_t, nes_addr_t, int data ); + + // Read from status register at 0x4015 + enum { status_addr = 0x4015 }; + int read_status( nes_time_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new time frame at time 0. Time frames have no effect on emulation + // and each can be whatever length is convenient. + void end_frame( nes_time_t ); + +// Additional optional features (can be ignored without any problem) + + // Reset internal frame counter, registers, and all oscillators. + // Use PAL timing if pal_timing is true, otherwise use NTSC timing. + // Set the DMC oscillator's initial DAC value to initial_dmc_dac without + // any audible click. + void reset( bool pal_mode = false, int initial_dmc_dac = 0 ); + + // Adjust frame period + void set_tempo( double ); + + // Save/load exact emulation state + void save_state( apu_state_t* out ) const; + void load_state( apu_state_t const& ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see notes.txt) + void treble_eq( const blip_eq_t& ); + + // Set sound output of specific oscillator to buffer. If buffer is NULL, + // the specified oscillator is muted and emulation accuracy is reduced. + // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, + // 2) Triangle, 3) Noise, 4) DMC. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* buffer ); + + // Set IRQ time callback that is invoked when the time of earliest IRQ + // may have changed, or NULL to disable. When callback is invoked, + // 'user_data' is passed unchanged as the first parameter. + void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); + + // Get time that APU-generated IRQ will occur if no further register reads + // or writes occur. If IRQ is already pending, returns irq_waiting. If no + // IRQ will occur, returns no_irq. + enum { no_irq = INT_MAX / 2 + 1 }; + enum { irq_waiting = 0 }; + nes_time_t earliest_irq( nes_time_t ) const; + + // Count number of DMC reads that would occur if 'run_until( t )' were executed. + // If last_read is not NULL, set *last_read to the earliest time that + // 'count_dmc_reads( time )' would result in the same result. + int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; + + // Time when next DMC memory read will occur + nes_time_t next_dmc_read_time() const; + + // Run DMC until specified time, so that any DMC memory reads can be + // accounted for (i.e. inserting CPU wait states). + void run_until( nes_time_t ); + +public: + Nes_Apu(); + BLARGG_DISABLE_NOTHROW +private: + friend class Nes_Nonlinearizer; + void enable_nonlinear( double volume ); + static double nonlinear_tnd_gain() { return 0.75; } +private: + friend struct Nes_Dmc; + + // noncopyable + Nes_Apu( const Nes_Apu& ); + Nes_Apu& operator = ( const Nes_Apu& ); + + Nes_Osc* oscs [osc_count]; + Nes_Square square1; + Nes_Square square2; + Nes_Noise noise; + Nes_Triangle triangle; + Nes_Dmc dmc; + + double tempo_; + nes_time_t last_time; // has been run until this time in current frame + nes_time_t last_dmc_time; + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int osc_enables; + int frame_mode; + bool irq_flag; + void (*irq_notifier_)( void* user_data ); + void* irq_data; + Nes_Square::Synth square_synth; // shared by squares + + void irq_changed(); + void state_restored(); + void run_until_( nes_time_t ); + + // TODO: remove + friend class Nes_Core; +}; + +inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) +{ + assert( (unsigned) osc < osc_count ); + oscs [osc]->output = buf; +} + +inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const +{ + return earliest_irq_; +} + +inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) +{ + dmc.prg_reader_data = user_data; + dmc.prg_reader = func; +} + +inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) +{ + irq_notifier_ = func; + irq_data = user_data; +} + +inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const +{ + return dmc.count_reads( time, last_read ); +} + +inline nes_time_t Nes_Dmc::next_read_time() const +{ + if ( length_counter == 0 ) + return Nes_Apu::no_irq; // not reading + + return apu->last_dmc_time + delay + long (bits_remain - 1) * period; +} + +inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } + +#endif diff --git a/game-music-emu/gme/Nes_Cpu.cpp b/game-music-emu/gme/Nes_Cpu.cpp new file mode 100644 index 000000000..480b4aa48 --- /dev/null +++ b/game-music-emu/gme/Nes_Cpu.cpp @@ -0,0 +1,1084 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Nes_Cpu.h" + +#include "blargg_endian.h" +#include + +#define BLARGG_CPU_X86 1 + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#define FLUSH_TIME() (void) (s.time = s_time) +#define CACHE_TIME() (void) (s_time = s.time) + +#include "nes_cpu_io.h" + +#include "blargg_source.h" + +#ifndef CPU_DONE + #define CPU_DONE( cpu, time, result_out ) { result_out = -1; } +#endif + +#ifndef CPU_READ_PPU + #define CPU_READ_PPU( cpu, addr, out, time )\ + {\ + FLUSH_TIME();\ + out = CPU_READ( cpu, addr, time );\ + CACHE_TIME();\ + } +#endif + +#if BLARGG_NONPORTABLE + #define PAGE_OFFSET( addr ) (addr) +#else + #define PAGE_OFFSET( addr ) ((addr) & (page_size - 1)) +#endif + +inline void Nes_Cpu::set_code_page( int i, void const* p ) +{ + state->code_map [i] = (uint8_t const*) p - PAGE_OFFSET( i * page_size ); +} + +int const st_n = 0x80; +int const st_v = 0x40; +int const st_r = 0x20; +int const st_b = 0x10; +int const st_d = 0x08; +int const st_i = 0x04; +int const st_z = 0x02; +int const st_c = 0x01; + +void Nes_Cpu::reset( void const* unmapped_page ) +{ + check( state == &state_ ); + state = &state_; + r.status = st_i; + r.sp = 0xFF; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + state_.time = 0; + state_.base = 0; + irq_time_ = future_nes_time; + end_time_ = future_nes_time; + error_count_ = 0; + + assert( page_size == 0x800 ); // assumes this + set_code_page( page_count, unmapped_page ); + map_code( 0x2000, 0xE000, unmapped_page, true ); + map_code( 0x0000, 0x2000, low_mem, true ); + + blargg_verify_byte_order(); +} + +void Nes_Cpu::map_code( nes_addr_t start, unsigned size, void const* data, bool mirror ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 ); + + unsigned page = start / page_size; + for ( unsigned n = size / page_size; n; --n ) + { + set_code_page( page++, data ); + if ( !mirror ) + data = (char const*) data + page_size; + } +} + +#define TIME (s_time + s.base) +#define READ_LIKELY_PPU( addr, out ) {CPU_READ_PPU( this, (addr), out, TIME );} +#define READ( addr ) CPU_READ( this, (addr), TIME ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );} +#define READ_LOW( addr ) (low_mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) +#define READ_PROG( addr ) (s.code_map [(addr) >> page_bits] [PAGE_OFFSET( addr )]) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; + +bool Nes_Cpu::run( nes_time_t end_time ) +{ + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + // even on x86, using s.time in place of s_time was slower + fint16 s_time = s.time; + + // registers + fuint16 pc = r.pc; + fuint8 a = r.a; + fuint8 x = r.x; + fuint8 y = r.y; + fuint16 sp; + SET_SP( r.sp ); + + // status flags + #define IS_NEG (nz & 0x8080) + + #define CALC_STATUS( out ) do {\ + out = status & (st_v | st_d | st_i);\ + out |= ((nz >> 8) | nz) & st_n;\ + out |= c >> 8 & st_c;\ + if ( !(nz & 0xFF) ) out |= st_z;\ + } while ( 0 ) + + #define SET_STATUS( in ) do {\ + status = in & (st_v | st_d | st_i);\ + nz = in << 8;\ + c = nz;\ + nz |= ~in & st_z;\ + } while ( 0 ) + + fuint8 status; + fuint16 c; // carry set if (c & 0x100) != 0 + fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + fuint8 temp = r.status; + SET_STATUS( temp ); + } + + goto loop; +dec_clock_loop: + s_time--; +loop: + + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) pc < 0x10000 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + check( -32768 <= s_time && s_time < 32767 ); + + uint8_t const* instr = s.code_map [pc >> page_bits]; + fuint8 opcode; + + // TODO: eliminate this special case + #if BLARGG_NONPORTABLE + opcode = instr [pc]; + pc++; + instr += pc; + #else + instr += PAGE_OFFSET( pc ); + opcode = *instr++; + pc++; + #endif + + static uint8_t const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 // F + }; // 0x00 was 7 and 0xF2 was 2 + + fuint16 data; + +#if !BLARGG_CPU_X86 + if ( s_time >= 0 ) + goto out_of_time; + s_time += clock_table [opcode]; + + data = *instr; + + switch ( opcode ) + { +#else + + data = clock_table [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = *instr; + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; +#endif + +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE() (pc++, data += 0x100 * GET_MSB()) +#define GET_ADDR() GET_LE16( instr ) + +#define NO_PAGE_CROSSING( lsb ) +#define HANDLE_PAGE_CROSSING( lsb ) s_time += (lsb) >> 8; + +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + +#define IND_Y( cross, out ) {\ + fuint16 temp = READ_LOW( data ) + y;\ + out = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\ + cross( temp );\ + } + +#define IND_X( out ) {\ + fuint16 temp = data + x;\ + out = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) );\ + } + +#define ARITH_ADDR_MODES( op )\ +case op - 0x04: /* (ind,x) */\ + IND_X( data )\ + goto ptr##op;\ +case op + 0x0C: /* (ind),y */\ + IND_Y( HANDLE_PAGE_CROSSING, data )\ + goto ptr##op;\ +case op + 0x10: /* zp,X */\ + data = uint8_t (data + x);\ +case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ +case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ +case op + 0x18: /* abs,X */\ + data += x;\ +ind##op:\ + HANDLE_PAGE_CROSSING( data );\ +case op + 0x08: /* abs */\ + ADD_PAGE();\ +ptr##op:\ + FLUSH_TIME();\ + data = READ( data );\ + CACHE_TIME();\ +case op + 0x04: /* imm */\ +imm##op: + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + fint16 offset = (BOOST::int8_t) data;\ + fuint16 extra_clock = (++pc & 0xFF) + offset;\ + if ( !(cond) ) goto dec_clock_loop;\ + pc = BOOST::uint16_t (pc + offset);\ + s_time += extra_clock >> 8 & 1;\ + goto loop;\ +} + +// Often-Used + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( uint8_t (data + x) ); + pc++; + goto loop; + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x20: { // JSR + fuint16 temp = pc + 1; + pc = GET_ADDR(); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0xE8: // INX + INC_DEC_XY( x, 1 ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ); + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: // INY + INC_DEC_XY( y, 1 ) + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAD:{// LDA abs + unsigned addr = GET_ADDR(); + pc += 2; + READ_LIKELY_PPU( addr, nz ); + a = nz; + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_LOW( sp ); + pc += 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); + sp = (sp - 0xFE) | 0x100; + goto loop; + + { + fuint16 addr; + + case 0x99: // STA abs,Y + addr = y + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x8D: // STA abs + addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x9D: // STA abs,X (slightly more common than STA abs) + addr = x + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + sta_ptr: + FLUSH_TIME(); + WRITE( addr, a ); + CACHE_TIME(); + goto loop; + + case 0x91: // STA (ind),Y + IND_Y( NO_PAGE_CROSSING, addr ) + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X( addr ) + pc++; + goto sta_ptr; + + } + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + + // common read instructions + { + fuint16 addr; + + case 0xA1: // LDA (ind,X) + IND_X( addr ) + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + HANDLE_PAGE_CROSSING( addr ); + addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); + pc++; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + HANDLE_PAGE_CROSSING( data + y ); + addr = GET_ADDR() + y; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xBD: // LDA abs,X + HANDLE_PAGE_CROSSING( data + x ); + addr = GET_ADDR() + x; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + a_nz_read_addr: + FLUSH_TIME(); + a = nz = READ( addr ); + CACHE_TIME(); + goto loop; + + } + +// Branch + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + x = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + { + fuint8 temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + unsigned addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, temp ); + goto loop; + } + FLUSH_TIME(); + WRITE( addr, temp ); + CACHE_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + unsigned addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc += 2; + status &= ~st_v; + READ_LIKELY_PPU( addr, nz ); + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + fint16 carry = c >> 8 & 1; + fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + status &= ~st_v; + status |= ov >> 2 & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + fint16 temp = c >> 8 & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE(); + FLUSH_TIME(); + int temp = READ( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE(); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz |= (c = READ( data ) << 1); + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: // DEX + INC_DEC_XY( x, -1 ) + + case 0x88: // DEY + INC_DEC_XY( y, -1 ) + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = (unsigned) -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = (unsigned) -1; + inc_common: + FLUSH_TIME(); + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xAA: // TAX + x = a; + nz = a; + goto loop; + + case 0x8A: // TXA + a = x; + nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + goto loop; + + case 0x40:{// RTI + fuint8 temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xFF) ); + pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; + sp = (sp - 0xFD) | 0x100; + data = status; + SET_STATUS( temp ); + if ( !((data ^ status) & st_i) ) goto loop; // I flag didn't change + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) goto loop; + if ( status & st_i ) goto loop; + s_time += delta; + s.base = irq_time_; + goto loop; + } + + case 0x28:{// PLP + fuint8 temp = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + fuint8 changed = status ^ temp; + SET_STATUS( temp ); + if ( !(changed & st_i) ) + goto loop; // I flag didn't change + if ( status & st_i ) + goto handle_sei; + goto handle_cli; + } + + case 0x08: { // PHP + fuint8 temp; + CALC_STATUS( temp ); + PUSH( temp | (st_b | st_r) ); + goto loop; + } + + case 0x6C:{// JMP (ind) + data = GET_ADDR(); + check( unsigned (data - 0x2000) >= 0x4000 ); // ensure it's outside I/O space + uint8_t const* page = s.code_map [data >> page_bits]; + pc = page [PAGE_OFFSET( data )]; + data = (data & 0xFF00) | ((data + 1) & 0xFF); + pc |= page [PAGE_OFFSET( data )] << 8; + goto loop; + } + + case 0x00: // BRK + goto handle_brk; + +// Flags + + case 0x38: // SEC + c = (unsigned) ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: { + //dprintf( "CLI at %d\n", TIME ); + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) + { + if ( TIME < irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + s.base += s_time + 1; + s_time = -1; + goto loop; + } + + // TODO: implement + delayed_cli: + dprintf( "Delayed CLI not emulated\n" ); + goto loop; + } + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + dprintf( "Delayed SEI not emulated\n" ); + goto loop; + } + +// Unofficial + + // SKW - Skip word + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: + HANDLE_PAGE_CROSSING( data + x ); + case 0x0C: + pc++; + // SKB - Skip byte + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + goto loop; + + // NOP + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: + goto loop; + + case bad_opcode: // HLT + pc--; + if ( pc > 0xFFFF ) + { + // handle wrap-around (assumes caller has put page of HLT at 0x10000) + pc &= 0xFFFF; + goto loop; + } + case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: + case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: + goto stop; + +// Unimplemented + + case 0xFF: // force 256-entry jump table for optimization purposes + c |= 1; + default: + check( (unsigned) opcode <= 0xFF ); + // skip over proper number of bytes + static unsigned char const illop_lens [8] = { + 0x40, 0x40, 0x40, 0x80, 0x40, 0x40, 0x80, 0xA0 + }; + fuint8 opcode = instr [-1]; + fint16 len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + if ( opcode == 0x9C ) + len = 2; + pc += len; + error_count_++; + + if ( (opcode >> 4) == 0x0B ) + { + if ( opcode == 0xB3 ) + data = READ_LOW( data ); + if ( opcode != 0xB7 ) + HANDLE_PAGE_CROSSING( data + y ); + } + goto loop; + } + assert( false ); + + int result_; +handle_brk: + pc++; + result_ = 4; + +interrupt: + { + s_time += 7; + + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + pc = GET_LE16( &READ_PROG( 0xFFFA ) + result_ ); + + sp = (sp - 3) | 0x100; + fuint8 temp; + CALC_STATUS( temp ); + temp |= st_r; + if ( result_ ) + temp |= st_b; // TODO: incorrectly sets B flag for IRQ + WRITE_LOW( sp, temp ); + + this->r.status = status |= st_i; + blargg_long delta = s.base - end_time_; + if ( delta >= 0 ) goto loop; + s_time += delta; + s.base = end_time_; + goto loop; + } + +out_of_time: + pc--; + FLUSH_TIME(); + CPU_DONE( this, TIME, result_ ); + CACHE_TIME(); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + +stop: + + s.time = s_time; + + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + + { + fuint8 temp; + CALC_STATUS( temp ); + r.status = temp; + } + + this->state_ = s; + this->state = &this->state_; + + return s_time < 0; +} + diff --git a/game-music-emu/gme/Nes_Cpu.h b/game-music-emu/gme/Nes_Cpu.h new file mode 100644 index 000000000..94286708d --- /dev/null +++ b/game-music-emu/gme/Nes_Cpu.h @@ -0,0 +1,114 @@ +// NES 6502 CPU emulator + +// Game_Music_Emu 0.5.2 +#ifndef NES_CPU_H +#define NES_CPU_H + +#include "blargg_common.h" + +typedef blargg_long nes_time_t; // clock cycle count +typedef unsigned nes_addr_t; // 16-bit address +enum { future_nes_time = INT_MAX / 2 + 1 }; + +class Nes_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + // Clear registers, map low memory and its three mirrors to address 0, + // and mirror unmapped_page in remaining memory + void reset( void const* unmapped_page = 0 ); + + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. If mirror is true, repeats code page + // throughout address range. + enum { page_size = 0x800 }; + void map_code( nes_addr_t start, unsigned size, void const* code, bool mirror = false ); + + // Access emulated memory as CPU does + uint8_t const* get_code( nes_addr_t ); + + // 2KB of RAM at address 0 + uint8_t low_mem [0x800]; + + // NES 6502 registers. Not kept updated during a call to run(). + struct registers_t { + BOOST::uint16_t pc; + BOOST::uint8_t a; + BOOST::uint8_t x; + BOOST::uint8_t y; + BOOST::uint8_t status; + BOOST::uint8_t sp; + }; + registers_t r; + + // Set end_time and run CPU from current time. Returns true if execution + // stopped due to encountering bad_opcode. + bool run( nes_time_t end_time ); + + // Time of beginning of next instruction to be executed + nes_time_t time() const { return state->time + state->base; } + void set_time( nes_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + nes_time_t irq_time() const { return irq_time_; } + void set_irq_time( nes_time_t ); + + nes_time_t end_time() const { return end_time_; } + void set_end_time( nes_time_t ); + + // Number of undefined instructions encountered and skipped + void clear_error_count() { error_count_ = 0; } + unsigned long error_count() const { return error_count_; } + + // CPU invokes bad opcode handler if it encounters this + enum { bad_opcode = 0xF2 }; + +public: + Nes_Cpu() { state = &state_; } + enum { page_bits = 11 }; + enum { page_count = 0x10000 >> page_bits }; + enum { irq_inhibit = 0x04 }; +private: + struct state_t { + uint8_t const* code_map [page_count + 1]; + nes_time_t base; + int time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + nes_time_t irq_time_; + nes_time_t end_time_; + unsigned long error_count_; + + void set_code_page( int, void const* ); + inline int update_end_time( nes_time_t end, nes_time_t irq ); +}; + +inline BOOST::uint8_t const* Nes_Cpu::get_code( nes_addr_t addr ) +{ + return state->code_map [addr >> page_bits] + addr + #if !BLARGG_NONPORTABLE + % (unsigned) page_size + #endif + ; +} + +inline int Nes_Cpu::update_end_time( nes_time_t t, nes_time_t irq ) +{ + if ( irq < t && !(r.status & irq_inhibit) ) t = irq; + int delta = state->base - t; + state->base = t; + return delta; +} + +inline void Nes_Cpu::set_irq_time( nes_time_t t ) +{ + state->time += update_end_time( end_time_, (irq_time_ = t) ); +} + +inline void Nes_Cpu::set_end_time( nes_time_t t ) +{ + state->time += update_end_time( (end_time_ = t), irq_time_ ); +} + +#endif diff --git a/game-music-emu/gme/Nes_Fme7_Apu.cpp b/game-music-emu/gme/Nes_Fme7_Apu.cpp new file mode 100644 index 000000000..c058f6b1f --- /dev/null +++ b/game-music-emu/gme/Nes_Fme7_Apu.cpp @@ -0,0 +1,121 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Nes_Fme7_Apu.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Nes_Fme7_Apu::reset() +{ + last_time = 0; + + for ( int i = 0; i < osc_count; i++ ) + oscs [i].last_amp = 0; + + fme7_apu_state_t* state = this; + memset( state, 0, sizeof *state ); +} + +unsigned char const Nes_Fme7_Apu::amp_table [16] = +{ + #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), + ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), + ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), + ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) + #undef ENTRY +}; + +void Nes_Fme7_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); + + for ( int index = 0; index < osc_count; index++ ) + { + int mode = regs [7] >> index; + int vol_mode = regs [010 + index]; + int volume = amp_table [vol_mode & 0x0F]; + + Blip_Buffer* const osc_output = oscs [index].output; + if ( !osc_output ) + continue; + osc_output->set_modified(); + + // check for unsupported mode + #ifndef NDEBUG + if ( (mode & 011) <= 001 && vol_mode & 0x1F ) + dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n", + mode, vol_mode & 0x1F ); + #endif + + if ( (mode & 001) | (vol_mode & 0x10) ) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor + + regs [index * 2] * period_factor; + if ( period < 50 ) // around 22 kHz + { + volume = 0; + if ( !period ) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if ( !phases [index] ) + amp = 0; + { + int delta = amp - oscs [index].last_amp; + if ( delta ) + { + oscs [index].last_amp = amp; + synth.offset( last_time, delta, osc_output ); + } + } + + blip_time_t time = last_time + delays [index]; + if ( time < end_time ) + { + int delta = amp * 2 - volume; + if ( volume ) + { + do + { + delta = -delta; + synth.offset_inline( time, delta, osc_output ); + time += period; + } + while ( time < end_time ); + + oscs [index].last_amp = (delta + volume) >> 1; + phases [index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + phases [index] ^= count & 1; + time += (blargg_long) count * period; + } + } + + delays [index] = time - end_time; + } + + last_time = end_time; +} + diff --git a/game-music-emu/gme/Nes_Fme7_Apu.h b/game-music-emu/gme/Nes_Fme7_Apu.h new file mode 100644 index 000000000..eb60af038 --- /dev/null +++ b/game-music-emu/gme/Nes_Fme7_Apu.h @@ -0,0 +1,131 @@ +// Sunsoft FME-7 sound emulator + +// Game_Music_Emu 0.5.2 +#ifndef NES_FME7_APU_H +#define NES_FME7_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct fme7_apu_state_t +{ + enum { reg_count = 14 }; + BOOST::uint8_t regs [reg_count]; + BOOST::uint8_t phases [3]; // 0 or 1 + BOOST::uint8_t latch; + BOOST::uint16_t delays [3]; // a, b, c +}; + +class Nes_Fme7_Apu : private fme7_apu_state_t { +public: + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_state( fme7_apu_state_t* ) const; + void load_state( fme7_apu_state_t const& ); + + // Mask and addresses of registers + enum { addr_mask = 0xE000 }; + enum { data_addr = 0xE000 }; + enum { latch_addr = 0xC000 }; + + // (addr & addr_mask) == latch_addr + void write_latch( int ); + + // (addr & addr_mask) == data_addr + void write_data( blip_time_t, int data ); + +public: + Nes_Fme7_Apu(); + BLARGG_DISABLE_NOTHROW +private: + // noncopyable + Nes_Fme7_Apu( const Nes_Fme7_Apu& ); + Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); + + static unsigned char const amp_table [16]; + + struct { + Blip_Buffer* output; + int last_amp; + } oscs [osc_count]; + blip_time_t last_time; + + enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff + Blip_Synth synth; + + void run_until( blip_time_t ); +}; + +inline void Nes_Fme7_Apu::volume( double v ) +{ + synth.volume( 0.38 / amp_range * v ); // to do: fine-tune +} + +inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq ) +{ + synth.treble_eq( eq ); +} + +inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Fme7_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +inline Nes_Fme7_Apu::Nes_Fme7_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; } + +inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data ) +{ + if ( (unsigned) latch >= reg_count ) + { + #ifdef dprintf + dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); + #endif + return; + } + + run_until( time ); + regs [latch] = data; +} + +inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const +{ + *out = *this; +} + +inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) +{ + reset(); + fme7_apu_state_t* state = this; + *state = in; +} + +#endif diff --git a/game-music-emu/gme/Nes_Namco_Apu.cpp b/game-music-emu/gme/Nes_Namco_Apu.cpp new file mode 100644 index 000000000..f3235b383 --- /dev/null +++ b/game-music-emu/gme/Nes_Namco_Apu.cpp @@ -0,0 +1,145 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "Nes_Namco_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Nes_Namco_Apu::Nes_Namco_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +void Nes_Namco_Apu::reset() +{ + last_time = 0; + addr_reg = 0; + + int i; + for ( i = 0; i < reg_count; i++ ) + reg [i] = 0; + + for ( i = 0; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + osc.delay = 0; + osc.last_amp = 0; + osc.wave_pos = 0; + } +} + +void Nes_Namco_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +/* +void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) +{ + reflect_int16( data, BLARGG_4CHAR('A','D','D','R'), &addr_reg ); + + static const char hex [17] = "0123456789ABCDEF"; + int i; + for ( i = 0; i < reg_count; i++ ) + reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); + + for ( i = 0; i < osc_count; i++ ) + { + reflect_int32( data, BLARGG_4CHAR('D','L','Y','0') + i, &oscs [i].delay ); + reflect_int16( data, BLARGG_4CHAR('P','O','S','0') + i, &oscs [i].wave_pos ); + } +} +*/ + +void Nes_Namco_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +void Nes_Namco_Apu::run_until( blip_time_t nes_end_time ) +{ + int active_oscs = (reg [0x7F] >> 4 & 7) + 1; + for ( int i = osc_count - active_oscs; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + Blip_Buffer* output = osc.output; + if ( !output ) + continue; + output->set_modified(); + + blip_resampled_time_t time = + output->resampled_time( last_time ) + osc.delay; + blip_resampled_time_t end_time = output->resampled_time( nes_end_time ); + osc.delay = 0; + if ( time < end_time ) + { + const BOOST::uint8_t* osc_reg = ® [i * 8 + 0x40]; + if ( !(osc_reg [4] & 0xE0) ) + continue; + + int volume = osc_reg [7] & 15; + if ( !volume ) + continue; + + blargg_long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + if ( freq < 64 * active_oscs ) + continue; // prevent low frequencies from excessively delaying freq changes + blip_resampled_time_t period = + output->resampled_duration( 983040 ) / freq * active_oscs; + + int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; + if ( !wave_size ) + continue; + + int last_amp = osc.last_amp; + int wave_pos = osc.wave_pos; + + do + { + // read wave sample + int addr = wave_pos + osc_reg [6]; + int sample = reg [addr >> 1] >> (addr << 2 & 4); + wave_pos++; + sample = (sample & 15) * volume; + + // output impulse if amplitude changed + int delta = sample - last_amp; + if ( delta ) + { + last_amp = sample; + synth.offset_resampled( time, delta, output ); + } + + // next sample + time += period; + if ( wave_pos >= wave_size ) + wave_pos = 0; + } + while ( time < end_time ); + + osc.wave_pos = wave_pos; + osc.last_amp = last_amp; + } + osc.delay = time - end_time; + } + + last_time = nes_end_time; +} + diff --git a/game-music-emu/gme/Nes_Namco_Apu.h b/game-music-emu/gme/Nes_Namco_Apu.h new file mode 100644 index 000000000..db5fea4bf --- /dev/null +++ b/game-music-emu/gme/Nes_Namco_Apu.h @@ -0,0 +1,102 @@ +// Namco 106 sound chip emulator + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_NAMCO_APU_H +#define NES_NAMCO_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct namco_state_t; + +class Nes_Namco_Apu { +public: + // See Nes_Apu.h for reference. + void volume( double ); + void treble_eq( const blip_eq_t& ); + void output( Blip_Buffer* ); + enum { osc_count = 8 }; + void osc_output( int index, Blip_Buffer* ); + void reset(); + void end_frame( blip_time_t ); + + // Read/write data register is at 0x4800 + enum { data_reg_addr = 0x4800 }; + void write_data( blip_time_t, int ); + int read_data(); + + // Write-only address register is at 0xF800 + enum { addr_reg_addr = 0xF800 }; + void write_addr( int ); + + // to do: implement save/restore + void save_state( namco_state_t* out ) const; + void load_state( namco_state_t const& ); + +public: + Nes_Namco_Apu(); + BLARGG_DISABLE_NOTHROW +private: + // noncopyable + Nes_Namco_Apu( const Nes_Namco_Apu& ); + Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); + + struct Namco_Osc { + blargg_long delay; + Blip_Buffer* output; + short last_amp; + short wave_pos; + }; + + Namco_Osc oscs [osc_count]; + + blip_time_t last_time; + int addr_reg; + + enum { reg_count = 0x80 }; + BOOST::uint8_t reg [reg_count]; + Blip_Synth synth; + + BOOST::uint8_t& access(); + void run_until( blip_time_t ); +}; +/* +struct namco_state_t +{ + BOOST::uint8_t regs [0x80]; + BOOST::uint8_t addr; + BOOST::uint8_t unused; + BOOST::uint8_t positions [8]; + BOOST::uint32_t delays [8]; +}; +*/ + +inline BOOST::uint8_t& Nes_Namco_Apu::access() +{ + int addr = addr_reg & 0x7F; + if ( addr_reg & 0x80 ) + addr_reg = (addr + 1) | 0x80; + return reg [addr]; +} + +inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); } + +inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } + +inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; } + +inline int Nes_Namco_Apu::read_data() { return access(); } + +inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Namco_Apu::write_data( blip_time_t time, int data ) +{ + run_until( time ); + access() = data; +} + +#endif diff --git a/game-music-emu/gme/Nes_Oscs.cpp b/game-music-emu/gme/Nes_Oscs.cpp new file mode 100644 index 000000000..1ad3f59c0 --- /dev/null +++ b/game-music-emu/gme/Nes_Oscs.cpp @@ -0,0 +1,551 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Nes_Osc + +void Nes_Osc::clock_length( int halt_mask ) +{ + if ( length_counter && !(regs [0] & halt_mask) ) + length_counter--; +} + +void Nes_Envelope::clock_envelope() +{ + int period = regs [0] & 15; + if ( reg_written [3] ) { + reg_written [3] = false; + env_delay = period; + envelope = 15; + } + else if ( --env_delay < 0 ) { + env_delay = period; + if ( envelope | (regs [0] & 0x20) ) + envelope = (envelope - 1) & 15; + } +} + +int Nes_Envelope::volume() const +{ + return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope; +} + +// Nes_Square + +void Nes_Square::clock_sweep( int negative_adjust ) +{ + int sweep = regs [1]; + + if ( --sweep_delay < 0 ) + { + reg_written [1] = true; + + int period = this->period(); + int shift = sweep & shift_mask; + if ( shift && (sweep & 0x80) && period >= 8 ) + { + int offset = period >> shift; + + if ( sweep & negate_flag ) + offset = negative_adjust - offset; + + if ( period + offset < 0x800 ) + { + period += offset; + // rewrite period + regs [2] = period & 0xFF; + regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); + } + } + } + + if ( reg_written [1] ) { + reg_written [1] = false; + sweep_delay = (sweep >> 4) & 7; + } +} + +// TODO: clean up +inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + nes_time_t remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (blargg_long) count * timer_period; + } + return time; +} + +void Nes_Square::run( nes_time_t time, nes_time_t end_time ) +{ + const int period = this->period(); + const int timer_period = (period + 1) * 2; + + if ( !output ) + { + delay = maintain_phase( time + delay, end_time, timer_period ) - end_time; + return; + } + + output->set_modified(); + + int offset = period >> (regs [1] & shift_mask); + if ( regs [1] & negate_flag ) + offset = 0; + + const int volume = this->volume(); + if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) + { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + + time += delay; + time = maintain_phase( time, end_time, timer_period ); + } + else + { + // handle duty select + int duty_select = (regs [0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if ( duty_select == 3 ) { + duty = 2; // negated 25% + amp = volume; + } + if ( phase < duty ) + amp ^= volume; + + { + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const Synth& synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (phase_range - 1); + if ( phase == 0 || phase == duty ) { + delta = -delta; + synth.offset_inline( time, delta, output ); + } + time += timer_period; + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + delay = time - end_time; +} + +// Nes_Triangle + +void Nes_Triangle::clock_linear_counter() +{ + if ( reg_written [3] ) + linear_counter = regs [0] & 0x7F; + else if ( linear_counter ) + linear_counter--; + + if ( !(regs [0] & 0x80) ) + reg_written [3] = false; +} + +inline int Nes_Triangle::calc_amp() const +{ + int amp = phase_range - phase; + if ( amp < 0 ) + amp = phase - (phase_range + 1); + return amp; +} + +// TODO: clean up +inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + nes_time_t remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1); + phase++; + time += (blargg_long) count * timer_period; + } + return time; +} + +void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) +{ + const int timer_period = period() + 1; + if ( !output ) + { + time += delay; + delay = 0; + if ( length_counter && linear_counter && timer_period >= 3 ) + delay = maintain_phase( time, end_time, timer_period ) - end_time; + return; + } + + output->set_modified(); + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + int delta = update_amp( calc_amp() ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) + { + time = end_time; + } + else if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + + int phase = this->phase; + int volume = 1; + if ( phase > phase_range ) { + phase -= phase_range; + volume = -volume; + } + + do { + if ( --phase == 0 ) { + phase = phase_range; + volume = -volume; + } + else { + synth.offset_inline( time, volume, output ); + } + + time += timer_period; + } + while ( time < end_time ); + + if ( volume < 0 ) + phase += phase_range; + this->phase = phase; + last_amp = calc_amp(); + } + delay = time - end_time; +} + +// Nes_Dmc + +void Nes_Dmc::reset() +{ + address = 0; + dac = 0; + buf = 0; + bits_remain = 1; + bits = 0; + buf_full = false; + silence = true; + next_irq = Nes_Apu::no_irq; + irq_flag = false; + irq_enabled = false; + + Nes_Osc::reset(); + period = 0x1AC; +} + +void Nes_Dmc::recalc_irq() +{ + nes_time_t irq = Nes_Apu::no_irq; + if ( irq_enabled && length_counter ) + irq = apu->last_dmc_time + delay + + ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; + if ( irq != next_irq ) { + next_irq = irq; + apu->irq_changed(); + } +} + +int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const +{ + if ( last_read ) + *last_read = time; + + if ( length_counter == 0 ) + return 0; // not reading + + nes_time_t first_read = next_read_time(); + nes_time_t avail = time - first_read; + if ( avail <= 0 ) + return 0; + + int count = (avail - 1) / (period * 8) + 1; + if ( !(regs [0] & loop_flag) && count > length_counter ) + count = length_counter; + + if ( last_read ) + { + *last_read = first_read + (count - 1) * (period * 8) + 1; + check( *last_read <= time ); + check( count == count_reads( *last_read, NULL ) ); + check( count - 1 == count_reads( *last_read - 1, NULL ) ); + } + + return count; +} + +static short const dmc_period_table [2] [16] = { + {428, 380, 340, 320, 286, 254, 226, 214, // NTSC + 190, 160, 142, 128, 106, 84, 72, 54}, + + {398, 354, 316, 298, 276, 236, 210, 198, // PAL + 176, 148, 132, 118, 98, 78, 66, 50} +}; + +inline void Nes_Dmc::reload_sample() +{ + address = 0x4000 + regs [2] * 0x40; + length_counter = regs [3] * 0x10 + 1; +} + +static byte const dac_table [128] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, + 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, + 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, + 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, + 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, + 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, + 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, + 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, +}; + +void Nes_Dmc::write_register( int addr, int data ) +{ + if ( addr == 0 ) + { + period = dmc_period_table [pal_mode] [data & 15]; + irq_enabled = (data & 0xC0) == 0x80; // enabled only if loop disabled + irq_flag &= irq_enabled; + recalc_irq(); + } + else if ( addr == 1 ) + { + int old_dac = dac; + dac = data & 0x7F; + + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]); + if ( !nonlinear ) + last_amp = faked_nonlinear; + } +} + +void Nes_Dmc::start() +{ + reload_sample(); + fill_buffer(); + recalc_irq(); +} + +void Nes_Dmc::fill_buffer() +{ + if ( !buf_full && length_counter ) + { + require( prg_reader ); // prg_reader must be set + buf = prg_reader( prg_reader_data, 0x8000u + address ); + address = (address + 1) & 0x7FFF; + buf_full = true; + if ( --length_counter == 0 ) + { + if ( regs [0] & loop_flag ) { + reload_sample(); + } + else { + apu->osc_enables &= ~0x10; + irq_flag = irq_enabled; + next_irq = Nes_Apu::no_irq; + apu->irq_changed(); + } + } + } +} + +void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) +{ + int delta = update_amp( dac ); + if ( !output ) + { + silence = true; + } + else + { + output->set_modified(); + if ( delta ) + synth.offset( time, delta, output ); + } + + time += delay; + if ( time < end_time ) + { + int bits_remain = this->bits_remain; + if ( silence && !buf_full ) + { + int count = (end_time - time + period - 1) / period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * period; + } + else + { + Blip_Buffer* const output = this->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if ( !silence ) + { + int step = (bits & 1) * 4 - 2; + bits >>= 1; + if ( unsigned (dac + step) <= 0x7F ) { + dac += step; + synth.offset_inline( time, step, output ); + } + } + + time += period; + + if ( --bits_remain == 0 ) + { + bits_remain = 8; + if ( !buf_full ) { + silence = true; + } + else { + silence = false; + bits = buf; + buf_full = false; + if ( !output ) + silence = true; + fill_buffer(); + } + } + } + while ( time < end_time ); + + this->dac = dac; + this->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + delay = time - end_time; +} + +// Nes_Noise + +static short const noise_period_table [16] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 +}; + +void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) +{ + int period = noise_period_table [regs [2] & 15]; + + if ( !output ) + { + // TODO: clean up + time += delay; + delay = time + (end_time - time + period - 1) / period * period - end_time; + return; + } + + output->set_modified(); + + const int volume = this->volume(); + int amp = (noise & 1) ? volume : 0; + { + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + } + + time += delay; + if ( time < end_time ) + { + const int mode_flag = 0x80; + + if ( !volume ) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register + // to do: precise muted noise cycling? + if ( !(regs [2] & mode_flag) ) { + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + else + { + Blip_Buffer* const output = this->output; + + // using resampled time avoids conversion in synth.offset() + blip_resampled_time_t rperiod = output->resampled_duration( period ); + blip_resampled_time_t rtime = output->resampled_time( time ); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (regs [2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ( (noise + 1) & 2 ) { + // bits 0 and 1 of noise differ + delta = -delta; + synth.offset_resampled( rtime, delta, output ); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + delay = time - end_time; +} + diff --git a/game-music-emu/gme/Nes_Oscs.h b/game-music-emu/gme/Nes_Oscs.h new file mode 100644 index 000000000..b675bfb47 --- /dev/null +++ b/game-music-emu/gme/Nes_Oscs.h @@ -0,0 +1,147 @@ +// Private oscillators used by Nes_Apu + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_OSCS_H +#define NES_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Nes_Apu; + +struct Nes_Osc +{ + unsigned char regs [4]; + bool reg_written [4]; + Blip_Buffer* output; + int length_counter;// length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting + + void clock_length( int halt_mask ); + int period() const { + return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF); + } + void reset() { + delay = 0; + last_amp = 0; + } + int update_amp( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + return delta; + } +}; + +struct Nes_Envelope : Nes_Osc +{ + int envelope; + int env_delay; + + void clock_envelope(); + int volume() const; + void reset() { + envelope = 0; + env_delay = 0; + Nes_Osc::reset(); + } +}; + +// Nes_Square +struct Nes_Square : Nes_Envelope +{ + enum { negate_flag = 0x08 }; + enum { shift_mask = 0x07 }; + enum { phase_range = 8 }; + int phase; + int sweep_delay; + + typedef Blip_Synth Synth; + Synth const& synth; // shared between squares + + Nes_Square( Synth const* s ) : synth( *s ) { } + + void clock_sweep( int adjust ); + void run( nes_time_t, nes_time_t ); + void reset() { + sweep_delay = 0; + Nes_Envelope::reset(); + } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); +}; + +// Nes_Triangle +struct Nes_Triangle : Nes_Osc +{ + enum { phase_range = 16 }; + int phase; + int linear_counter; + Blip_Synth synth; + + int calc_amp() const; + void run( nes_time_t, nes_time_t ); + void clock_linear_counter(); + void reset() { + linear_counter = 0; + phase = 1; + Nes_Osc::reset(); + } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); +}; + +// Nes_Noise +struct Nes_Noise : Nes_Envelope +{ + int noise; + Blip_Synth synth; + + void run( nes_time_t, nes_time_t ); + void reset() { + noise = 1 << 14; + Nes_Envelope::reset(); + } +}; + +// Nes_Dmc +struct Nes_Dmc : Nes_Osc +{ + int address; // address of next byte to read + int period; + //int length_counter; // bytes remaining to play (already defined in Nes_Osc) + int buf; + int bits_remain; + int bits; + bool buf_full; + bool silence; + + enum { loop_flag = 0x40 }; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function + void* prg_reader_data; + + Nes_Apu* apu; + + Blip_Synth synth; + + void start(); + void write_register( int, int ); + void run( nes_time_t, nes_time_t ); + void recalc_irq(); + void fill_buffer(); + void reload_sample(); + void reset(); + int count_reads( nes_time_t, nes_time_t* ) const; + nes_time_t next_read_time() const; +}; + +#endif diff --git a/game-music-emu/gme/Nes_Vrc6_Apu.cpp b/game-music-emu/gme/Nes_Vrc6_Apu.cpp new file mode 100644 index 000000000..d178407c3 --- /dev/null +++ b/game-music-emu/gme/Nes_Vrc6_Apu.cpp @@ -0,0 +1,215 @@ +// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/ + +#include "Nes_Vrc6_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Nes_Vrc6_Apu::Nes_Vrc6_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +void Nes_Vrc6_Apu::reset() +{ + last_time = 0; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int j = 0; j < reg_count; j++ ) + osc.regs [j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } +} + +void Nes_Vrc6_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc6_Apu::run_until( blip_time_t time ) +{ + require( time >= last_time ); + run_square( oscs [0], time ); + run_square( oscs [1], time ); + run_saw( time ); + last_time = time; +} + +void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data ) +{ + require( (unsigned) osc_index < osc_count ); + require( (unsigned) reg < reg_count ); + + run_until( time ); + oscs [osc_index].regs [reg] = data; +} + +void Nes_Vrc6_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const +{ + assert( sizeof (vrc6_apu_state_t) == 20 ); + out->saw_amp = oscs [2].amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc const& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + out->regs [i] [r] = osc.regs [r]; + + out->delays [i] = osc.delay; + out->phases [i] = osc.phase; + } +} + +void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in ) +{ + reset(); + oscs [2].amp = in.saw_amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + osc.regs [r] = in.regs [i] [r]; + + osc.delay = in.delays [i]; + osc.phase = in.phases [i]; + } + if ( !oscs [2].phase ) + oscs [2].phase = 1; +} + +void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time ) +{ + Blip_Buffer* output = osc.output; + if ( !output ) + return; + output->set_modified(); + + int volume = osc.regs [0] & 15; + if ( !(osc.regs [2] & 0x80) ) + volume = 0; + + int gate = osc.regs [0] & 0x80; + int duty = ((osc.regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + blip_time_t time = last_time; + if ( delta ) + { + osc.last_amp += delta; + square_synth.offset( time, delta, output ); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc.phase; + + do + { + phase++; + if ( phase == 16 ) + { + phase = 0; + osc.last_amp = volume; + square_synth.offset( time, volume, output ); + } + if ( phase == duty ) + { + osc.last_amp = 0; + square_synth.offset( time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase; + } + osc.delay = time - end_time; + } +} + +void Nes_Vrc6_Apu::run_saw( blip_time_t end_time ) +{ + Vrc6_Osc& osc = oscs [2]; + Blip_Buffer* output = osc.output; + if ( !output ) + return; + output->set_modified(); + + int amp = osc.amp; + int amp_step = osc.regs [0] & 0x3F; + blip_time_t time = last_time; + int last_amp = osc.last_amp; + if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + else + { + time += osc.delay; + if ( time < end_time ) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do + { + if ( --phase == 0 ) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) + { + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; +} + diff --git a/game-music-emu/gme/Nes_Vrc6_Apu.h b/game-music-emu/gme/Nes_Vrc6_Apu.h new file mode 100644 index 000000000..18722233f --- /dev/null +++ b/game-music-emu/gme/Nes_Vrc6_Apu.h @@ -0,0 +1,95 @@ +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.8 +#ifndef NES_VRC6_APU_H +#define NES_VRC6_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct vrc6_apu_state_t; + +class Nes_Vrc6_Apu { +public: + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_state( vrc6_apu_state_t* ) const; + void load_state( vrc6_apu_state_t const& ); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum { reg_count = 3 }; + enum { base_addr = 0x9000 }; + enum { addr_step = 0x1000 }; + void write_osc( blip_time_t, int osc, int reg, int data ); + +public: + Nes_Vrc6_Apu(); + BLARGG_DISABLE_NOTHROW +private: + // noncopyable + Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); + Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& ); + + struct Vrc6_Osc + { + BOOST::uint8_t regs [3]; + Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const + { + return (regs [2] & 0x0F) * 0x100L + regs [1] + 1; + } + }; + + Vrc6_Osc oscs [osc_count]; + blip_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until( blip_time_t ); + void run_square( Vrc6_Osc& osc, blip_time_t ); + void run_saw( blip_time_t ); +}; + +struct vrc6_apu_state_t +{ + BOOST::uint8_t regs [3] [3]; + BOOST::uint8_t saw_amp; + BOOST::uint16_t delays [3]; + BOOST::uint8_t phases [3]; + BOOST::uint8_t unused; +}; + +inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Vrc6_Apu::volume( double v ) +{ + double const factor = 0.0967 * 2; + saw_synth.volume( factor / 31 * v ); + square_synth.volume( factor * 0.5 / 15 * v ); +} + +inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) +{ + saw_synth.treble_eq( eq ); + square_synth.treble_eq( eq ); +} + +#endif diff --git a/game-music-emu/gme/Nsf_Emu.cpp b/game-music-emu/gme/Nsf_Emu.cpp new file mode 100644 index 000000000..15f9e0b46 --- /dev/null +++ b/game-music-emu/gme/Nsf_Emu.cpp @@ -0,0 +1,559 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Nsf_Emu.h" + +#include "blargg_endian.h" +#include +#include + +#if !NSF_EMU_APU_ONLY + #include "Nes_Namco_Apu.h" + #include "Nes_Vrc6_Apu.h" + #include "Nes_Fme7_Apu.h" +#endif + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const vrc6_flag = 0x01; +int const namco_flag = 0x10; +int const fme7_flag = 0x20; + +long const clock_divisor = 12; + +Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80 }; +Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80 }; + +int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr ) +{ + return *((Nsf_Emu*) emu)->cpu::get_code( addr ); +} + +Nsf_Emu::Nsf_Emu() +{ + vrc6 = 0; + namco = 0; + fme7 = 0; + + set_type( gme_nsf_type ); + set_silence_lookahead( 6 ); + apu.dmc_reader( pcm_read, this ); + Music_Emu::set_equalizer( nes_eq ); + set_gain( 1.4 ); + memset( unmapped_code, Nes_Cpu::bad_opcode, sizeof unmapped_code ); +} + +Nsf_Emu::~Nsf_Emu() { unload(); } + +void Nsf_Emu::unload() +{ + #if !NSF_EMU_APU_ONLY + { + delete vrc6; + vrc6 = 0; + + delete namco; + namco = 0; + + delete fme7; + fme7 = 0; + } + #endif + + rom.clear(); + Music_Emu::unload(); +} + +// Track info + +static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out ) +{ + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, author ); + GME_COPY_FIELD( h, out, copyright ); + if ( h.chip_flags ) + Gme_File::copy_field_( out->system, "Famicom" ); +} + +blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const +{ + copy_nsf_fields( header_, out ); + return 0; +} + +static blargg_err_t check_nsf_header( void const* header ) +{ + if ( memcmp( header, "NESM\x1A", 5 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Nsf_File : Gme_Info_ +{ + Nsf_Emu::header_t h; + + Nsf_File() { set_type( gme_nsf_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + blargg_err_t err = in.read( &h, Nsf_Emu::header_size ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + + if ( h.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) ) + set_warning( "Uses unsupported audio expansion hardware" ); + + set_track_count( h.track_count ); + return check_nsf_header( &h ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_nsf_fields( h, out ); + return 0; + } +}; + +static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; } +static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; } + +static gme_type_t_ const gme_nsf_type_ = { "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 }; +gme_type_t const gme_nsf_type = &gme_nsf_type_; + + +// Setup + +void Nsf_Emu::set_tempo_( double t ) +{ + unsigned playback_rate = get_le16( header_.ntsc_speed ); + unsigned standard_rate = 0x411A; + clock_rate_ = 1789772.72727; + play_period = 262 * 341L * 4 - 2; // two fewer PPU clocks every four frames + + if ( pal_only ) + { + play_period = 33247 * clock_divisor; + clock_rate_ = 1662607.125; + standard_rate = 0x4E20; + playback_rate = get_le16( header_.pal_speed ); + } + + if ( !playback_rate ) + playback_rate = standard_rate; + + if ( playback_rate != standard_rate || t != 1.0 ) + play_period = long (playback_rate * clock_rate_ / (1000000.0 / clock_divisor * t)); + + apu.set_tempo( t ); +} + +blargg_err_t Nsf_Emu::init_sound() +{ + if ( header_.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag) ) + set_warning( "Uses unsupported audio expansion hardware" ); + + { + #define APU_NAMES "Square 1", "Square 2", "Triangle", "Noise", "DMC" + + int const count = Nes_Apu::osc_count; + static const char* const apu_names [count] = { APU_NAMES }; + set_voice_count( count ); + set_voice_names( apu_names ); + + } + + static int const types [] = { + wave_type | 1, wave_type | 2, wave_type | 0, + noise_type | 0, mixed_type | 1, + wave_type | 3, wave_type | 4, wave_type | 5, + wave_type | 6, wave_type | 7, wave_type | 8, wave_type | 9, + wave_type |10, wave_type |11, wave_type |12, wave_type |13 + }; + set_voice_types( types ); // common to all sound chip configurations + + double adjusted_gain = gain(); + + #if NSF_EMU_APU_ONLY + { + if ( header_.chip_flags ) + set_warning( "Uses unsupported audio expansion hardware" ); + } + #else + { + if ( header_.chip_flags & (namco_flag | vrc6_flag | fme7_flag) ) + set_voice_count( Nes_Apu::osc_count + 3 ); + + if ( header_.chip_flags & namco_flag ) + { + namco = BLARGG_NEW Nes_Namco_Apu; + CHECK_ALLOC( namco ); + adjusted_gain *= 0.75; + + int const count = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Wave 1", "Wave 2", "Wave 3", "Wave 4", + "Wave 5", "Wave 6", "Wave 7", "Wave 8" + }; + set_voice_count( count ); + set_voice_names( names ); + } + + if ( header_.chip_flags & vrc6_flag ) + { + vrc6 = BLARGG_NEW Nes_Vrc6_Apu; + CHECK_ALLOC( vrc6 ); + adjusted_gain *= 0.75; + + { + int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Saw Wave", "Square 3", "Square 4" + }; + set_voice_count( count ); + set_voice_names( names ); + } + + if ( header_.chip_flags & namco_flag ) + { + int const count = Nes_Apu::osc_count + Nes_Vrc6_Apu::osc_count + + Nes_Namco_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Saw Wave", "Square 3", "Square 4", + "Wave 1", "Wave 2", "Wave 3", "Wave 4", + "Wave 5", "Wave 6", "Wave 7", "Wave 8" + }; + set_voice_count( count ); + set_voice_names( names ); + } + } + + if ( header_.chip_flags & fme7_flag ) + { + fme7 = BLARGG_NEW Nes_Fme7_Apu; + CHECK_ALLOC( fme7 ); + adjusted_gain *= 0.75; + + int const count = Nes_Apu::osc_count + Nes_Fme7_Apu::osc_count; + static const char* const names [count] = { + APU_NAMES, + "Square 3", "Square 4", "Square 5" + }; + set_voice_count( count ); + set_voice_names( names ); + } + + if ( namco ) namco->volume( adjusted_gain ); + if ( vrc6 ) vrc6 ->volume( adjusted_gain ); + if ( fme7 ) fme7 ->volume( adjusted_gain ); + } + #endif + + apu.volume( adjusted_gain ); + + return 0; +} + +blargg_err_t Nsf_Emu::load_( Data_Reader& in ) +{ + assert( offsetof (header_t,unused [4]) == header_size ); + RETURN_ERR( rom.load( in, header_size, &header_, 0 ) ); + + set_track_count( header_.track_count ); + RETURN_ERR( check_nsf_header( &header_ ) ); + + if ( header_.vers != 1 ) + set_warning( "Unknown file version" ); + + // sound and memory + blargg_err_t err = init_sound(); + if ( err ) + return err; + + // set up data + nes_addr_t load_addr = get_le16( header_.load_addr ); + init_addr = get_le16( header_.init_addr ); + play_addr = get_le16( header_.play_addr ); + if ( !load_addr ) load_addr = rom_begin; + if ( !init_addr ) init_addr = rom_begin; + if ( !play_addr ) play_addr = rom_begin; + if ( load_addr < rom_begin || init_addr < rom_begin ) + { + const char* w = warning(); + if ( !w ) + w = "Corrupt file (invalid load/init/play address)"; + return w; + } + + rom.set_addr( load_addr % bank_size ); + int total_banks = rom.size() / bank_size; + + // bank switching + int first_bank = (load_addr - rom_begin) / bank_size; + for ( int i = 0; i < bank_count; i++ ) + { + unsigned bank = i - first_bank; + if ( bank >= (unsigned) total_banks ) + bank = 0; + initial_banks [i] = bank; + + if ( header_.banks [i] ) + { + // bank-switched + memcpy( initial_banks, header_.banks, sizeof initial_banks ); + break; + } + } + + pal_only = (header_.speed_flags & 3) == 1; + + #if !NSF_EMU_EXTRA_FLAGS + header_.speed_flags = 0; + #endif + + set_tempo( tempo() ); + + return setup_buffer( (long) (clock_rate_ + 0.5) ); +} + +void Nsf_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); + + #if !NSF_EMU_APU_ONLY + { + if ( namco ) namco->treble_eq( eq ); + if ( vrc6 ) vrc6 ->treble_eq( eq ); + if ( fme7 ) fme7 ->treble_eq( eq ); + } + #endif +} + +void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) +{ + if ( i < Nes_Apu::osc_count ) + { + apu.osc_output( i, buf ); + return; + } + i -= Nes_Apu::osc_count; + + #if !NSF_EMU_APU_ONLY + { + if ( fme7 && i < Nes_Fme7_Apu::osc_count ) + { + fme7->osc_output( i, buf ); + return; + } + + if ( vrc6 ) + { + if ( i < Nes_Vrc6_Apu::osc_count ) + { + // put saw first + if ( --i < 0 ) + i = 2; + vrc6->osc_output( i, buf ); + return; + } + i -= Nes_Vrc6_Apu::osc_count; + } + + if ( namco && i < Nes_Namco_Apu::osc_count ) + { + namco->osc_output( i, buf ); + return; + } + } + #endif +} + +// Emulation + +// see nes_cpu_io.h for read/write functions + +void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data ) +{ + #if !NSF_EMU_APU_ONLY + { + if ( namco ) + { + switch ( addr ) + { + case Nes_Namco_Apu::data_reg_addr: + namco->write_data( time(), data ); + return; + + case Nes_Namco_Apu::addr_reg_addr: + namco->write_addr( data ); + return; + } + } + + if ( addr >= Nes_Fme7_Apu::latch_addr && fme7 ) + { + switch ( addr & Nes_Fme7_Apu::addr_mask ) + { + case Nes_Fme7_Apu::latch_addr: + fme7->write_latch( data ); + return; + + case Nes_Fme7_Apu::data_addr: + fme7->write_data( time(), data ); + return; + } + } + + if ( vrc6 ) + { + unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1); + unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step; + if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count ) + { + vrc6->write_osc( time(), osc, reg, data ); + return; + } + } + } + #endif + + // unmapped write + + #ifndef NDEBUG + { + // some games write to $8000 and $8001 repeatedly + if ( addr == 0x8000 || addr == 0x8001 ) return; + + // probably namco sound mistakenly turned on in mck + if ( addr == 0x4800 || addr == 0xF800 ) return; + + // memory mapper? + if ( addr == 0xFFF8 ) return; + + dprintf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data ); + } + #endif +} + +blargg_err_t Nsf_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( low_mem, 0, sizeof low_mem ); + memset( sram, 0, sizeof sram ); + + cpu::reset( unmapped_code ); // also maps low_mem + cpu::map_code( sram_addr, sizeof sram, sram ); + for ( int i = 0; i < bank_count; ++i ) + cpu_write( bank_select_addr + i, initial_banks [i] ); + + apu.reset( pal_only, (header_.speed_flags & 0x20) ? 0x3F : 0 ); + apu.write_register( 0, 0x4015, 0x0F ); + apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 ); + #if !NSF_EMU_APU_ONLY + { + if ( namco ) namco->reset(); + if ( vrc6 ) vrc6 ->reset(); + if ( fme7 ) fme7 ->reset(); + } + #endif + + play_ready = 4; + play_extra = 0; + next_play = play_period / clock_divisor; + + saved_state.pc = badop_addr; + low_mem [0x1FF] = (badop_addr - 1) >> 8; + low_mem [0x1FE] = (badop_addr - 1) & 0xFF; + r.sp = 0xFD; + r.pc = init_addr; + r.a = track; + r.x = pal_only; + + return 0; +} + +blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int ) +{ + set_time( 0 ); + while ( time() < duration ) + { + nes_time_t end = min( (blip_time_t) next_play, duration ); + end = min( end, time() + 32767 ); // allows CPU to use 16-bit time delta + if ( cpu::run( end ) ) + { + if ( r.pc != badop_addr ) + { + set_warning( "Emulation error (illegal instruction)" ); + r.pc++; + } + else + { + play_ready = 1; + if ( saved_state.pc != badop_addr ) + { + cpu::r = saved_state; + saved_state.pc = badop_addr; + } + else + { + set_time( end ); + } + } + } + + if ( time() >= next_play ) + { + nes_time_t period = (play_period + play_extra) / clock_divisor; + play_extra = play_period - period * clock_divisor; + next_play += period; + if ( play_ready && !--play_ready ) + { + check( saved_state.pc == badop_addr ); + if ( r.pc != badop_addr ) + saved_state = cpu::r; + + r.pc = play_addr; + low_mem [0x100 + r.sp--] = (badop_addr - 1) >> 8; + low_mem [0x100 + r.sp--] = (badop_addr - 1) & 0xFF; + GME_FRAME_HOOK( this ); + } + } + } + + if ( cpu::error_count() ) + { + cpu::clear_error_count(); + set_warning( "Emulation error (illegal instruction)" ); + } + + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + if ( next_play < 0 ) + next_play = 0; + + apu.end_frame( duration ); + + #if !NSF_EMU_APU_ONLY + { + if ( namco ) namco->end_frame( duration ); + if ( vrc6 ) vrc6 ->end_frame( duration ); + if ( fme7 ) fme7 ->end_frame( duration ); + } + #endif + + return 0; +} diff --git a/game-music-emu/gme/Nsf_Emu.h b/game-music-emu/gme/Nsf_Emu.h new file mode 100644 index 000000000..e06b91727 --- /dev/null +++ b/game-music-emu/gme/Nsf_Emu.h @@ -0,0 +1,106 @@ +// Nintendo NES/Famicom NSF music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef NSF_EMU_H +#define NSF_EMU_H + +#include "Classic_Emu.h" +#include "Nes_Apu.h" +#include "Nes_Cpu.h" + +class Nsf_Emu : private Nes_Cpu, public Classic_Emu { + typedef Nes_Cpu cpu; +public: + // Equalizer profiles for US NES and Japanese Famicom + static equalizer_t const nes_eq; + static equalizer_t const famicom_eq; + + // NSF file header + enum { header_size = 0x80 }; + struct header_t + { + char tag [5]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + char game [32]; + char author [32]; + char copyright [32]; + byte ntsc_speed [2]; + byte banks [8]; + byte pal_speed [2]; + byte speed_flags; + byte chip_flags; + byte unused [4]; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + static gme_type_t static_type() { return gme_nsf_type; } + +public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + +public: + Nsf_Emu(); + ~Nsf_Emu(); + Nes_Apu* apu_() { return &apu; } +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_( Data_Reader& ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + void unload(); +protected: + enum { bank_count = 8 }; + byte initial_banks [bank_count]; + nes_addr_t init_addr; + nes_addr_t play_addr; + double clock_rate_; + bool pal_only; + + // timing + Nes_Cpu::registers_t saved_state; + nes_time_t next_play; + nes_time_t play_period; + int play_extra; + int play_ready; + + enum { rom_begin = 0x8000 }; + enum { bank_select_addr = 0x5FF8 }; + enum { bank_size = 0x1000 }; + Rom_Data rom; + +public: private: friend class Nes_Cpu; + void cpu_jsr( nes_addr_t ); + int cpu_read( nes_addr_t ); + void cpu_write( nes_addr_t, int ); + void cpu_write_misc( nes_addr_t, int ); + enum { badop_addr = bank_select_addr }; + +private: + class Nes_Namco_Apu* namco; + class Nes_Vrc6_Apu* vrc6; + class Nes_Fme7_Apu* fme7; + Nes_Apu apu; + static int pcm_read( void*, nes_addr_t ); + blargg_err_t init_sound(); + + header_t header_; + + enum { sram_addr = 0x6000 }; + byte sram [0x2000]; + byte unmapped_code [Nes_Cpu::page_size + 8]; +}; + +#endif diff --git a/game-music-emu/gme/Nsfe_Emu.cpp b/game-music-emu/gme/Nsfe_Emu.cpp new file mode 100644 index 000000000..a81b18865 --- /dev/null +++ b/game-music-emu/gme/Nsfe_Emu.cpp @@ -0,0 +1,334 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#define _CRT_SECURE_NO_WARNINGS + +#include "Nsfe_Emu.h" + +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2005-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Nsfe_Info::Nsfe_Info() { playlist_disabled = false; } + +Nsfe_Info::~Nsfe_Info() { } + +inline void Nsfe_Info::unload() +{ + track_name_data.clear(); + track_names.clear(); + playlist.clear(); + track_times.clear(); +} + +// TODO: if no playlist, treat as if there is a playlist that is just 1,2,3,4,5... ? +void Nsfe_Info::disable_playlist( bool b ) +{ + playlist_disabled = b; + info.track_count = (byte)playlist.size(); + if ( !info.track_count || playlist_disabled ) + info.track_count = actual_track_count_; +} + +int Nsfe_Info::remap_track( int track ) const +{ + if ( !playlist_disabled && (unsigned) track < playlist.size() ) + track = playlist [track]; + return track; +} + +// Read multiple strings and separate into individual strings +static blargg_err_t read_strs( Data_Reader& in, long size, blargg_vector& chars, + blargg_vector& strs ) +{ + RETURN_ERR( chars.resize( size + 1 ) ); + chars [size] = 0; // in case last string doesn't have terminator + RETURN_ERR( in.read( &chars [0], size ) ); + + RETURN_ERR( strs.resize( 128 ) ); + int count = 0; + for ( int i = 0; i < size; i++ ) + { + if ( (int) strs.size() <= count ) + RETURN_ERR( strs.resize( count * 2 ) ); + strs [count++] = &chars [i]; + while ( i < size && chars [i] ) + i++; + } + + return strs.resize( count ); +} + +// Copy in to out, where out has out_max characters allocated. Truncate to +// out_max - 1 characters. +static void copy_str( const char* in, char* out, int out_max ) +{ + out [out_max - 1] = 0; + strncpy( out, in, out_max - 1 ); +} + +struct nsfe_info_t +{ + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte speed_flags; + byte chip_flags; + byte track_count; + byte first_track; + byte unused [6]; +}; + +blargg_err_t Nsfe_Info::load( Data_Reader& in, Nsf_Emu* nsf_emu ) +{ + int const nsfe_info_size = 16; + assert( offsetof (nsfe_info_t,unused [6]) == nsfe_info_size ); + + // check header + byte signature [4]; + blargg_err_t err = in.read( signature, sizeof signature ); + if ( err ) + return (err == in.eof_error ? gme_wrong_file_type : err); + if ( memcmp( signature, "NSFE", 4 ) ) + return gme_wrong_file_type; + + // free previous info + track_name_data.clear(); + track_names.clear(); + playlist.clear(); + track_times.clear(); + + // default nsf header + static const Nsf_Emu::header_t base_header = + { + {'N','E','S','M','\x1A'},// tag + 1, // version + 1, 1, // track count, first track + {0,0},{0,0},{0,0}, // addresses + "","","", // strings + {0x1A, 0x41}, // NTSC rate + {0,0,0,0,0,0,0,0}, // banks + {0x20, 0x4E}, // PAL rate + 0, 0, // flags + {0,0,0,0} // unused + }; + Nsf_Emu::header_t& header = info; + header = base_header; + + // parse tags + int phase = 0; + while ( phase != 3 ) + { + // read size and tag + byte block_header [2] [4]; + RETURN_ERR( in.read( block_header, sizeof block_header ) ); + blargg_long size = get_le32( block_header [0] ); + blargg_long tag = get_le32( block_header [1] ); + + //dprintf( "tag: %c%c%c%c\n", char(tag), char(tag>>8), char(tag>>16), char(tag>>24) ); + + switch ( tag ) + { + case BLARGG_4CHAR('O','F','N','I'): { + check( phase == 0 ); + if ( size < 8 ) + return "Corrupt file"; + + nsfe_info_t finfo; + finfo.track_count = 1; + finfo.first_track = 0; + + RETURN_ERR( in.read( &finfo, min( size, (blargg_long) nsfe_info_size ) ) ); + if ( size > nsfe_info_size ) + RETURN_ERR( in.skip( size - nsfe_info_size ) ); + phase = 1; + info.speed_flags = finfo.speed_flags; + info.chip_flags = finfo.chip_flags; + info.track_count = finfo.track_count; + this->actual_track_count_ = finfo.track_count; + info.first_track = finfo.first_track; + memcpy( info.load_addr, finfo.load_addr, 2 * 3 ); + break; + } + + case BLARGG_4CHAR('K','N','A','B'): + if ( size > (int) sizeof info.banks ) + return "Corrupt file"; + RETURN_ERR( in.read( info.banks, size ) ); + break; + + case BLARGG_4CHAR('h','t','u','a'): { + blargg_vector chars; + blargg_vector strs; + RETURN_ERR( read_strs( in, size, chars, strs ) ); + int n = (int)strs.size(); + + if ( n > 3 ) + copy_str( strs [3], info.dumper, sizeof info.dumper ); + + if ( n > 2 ) + copy_str( strs [2], info.copyright, sizeof info.copyright ); + + if ( n > 1 ) + copy_str( strs [1], info.author, sizeof info.author ); + + if ( n > 0 ) + copy_str( strs [0], info.game, sizeof info.game ); + + break; + } + + case BLARGG_4CHAR('e','m','i','t'): + RETURN_ERR( track_times.resize( size / 4 ) ); + RETURN_ERR( in.read( track_times.begin(), (long)track_times.size() * 4 ) ); + break; + + case BLARGG_4CHAR('l','b','l','t'): + RETURN_ERR( read_strs( in, size, track_name_data, track_names ) ); + break; + + case BLARGG_4CHAR('t','s','l','p'): + RETURN_ERR( playlist.resize( size ) ); + RETURN_ERR( in.read( &playlist [0], size ) ); + break; + + case BLARGG_4CHAR('A','T','A','D'): { + check( phase == 1 ); + phase = 2; + if ( !nsf_emu ) + { + RETURN_ERR( in.skip( size ) ); + } + else + { + Subset_Reader sub( &in, size ); // limit emu to nsf data + Remaining_Reader rem( &header, Nsf_Emu::header_size, &sub ); + RETURN_ERR( nsf_emu->load( rem ) ); + check( rem.remain() == 0 ); + } + break; + } + + case BLARGG_4CHAR('D','N','E','N'): + check( phase == 2 ); + phase = 3; + break; + + default: + // tags that can be skipped start with a lowercase character + check( islower( (tag >> 24) & 0xFF ) ); + RETURN_ERR( in.skip( size ) ); + break; + } + } + + return 0; +} + +blargg_err_t Nsfe_Info::track_info_( track_info_t* out, int track ) const +{ + int remapped = remap_track( track ); + if ( (unsigned) remapped < track_times.size() ) + { + long length = (BOOST::int32_t) get_le32( track_times [remapped] ); + if ( length > 0 ) + out->length = length; + } + if ( (unsigned) remapped < track_names.size() ) + Gme_File::copy_field_( out->song, track_names [remapped] ); + + GME_COPY_FIELD( info, out, game ); + GME_COPY_FIELD( info, out, author ); + GME_COPY_FIELD( info, out, copyright ); + GME_COPY_FIELD( info, out, dumper ); + return 0; +} + +Nsfe_Emu::Nsfe_Emu() +{ + loading = false; + set_type( gme_nsfe_type ); +} + +Nsfe_Emu::~Nsfe_Emu() { } + +void Nsfe_Emu::unload() +{ + if ( !loading ) + info.unload(); // TODO: extremely hacky! + Nsf_Emu::unload(); +} + +blargg_err_t Nsfe_Emu::track_info_( track_info_t* out, int track ) const +{ + return info.track_info_( out, track ); +} + +struct Nsfe_File : Gme_Info_ +{ + Nsfe_Info info; + + Nsfe_File() { set_type( gme_nsfe_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + RETURN_ERR( info.load( in, 0 ) ); + info.disable_playlist( false ); + set_track_count( info.info.track_count ); + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int track ) const + { + return info.track_info_( out, track ); + } +}; + +static Music_Emu* new_nsfe_emu () { return BLARGG_NEW Nsfe_Emu ; } +static Music_Emu* new_nsfe_file() { return BLARGG_NEW Nsfe_File; } + +static gme_type_t_ const gme_nsfe_type_ = { "Nintendo NES", 0, &new_nsfe_emu, &new_nsfe_file, "NSFE", 1 }; +gme_type_t const gme_nsfe_type = &gme_nsfe_type_; + + +blargg_err_t Nsfe_Emu::load_( Data_Reader& in ) +{ + if ( loading ) + return Nsf_Emu::load_( in ); + + // TODO: this hacky recursion-avoidance could have subtle problems + loading = true; + blargg_err_t err = info.load( in, this ); + loading = false; + disable_playlist( false ); + return err; +} + +void Nsfe_Emu::disable_playlist( bool b ) +{ + info.disable_playlist( b ); + set_track_count( info.info.track_count ); +} + +void Nsfe_Emu::clear_playlist_() +{ + disable_playlist(); + Nsf_Emu::clear_playlist_(); +} + +blargg_err_t Nsfe_Emu::start_track_( int track ) +{ + return Nsf_Emu::start_track_( info.remap_track( track ) ); +} diff --git a/game-music-emu/gme/Nsfe_Emu.h b/game-music-emu/gme/Nsfe_Emu.h new file mode 100644 index 000000000..561c3be0f --- /dev/null +++ b/game-music-emu/gme/Nsfe_Emu.h @@ -0,0 +1,68 @@ +// Nintendo NES/Famicom NSFE music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef NSFE_EMU_H +#define NSFE_EMU_H + +#include "blargg_common.h" +#include "Nsf_Emu.h" + +// Allows reading info from NSFE file without creating emulator +class Nsfe_Info { +public: + blargg_err_t load( Data_Reader&, Nsf_Emu* ); + + struct info_t : Nsf_Emu::header_t + { + char game [256]; + char author [256]; + char copyright [256]; + char dumper [256]; + } info; + + void disable_playlist( bool = true ); + + blargg_err_t track_info_( track_info_t* out, int track ) const; + + int remap_track( int i ) const; + + void unload(); + + Nsfe_Info(); + ~Nsfe_Info(); +private: + blargg_vector track_name_data; + blargg_vector track_names; + blargg_vector playlist; + blargg_vector track_times; + int actual_track_count_; + bool playlist_disabled; +}; + +class Nsfe_Emu : public Nsf_Emu { +public: + static gme_type_t static_type() { return gme_nsfe_type; } + +public: + // deprecated + struct header_t { char tag [4]; }; + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + void disable_playlist( bool = true ); // use clear_playlist() + +public: + Nsfe_Emu(); + ~Nsfe_Emu(); +protected: + blargg_err_t load_( Data_Reader& ); + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t start_track_( int ); + void unload(); + void clear_playlist_(); +private: + Nsfe_Info info; + bool loading; +}; + +#endif diff --git a/game-music-emu/gme/Sap_Apu.cpp b/game-music-emu/gme/Sap_Apu.cpp new file mode 100644 index 000000000..4ad7db11c --- /dev/null +++ b/game-music-emu/gme/Sap_Apu.cpp @@ -0,0 +1,334 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Sap_Apu.h" + +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const max_frequency = 12000; // pure waves above this frequency are silenced + +static void gen_poly( blargg_ulong mask, int count, byte* out ) +{ + blargg_ulong n = 1; + do + { + int bits = 0; + int b = 0; + do + { + // implemented using "Galios configuration" + bits |= (n & 1) << b; + n = (n >> 1) ^ (mask & (blargg_ulong)-(blargg_long)(n & 1)); + } + while ( b++ < 7 ); + *out++ = bits; + } + while ( --count ); +} + +// poly5 +int const poly5_len = (1 << 5) - 1; +blargg_ulong const poly5_mask = (1UL << poly5_len) - 1; +blargg_ulong const poly5 = 0x167C6EA1; + +inline blargg_ulong run_poly5( blargg_ulong in, int shift ) +{ + return (in << shift & poly5_mask) | (in >> (poly5_len - shift)); +} + +#define POLY_MASK( width, tap1, tap2 ) \ + ((1UL << (width - 1 - tap1)) | (1UL << (width - 1 - tap2))) + +Sap_Apu_Impl::Sap_Apu_Impl() +{ + gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 ); + gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 ); + gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 ); + + if ( 0 ) // comment out to recauculate poly5 constant + { + byte poly5 [4]; + gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 ); + blargg_ulong n = poly5 [3] * 0x1000000L + poly5 [2] * 0x10000L + + poly5 [1] * 0x100L + poly5 [0]; + blargg_ulong rev = n & 1; + for ( int i = 1; i < poly5_len; i++ ) + rev |= (n >> i & 1) << (poly5_len - i); + dprintf( "poly5: 0x%08lX\n", rev ); + } +} + +Sap_Apu::Sap_Apu() +{ + impl = 0; + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, 0 ); +} + +void Sap_Apu::reset( Sap_Apu_Impl* new_impl ) +{ + impl = new_impl; + last_time = 0; + poly5_pos = 0; + poly4_pos = 0; + polym_pos = 0; + control = 0; + + for ( int i = 0; i < osc_count; i++ ) + memset( &oscs [i], 0, offsetof (osc_t,output) ); +} + +inline void Sap_Apu::calc_periods() +{ + // 15/64 kHz clock + int divider = 28; + if ( this->control & 1 ) + divider = 114; + + for ( int i = 0; i < osc_count; i++ ) + { + osc_t* const osc = &oscs [i]; + + int const osc_reload = osc->regs [0]; // cache + blargg_long period = (osc_reload + 1) * divider; + static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 }; + if ( this->control & fast_bits [i] ) + { + period = osc_reload + 4; + if ( i & 1 ) + { + period = osc_reload * 0x100L + osc [-1].regs [0] + 7; + if ( !(this->control & fast_bits [i - 1]) ) + period = (period - 6) * divider; + + if ( (osc [-1].regs [1] & 0x1F) > 0x10 ) + dprintf( "Use of slave channel in 16-bit mode not supported\n" ); + } + } + osc->period = period; + } +} + +void Sap_Apu::run_until( blip_time_t end_time ) +{ + calc_periods(); + Sap_Apu_Impl* const impl = this->impl; // cache + + // 17/9-bit poly selection + byte const* polym = impl->poly17; + int polym_len = poly17_len; + if ( this->control & 0x80 ) + { + polym_len = poly9_len; + polym = impl->poly9; + } + polym_pos %= polym_len; + + for ( int i = 0; i < osc_count; i++ ) + { + osc_t* const osc = &oscs [i]; + blip_time_t time = last_time + osc->delay; + blip_time_t const period = osc->period; + + // output + Blip_Buffer* output = osc->output; + if ( output ) + { + output->set_modified(); + + int const osc_control = osc->regs [1]; // cache + int volume = (osc_control & 0x0F) * 2; + if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency + ((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) ) + { + if ( !(osc_control & 0x10) ) + volume >>= 1; // inaudible frequency = half volume + + int delta = volume - osc->last_amp; + if ( delta ) + { + osc->last_amp = volume; + impl->synth.offset( last_time, delta, output ); + } + + // TODO: doesn't maintain high pass flip-flop (very minor issue) + } + else + { + // high pass + static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 }; + blip_time_t period2 = 0; // unused if no high pass + blip_time_t time2 = end_time; + if ( this->control & hipass_bits [i] ) + { + period2 = osc [2].period; + time2 = last_time + osc [2].delay; + if ( osc->invert ) + { + // trick inner wave loop into inverting output + osc->last_amp -= volume; + volume = -volume; + } + } + + if ( time < end_time || time2 < end_time ) + { + // poly source + static byte const poly1 [] = { 0x55, 0x55 }; // square wave + byte const* poly = poly1; + int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster + int poly_pos = osc->phase & 1; + int poly_inc = 1; + if ( !(osc_control & 0x20) ) + { + poly = polym; + poly_len = polym_len; + poly_pos = polym_pos; + if ( osc_control & 0x40 ) + { + poly = impl->poly4; + poly_len = poly4_len; + poly_pos = poly4_pos; + } + poly_inc = period % poly_len; + poly_pos = (poly_pos + osc->delay) % poly_len; + } + poly_inc -= poly_len; // allows more optimized inner loop below + + // square/poly5 wave + blargg_ulong wave = poly5; + check( poly5 & 1 ); // low bit is set for pure wave + int poly5_inc = 0; + if ( !(osc_control & 0x80) ) + { + wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len ); + poly5_inc = period % poly5_len; + } + + // Run wave and high pass interleved with each catching up to the other. + // Disabled high pass has no performance effect since inner wave loop + // makes no compromise for high pass, and only runs once in that case. + int osc_last_amp = osc->last_amp; + do + { + // run high pass + if ( time2 < time ) + { + int delta = -osc_last_amp; + if ( volume < 0 ) + delta += volume; + if ( delta ) + { + osc_last_amp += delta - volume; + volume = -volume; + impl->synth.offset( time2, delta, output ); + } + } + while ( time2 <= time ) // must advance *past* time to avoid hang + time2 += period2; + + // run wave + blip_time_t end = end_time; + if ( end > time2 ) + end = time2; + while ( time < end ) + { + if ( wave & 1 ) + { + int amp = volume & -(poly [poly_pos >> 3] >> (poly_pos & 7) & 1); + if ( (poly_pos += poly_inc) < 0 ) + poly_pos += poly_len; + int delta = amp - osc_last_amp; + if ( delta ) + { + osc_last_amp = amp; + impl->synth.offset( time, delta, output ); + } + } + wave = run_poly5( wave, poly5_inc ); + time += period; + } + } + while ( time < end_time || time2 < end_time ); + + osc->phase = poly_pos; + osc->last_amp = osc_last_amp; + } + + osc->invert = 0; + if ( volume < 0 ) + { + // undo inversion trickery + osc->last_amp -= volume; + osc->invert = 1; + } + } + } + + // maintain divider + blip_time_t remain = end_time - time; + if ( remain > 0 ) + { + blargg_long count = (remain + period - 1) / period; + osc->phase ^= count; + time += count * period; + } + osc->delay = time - end_time; + } + + // advance polies + blip_time_t duration = end_time - last_time; + last_time = end_time; + poly4_pos = (poly4_pos + duration) % poly4_len; + poly5_pos = (poly5_pos + duration) % poly5_len; + polym_pos += duration; // will get %'d on next call +} + +void Sap_Apu::write_data( blip_time_t time, unsigned addr, int data ) +{ + run_until( time ); + int i = (addr ^ 0xD200) >> 1; + if ( i < osc_count ) + { + oscs [i].regs [addr & 1] = data; + } + else if ( addr == 0xD208 ) + { + control = data; + } + else if ( addr == 0xD209 ) + { + oscs [0].delay = 0; + oscs [1].delay = 0; + oscs [2].delay = 0; + oscs [3].delay = 0; + } + /* + // TODO: are polynomials reset in this case? + else if ( addr == 0xD20F ) + { + if ( (data & 3) == 0 ) + polym_pos = 0; + } + */ +} + +void Sap_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + last_time -= end_time; +} diff --git a/game-music-emu/gme/Sap_Apu.h b/game-music-emu/gme/Sap_Apu.h new file mode 100644 index 000000000..c71ce31ed --- /dev/null +++ b/game-music-emu/gme/Sap_Apu.h @@ -0,0 +1,77 @@ +// Atari POKEY sound chip emulator + +// Game_Music_Emu 0.5.2 +#ifndef SAP_APU_H +#define SAP_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Sap_Apu_Impl; + +class Sap_Apu { +public: + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* ); + + void reset( Sap_Apu_Impl* ); + + enum { start_addr = 0xD200 }; + enum { end_addr = 0xD209 }; + void write_data( blip_time_t, unsigned addr, int data ); + + void end_frame( blip_time_t ); + +public: + Sap_Apu(); +private: + struct osc_t + { + unsigned char regs [2]; + unsigned char phase; + unsigned char invert; + int last_amp; + blip_time_t delay; + blip_time_t period; // always recalculated before use; here for convenience + Blip_Buffer* output; + }; + osc_t oscs [osc_count]; + Sap_Apu_Impl* impl; + blip_time_t last_time; + int poly5_pos; + int poly4_pos; + int polym_pos; + int control; + + void calc_periods(); + void run_until( blip_time_t ); + + enum { poly4_len = (1L << 4) - 1 }; + enum { poly9_len = (1L << 9) - 1 }; + enum { poly17_len = (1L << 17) - 1 }; + friend class Sap_Apu_Impl; +}; + +// Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects +class Sap_Apu_Impl { +public: + Blip_Synth synth; + + Sap_Apu_Impl(); + void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); } + +private: + typedef unsigned char byte; + byte poly4 [Sap_Apu::poly4_len / 8 + 1]; + byte poly9 [Sap_Apu::poly9_len / 8 + 1]; + byte poly17 [Sap_Apu::poly17_len / 8 + 1]; + friend class Sap_Apu; +}; + +inline void Sap_Apu::osc_output( int i, Blip_Buffer* b ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = b; +} + +#endif diff --git a/game-music-emu/gme/Sap_Cpu.cpp b/game-music-emu/gme/Sap_Cpu.cpp new file mode 100644 index 000000000..10dc60618 --- /dev/null +++ b/game-music-emu/gme/Sap_Cpu.cpp @@ -0,0 +1,1011 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Sap_Cpu.h" + +#include +#include "blargg_endian.h" + +//#include "nes_cpu_log.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define FLUSH_TIME() (void) (s.time = s_time) +#define CACHE_TIME() (void) (s_time = s.time) + +#include "sap_cpu_io.h" + +#ifndef CPU_DONE + #define CPU_DONE( cpu, time, result_out ) { result_out = -1; } +#endif + +#include "blargg_source.h" + +int const st_n = 0x80; +int const st_v = 0x40; +int const st_r = 0x20; +int const st_b = 0x10; +int const st_d = 0x08; +int const st_i = 0x04; +int const st_z = 0x02; +int const st_c = 0x01; + +void Sap_Cpu::reset( void* new_mem ) +{ + check( state == &state_ ); + state = &state_; + mem = (uint8_t*) new_mem; + r.status = st_i; + r.sp = 0xFF; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + state_.time = 0; + state_.base = 0; + irq_time_ = future_sap_time; + end_time_ = future_sap_time; + + blargg_verify_byte_order(); +} + +#define TIME (s_time + s.base) +#define READ( addr ) CPU_READ( this, (addr), TIME ) +#define WRITE( addr, data ) {CPU_WRITE( this, (addr), (data), TIME );} +#define READ_LOW( addr ) (mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) +#define READ_PROG( addr ) (READ_LOW( addr )) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +// even on x86, using short and unsigned char was slower +typedef int fint16; +typedef unsigned fuint16; +typedef unsigned fuint8; +typedef blargg_long fint32; + +bool Sap_Cpu::run( sap_time_t end_time ) +{ + bool illegal_encountered = false; + set_end_time( end_time ); + state_t s = this->state_; + this->state = &s; + fint32 s_time = s.time; + uint8_t* const mem = this->mem; // cache + + // registers + fuint16 pc = r.pc; + fuint8 a = r.a; + fuint8 x = r.x; + fuint8 y = r.y; + fuint16 sp; + SET_SP( r.sp ); + + // status flags + #define IS_NEG (nz & 0x8080) + + #define CALC_STATUS( out ) do {\ + out = status & (st_v | st_d | st_i);\ + out |= ((nz >> 8) | nz) & st_n;\ + out |= c >> 8 & st_c;\ + if ( !(nz & 0xFF) ) out |= st_z;\ + } while ( 0 ) + + #define SET_STATUS( in ) do {\ + status = in & (st_v | st_d | st_i);\ + nz = in << 8;\ + c = nz;\ + nz |= ~in & st_z;\ + } while ( 0 ) + + fuint8 status; + fuint16 c; // carry set if (c & 0x100) != 0 + fuint16 nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + fuint8 temp = r.status; + SET_STATUS( temp ); + } + + goto loop; +dec_clock_loop: + s_time--; +loop: + + #ifndef NDEBUG + { + sap_time_t correct = end_time_; + if ( !(status & st_i) && correct > irq_time_ ) + correct = irq_time_; + check( s.base == correct ); + } + #endif + + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + fuint8 opcode = mem [pc]; + pc++; + uint8_t const* instr = mem + pc; + + static uint8_t const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F + }; // 0x00 was 7 + + fuint16 data; + data = clock_table [opcode]; + if ( (s_time += data) >= 0 ) + goto possibly_out_of_time; +almost_out_of_time: + + data = *instr; + + #ifdef NES_CPU_LOG_H + nes_cpu_log( "cpu_log", pc - 1, opcode, instr [0], instr [1] ); + #endif + + switch ( opcode ) + { +possibly_out_of_time: + if ( s_time < (int) data ) + goto almost_out_of_time; + s_time -= data; + goto out_of_time; + +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE() (pc++, data += 0x100 * GET_MSB()) +#define GET_ADDR() GET_LE16( instr ) + +#define NO_PAGE_CROSSING( lsb ) +#define HANDLE_PAGE_CROSSING( lsb ) s_time += (lsb) >> 8; + +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + +#define IND_Y( cross, out ) {\ + fuint16 temp = READ_LOW( data ) + y;\ + out = temp + 0x100 * READ_LOW( uint8_t (data + 1) );\ + cross( temp );\ + } + +#define IND_X( out ) {\ + fuint16 temp = data + x;\ + out = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) );\ + } + +#define ARITH_ADDR_MODES( op )\ +case op - 0x04: /* (ind,x) */\ + IND_X( data )\ + goto ptr##op;\ +case op + 0x0C: /* (ind),y */\ + IND_Y( HANDLE_PAGE_CROSSING, data )\ + goto ptr##op;\ +case op + 0x10: /* zp,X */\ + data = uint8_t (data + x);\ +case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ +case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ +case op + 0x18: /* abs,X */\ + data += x;\ +ind##op:\ + HANDLE_PAGE_CROSSING( data );\ +case op + 0x08: /* abs */\ + ADD_PAGE();\ +ptr##op:\ + FLUSH_TIME();\ + data = READ( data );\ + CACHE_TIME();\ +case op + 0x04: /* imm */\ +imm##op: + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + fint16 offset = (BOOST::int8_t) data;\ + fuint16 extra_clock = (++pc & 0xFF) + offset;\ + if ( !(cond) ) goto dec_clock_loop;\ + pc += offset;\ + s_time += extra_clock >> 8 & 1;\ + goto loop;\ +} + +// Often-Used + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( uint8_t (data + x) ); + pc++; + goto loop; + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x20: { // JSR + fuint16 temp = pc + 1; + pc = GET_ADDR(); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0xE8: // INX + INC_DEC_XY( x, 1 ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ); + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: // INY + INC_DEC_XY( y, 1 ) + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAD:{// LDA abs + unsigned addr = GET_ADDR(); + pc += 2; + nz = READ( addr ); + a = nz; + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_LOW( sp ); + pc += 0x100 * READ_LOW( 0x100 | (sp - 0xFF) ); + sp = (sp - 0xFE) | 0x100; + goto loop; + + { + fuint16 addr; + + case 0x99: // STA abs,Y + addr = y + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x8D: // STA abs + addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + goto sta_ptr; + + case 0x9D: // STA abs,X (slightly more common than STA abs) + addr = x + GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, a ); + goto loop; + } + sta_ptr: + FLUSH_TIME(); + WRITE( addr, a ); + CACHE_TIME(); + goto loop; + + case 0x91: // STA (ind),Y + IND_Y( NO_PAGE_CROSSING, addr ) + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X( addr ) + pc++; + goto sta_ptr; + + } + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + + // common read instructions + { + fuint16 addr; + + case 0xA1: // LDA (ind,X) + IND_X( addr ) + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + HANDLE_PAGE_CROSSING( addr ); + addr += 0x100 * READ_LOW( (uint8_t) (data + 1) ); + pc++; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + HANDLE_PAGE_CROSSING( data + y ); + addr = GET_ADDR() + y; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + goto a_nz_read_addr; + + case 0xBD: // LDA abs,X + HANDLE_PAGE_CROSSING( data + x ); + addr = GET_ADDR() + x; + pc += 2; + a = nz = READ_PROG( addr ); + if ( (addr ^ 0x8000) <= 0x9FFF ) + goto loop; + a_nz_read_addr: + FLUSH_TIME(); + a = nz = READ( addr ); + CACHE_TIME(); + goto loop; + + } + +// Branch + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + unsigned addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + x = nz = READ( addr ); + CACHE_TIME(); + goto loop; + } + + { + fuint8 temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + unsigned addr = GET_ADDR(); + pc += 2; + if ( addr <= 0x7FF ) + { + WRITE_LOW( addr, temp ); + goto loop; + } + FLUSH_TIME(); + WRITE( addr, temp ); + CACHE_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + unsigned addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc += 2; + status &= ~st_v; + nz = READ( addr ); + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + check( !(status & st_d) ); + fint16 carry = c >> 8 & 1; + fint16 ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + status &= ~st_v; + status |= ov >> 2 & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + fint16 temp = c >> 8 & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE(); + FLUSH_TIME(); + int temp = READ( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE(); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz |= (c = READ( data ) << 1); + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: // DEX + INC_DEC_XY( x, -1 ) + + case 0x88: // DEY + INC_DEC_XY( y, -1 ) + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = (unsigned) -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = (unsigned) -1; + inc_common: + FLUSH_TIME(); + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xAA: // TAX + x = a; + nz = a; + goto loop; + + case 0x8A: // TXA + a = x; + nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + goto loop; + + case 0x40:{// RTI + fuint8 temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xFF) ); + pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; + sp = (sp - 0xFD) | 0x100; + data = status; + SET_STATUS( temp ); + this->r.status = status; // update externally-visible I flag + if ( (data ^ status) & st_i ) + { + sap_time_t new_time = end_time_; + if ( !(status & st_i) && new_time > irq_time_ ) + new_time = irq_time_; + blargg_long delta = s.base - new_time; + s.base = new_time; + s_time += delta; + } + goto loop; + } + + case 0x28:{// PLP + fuint8 temp = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + fuint8 changed = status ^ temp; + SET_STATUS( temp ); + if ( !(changed & st_i) ) + goto loop; // I flag didn't change + if ( status & st_i ) + goto handle_sei; + goto handle_cli; + } + + case 0x08: { // PHP + fuint8 temp; + CALC_STATUS( temp ); + PUSH( temp | (st_b | st_r) ); + goto loop; + } + + case 0x6C:{// JMP (ind) + data = GET_ADDR(); + pc = READ_PROG( data ); + data = (data & 0xFF00) | ((data + 1) & 0xFF); + pc |= 0x100 * READ_PROG( data ); + goto loop; + } + + case 0x00: // BRK + goto handle_brk; + +// Flags + + case 0x38: // SEC + c = (unsigned) ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - irq_time_; + if ( delta <= 0 ) + { + if ( TIME < irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + irq_time_ = s.base; // TODO: remove, as only to satisfy debug check in loop + goto loop; + } + delayed_cli: + dprintf( "Delayed CLI not emulated\n" ); + goto loop; + } + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: { + this->r.status = status; // update externally-visible I flag + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + dprintf( "Delayed SEI not emulated\n" ); + goto loop; + } + +// Unofficial + + // SKW - Skip word + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: + HANDLE_PAGE_CROSSING( data + x ); + case 0x0C: + pc++; + // SKB - Skip byte + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + goto loop; + + // NOP + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: + goto loop; + +// Unimplemented + + // halt + //case 0x02: case 0x12: case 0x22: case 0x32: case 0x42: case 0x52: + //case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2: + + default: + assert( (unsigned) opcode <= 0xFF ); + illegal_encountered = true; + pc--; + goto stop; + } + assert( false ); + + int result_; +handle_brk: + if ( (pc - 1) >= idle_addr ) + goto idle_done; + pc++; + result_ = 4; + dprintf( "BRK executed\n" ); + +interrupt: + { + s_time += 7; + + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + pc = GET_LE16( &READ_PROG( 0xFFFA ) + result_ ); + + sp = (sp - 3) | 0x100; + fuint8 temp; + CALC_STATUS( temp ); + temp |= st_r; + if ( result_ ) + temp |= st_b; // TODO: incorrectly sets B flag for IRQ + WRITE_LOW( sp, temp ); + + status &= ~st_d; + status |= st_i; + this->r.status = status; // update externally-visible I flag + + blargg_long delta = s.base - end_time_; + s.base = end_time_; + s_time += delta; + goto loop; + } + +idle_done: + //s_time = 0; + pc--; + goto stop; +out_of_time: + pc--; + FLUSH_TIME(); + CPU_DONE( this, TIME, result_ ); + CACHE_TIME(); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + +stop: + + s.time = s_time; + + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + + { + fuint8 temp; + CALC_STATUS( temp ); + r.status = temp; + } + + this->state_ = s; + this->state = &this->state_; + + return illegal_encountered; +} + diff --git a/game-music-emu/gme/Sap_Cpu.h b/game-music-emu/gme/Sap_Cpu.h new file mode 100644 index 000000000..433a4e3d3 --- /dev/null +++ b/game-music-emu/gme/Sap_Cpu.h @@ -0,0 +1,83 @@ +// Atari 6502 CPU emulator + +// Game_Music_Emu 0.5.2 +#ifndef SAP_CPU_H +#define SAP_CPU_H + +#include "blargg_common.h" + +typedef blargg_long sap_time_t; // clock cycle count +typedef unsigned sap_addr_t; // 16-bit address +enum { future_sap_time = INT_MAX / 2 + 1 }; + +class Sap_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + // Clear all registers and keep pointer to 64K memory passed in + void reset( void* mem_64k ); + + // Run until specified time is reached. Returns true if suspicious/unsupported + // instruction was encountered at any point during run. + bool run( sap_time_t end_time ); + + // Registers are not updated until run() returns (except I flag in status) + struct registers_t { + BOOST::uint16_t pc; + BOOST::uint8_t a; + BOOST::uint8_t x; + BOOST::uint8_t y; + BOOST::uint8_t status; + BOOST::uint8_t sp; + }; + registers_t r; + + enum { idle_addr = 0xFEFF }; + + // Time of beginning of next instruction to be executed + sap_time_t time() const { return state->time + state->base; } + void set_time( sap_time_t t ) { state->time = t - state->base; } + void adjust_time( int delta ) { state->time += delta; } + + sap_time_t irq_time() const { return irq_time_; } + void set_irq_time( sap_time_t ); + + sap_time_t end_time() const { return end_time_; } + void set_end_time( sap_time_t ); + +public: + Sap_Cpu() { state = &state_; } + enum { irq_inhibit = 0x04 }; +private: + struct state_t { + sap_time_t base; + sap_time_t time; + }; + state_t* state; // points to state_ or a local copy within run() + state_t state_; + sap_time_t irq_time_; + sap_time_t end_time_; + uint8_t* mem; + + inline sap_time_t update_end_time( sap_time_t end, sap_time_t irq ); +}; + +inline sap_time_t Sap_Cpu::update_end_time( sap_time_t t, sap_time_t irq ) +{ + if ( irq < t && !(r.status & irq_inhibit) ) t = irq; + sap_time_t delta = state->base - t; + state->base = t; + return delta; +} + +inline void Sap_Cpu::set_irq_time( sap_time_t t ) +{ + state->time += update_end_time( end_time_, (irq_time_ = t) ); +} + +inline void Sap_Cpu::set_end_time( sap_time_t t ) +{ + state->time += update_end_time( (end_time_ = t), irq_time_ ); +} + +#endif diff --git a/game-music-emu/gme/Sap_Emu.cpp b/game-music-emu/gme/Sap_Emu.cpp new file mode 100644 index 000000000..dfee3de72 --- /dev/null +++ b/game-music-emu/gme/Sap_Emu.cpp @@ -0,0 +1,444 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Sap_Emu.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +long const base_scanline_period = 114; + +Sap_Emu::Sap_Emu() +{ + set_type( gme_sap_type ); + + static const char* const names [Sap_Apu::osc_count * 2] = { + "Wave 1", "Wave 2", "Wave 3", "Wave 4", + "Wave 5", "Wave 6", "Wave 7", "Wave 8", + }; + set_voice_names( names ); + + static int const types [Sap_Apu::osc_count * 2] = { + wave_type | 1, wave_type | 2, wave_type | 3, wave_type | 0, + wave_type | 5, wave_type | 6, wave_type | 7, wave_type | 4, + }; + set_voice_types( types ); + set_silence_lookahead( 6 ); +} + +Sap_Emu::~Sap_Emu() { } + +// Track info + +// Returns 16 or greater if not hex +inline int from_hex_char( int h ) +{ + h -= 0x30; + if ( (unsigned) h > 9 ) + h = ((h - 0x11) & 0xDF) + 10; + return h; +} + +static long from_hex( byte const* in ) +{ + unsigned result = 0; + for ( int n = 4; n--; ) + { + int h = from_hex_char( *in++ ); + if ( h > 15 ) + return -1; + result = result * 0x10 + h; + } + return result; +} + +static int from_dec( byte const* in, byte const* end ) +{ + if ( in >= end ) + return -1; + + int n = 0; + while ( in < end ) + { + int dig = *in++ - '0'; + if ( (unsigned) dig > 9 ) + return -1; + n = n * 10 + dig; + } + return n; +} + +static void parse_string( byte const* in, byte const* end, int len, char* out ) +{ + byte const* start = in; + if ( *in++ == '\"' ) + { + start++; + while ( in < end && *in != '\"' ) + in++; + } + else + { + in = end; + } + len = min( len - 1, int (in - start) ); + out [len] = 0; + memcpy( out, start, len ); +} + +static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out ) +{ + out->track_count = 1; + out->author [0] = 0; + out->name [0] = 0; + out->copyright [0] = 0; + + if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) ) + return gme_wrong_file_type; + + byte const* file_end = in + size - 5; + in += 5; + while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) ) + { + byte const* line_end = in; + while ( line_end < file_end && *line_end != 0x0D ) + line_end++; + + char const* tag = (char const*) in; + while ( in < line_end && *in > ' ' ) + in++; + int tag_len = int((char const*) in - tag); + + while ( in < line_end && *in <= ' ' ) in++; + + if ( tag_len <= 0 ) + { + // skip line + } + else if ( !strncmp( "INIT", tag, tag_len ) ) + { + out->init_addr = from_hex( in ); + if ( (unsigned long) out->init_addr > 0xFFFF ) + return "Invalid init address"; + } + else if ( !strncmp( "PLAYER", tag, tag_len ) ) + { + out->play_addr = from_hex( in ); + if ( (unsigned long) out->play_addr > 0xFFFF ) + return "Invalid play address"; + } + else if ( !strncmp( "MUSIC", tag, tag_len ) ) + { + out->music_addr = from_hex( in ); + if ( (unsigned long) out->music_addr > 0xFFFF ) + return "Invalid music address"; + } + else if ( !strncmp( "SONGS", tag, tag_len ) ) + { + out->track_count = from_dec( in, line_end ); + if ( out->track_count <= 0 ) + return "Invalid track count"; + } + else if ( !strncmp( "TYPE", tag, tag_len ) ) + { + switch ( out->type = *in ) + { + case 'C': + case 'B': + break; + + case 'D': + return "Digimusic not supported"; + + default: + return "Unsupported player type"; + } + } + else if ( !strncmp( "STEREO", tag, tag_len ) ) + { + out->stereo = true; + } + else if ( !strncmp( "FASTPLAY", tag, tag_len ) ) + { + out->fastplay = from_dec( in, line_end ); + if ( out->fastplay <= 0 ) + return "Invalid fastplay value"; + } + else if ( !strncmp( "AUTHOR", tag, tag_len ) ) + { + parse_string( in, line_end, sizeof out->author, out->author ); + } + else if ( !strncmp( "NAME", tag, tag_len ) ) + { + parse_string( in, line_end, sizeof out->name, out->name ); + } + else if ( !strncmp( "DATE", tag, tag_len ) ) + { + parse_string( in, line_end, sizeof out->copyright, out->copyright ); + } + + in = line_end + 2; + } + + if ( in [0] != 0xFF || in [1] != 0xFF ) + return "ROM data missing"; + out->rom_data = in + 2; + + return 0; +} + +static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out ) +{ + Gme_File::copy_field_( out->game, in.name ); + Gme_File::copy_field_( out->author, in.author ); + Gme_File::copy_field_( out->copyright, in.copyright ); +} + +blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const +{ + copy_sap_fields( info, out ); + return 0; +} + +struct Sap_File : Gme_Info_ +{ + Sap_Emu::info_t info; + + Sap_File() { set_type( gme_sap_type ); } + + blargg_err_t load_mem_( byte const* begin, long size ) + { + RETURN_ERR( parse_info( begin, size, &info ) ); + set_track_count( info.track_count ); + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_sap_fields( info, out ); + return 0; + } +}; + +static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; } +static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; } + +static gme_type_t_ const gme_sap_type_ = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 }; +gme_type_t const gme_sap_type = &gme_sap_type_; + + +// Setup + +blargg_err_t Sap_Emu::load_mem_( byte const* in, long size ) +{ + file_end = in + size; + + info.warning = 0; + info.type = 'B'; + info.stereo = false; + info.init_addr = -1; + info.play_addr = -1; + info.music_addr = -1; + info.fastplay = 312; + RETURN_ERR( parse_info( in, size, &info ) ); + + set_warning( info.warning ); + set_track_count( info.track_count ); + set_voice_count( Sap_Apu::osc_count << (int)info.stereo ); + apu_impl.volume( gain() ); + + return setup_buffer( 1773447 ); +} + +void Sap_Emu::update_eq( blip_eq_t const& eq ) +{ + apu_impl.synth.treble_eq( eq ); +} + +void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + int i2 = i - Sap_Apu::osc_count; + if ( i2 >= 0 ) + apu2.osc_output( i2, right ); + else + apu.osc_output( i, (info.stereo ? left : center) ); +} + +// Emulation + +void Sap_Emu::set_tempo_( double t ) +{ + scanline_period = sap_time_t (base_scanline_period / t); +} + +inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; } + +void Sap_Emu::cpu_jsr( sap_addr_t addr ) +{ + check( r.sp >= 0xFE ); // catch anything trying to leave data on stack + r.pc = addr; + int high_byte = (idle_addr - 1) >> 8; + if ( r.sp == 0xFE && mem.ram [0x1FF] == high_byte ) + r.sp = 0xFF; // pop extra byte off + mem.ram [0x100 + r.sp--] = high_byte; // some routines use RTI to return + mem.ram [0x100 + r.sp--] = high_byte; + mem.ram [0x100 + r.sp--] = (idle_addr - 1) & 0xFF; +} + +void Sap_Emu::run_routine( sap_addr_t addr ) +{ + cpu_jsr( addr ); + cpu::run( 312 * base_scanline_period * 60 ); + check( r.pc == idle_addr ); +} + +inline void Sap_Emu::call_init( int track ) +{ + switch ( info.type ) + { + case 'B': + r.a = track; + run_routine( info.init_addr ); + break; + + case 'C': + r.a = 0x70; + r.x = (BOOST::uint8_t)(info.music_addr&0xFF); + r.y = (BOOST::uint8_t)(info.music_addr >> 8); + run_routine( info.play_addr + 3 ); + r.a = 0; + r.x = track; + run_routine( info.play_addr + 3 ); + break; + } +} + +blargg_err_t Sap_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + memset( &mem, 0, sizeof mem ); + + byte const* in = info.rom_data; + while ( file_end - in >= 5 ) + { + unsigned start = get_le16( in ); + unsigned end = get_le16( in + 2 ); + //dprintf( "Block $%04X-$%04X\n", start, end ); + in += 4; + if ( end < start ) + { + set_warning( "Invalid file data block" ); + break; + } + long len = end - start + 1; + if ( len > file_end - in ) + { + set_warning( "Invalid file data block" ); + break; + } + + memcpy( mem.ram + start, in, len ); + in += len; + if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF ) + in += 2; + } + + apu.reset( &apu_impl ); + apu2.reset( &apu_impl ); + cpu::reset( mem.ram ); + time_mask = 0; // disables sound during init + call_init( track ); + time_mask = -1; + + next_play = play_period(); + + return 0; +} + +// Emulation + +// see sap_cpu_io.h for read/write functions + +void Sap_Emu::cpu_write_( sap_addr_t addr, int data ) +{ + if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) ) + { + GME_APU_HOOK( this, addr - Sap_Apu::start_addr, data ); + apu.write_data( time() & time_mask, addr, data ); + return; + } + + if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) && + info.stereo ) + { + GME_APU_HOOK( this, addr - 0x10 - Sap_Apu::start_addr + 10, data ); + apu2.write_data( time() & time_mask, addr ^ 0x10, data ); + return; + } + + if ( (addr & ~0x0010) != 0xD20F || data != 0x03 ) + dprintf( "Unmapped write $%04X <- $%02X\n", addr, data ); +} + +inline void Sap_Emu::call_play() +{ + switch ( info.type ) + { + case 'B': + cpu_jsr( info.play_addr ); + break; + + case 'C': + cpu_jsr( info.play_addr + 6 ); + break; + } +} + +blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int ) +{ + set_time( 0 ); + while ( time() < duration ) + { + if ( cpu::run( duration ) || r.pc > idle_addr ) + return "Emulation error (illegal instruction)"; + + if ( r.pc == idle_addr ) + { + if ( next_play <= duration ) + { + set_time( next_play ); + next_play += play_period(); + call_play(); + GME_FRAME_HOOK( this ); + } + else + { + set_time( duration ); + } + } + } + + duration = time(); + next_play -= duration; + check( next_play >= 0 ); + if ( next_play < 0 ) + next_play = 0; + apu.end_frame( duration ); + if ( info.stereo ) + apu2.end_frame( duration ); + + return 0; +} diff --git a/game-music-emu/gme/Sap_Emu.h b/game-music-emu/gme/Sap_Emu.h new file mode 100644 index 000000000..4878faa66 --- /dev/null +++ b/game-music-emu/gme/Sap_Emu.h @@ -0,0 +1,69 @@ +// Atari XL/XE SAP music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef SAP_EMU_H +#define SAP_EMU_H + +#include "Classic_Emu.h" +#include "Sap_Apu.h" +#include "Sap_Cpu.h" + +class Sap_Emu : private Sap_Cpu, public Classic_Emu { + typedef Sap_Cpu cpu; +public: + static gme_type_t static_type() { return gme_sap_type; } +public: + Sap_Emu(); + ~Sap_Emu(); + struct info_t { + byte const* rom_data; + const char* warning; + long init_addr; + long play_addr; + long music_addr; + int type; + int track_count; + int fastplay; + bool stereo; + char author [256]; + char name [256]; + char copyright [ 32]; + }; +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t start_track_( int ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); +public: private: friend class Sap_Cpu; + int cpu_read( sap_addr_t ); + void cpu_write( sap_addr_t, int ); + void cpu_write_( sap_addr_t, int ); +private: + info_t info; + + byte const* file_end; + sap_time_t scanline_period; + sap_time_t next_play; + sap_time_t time_mask; + Sap_Apu apu; + Sap_Apu apu2; + + // large items + struct { + byte padding1 [0x100]; + byte ram [0x10000]; + byte padding2 [0x100]; + } mem; + Sap_Apu_Impl apu_impl; + + sap_time_t play_period() const; + void call_play(); + void cpu_jsr( sap_addr_t ); + void call_init( int track ); + void run_routine( sap_addr_t ); +}; + +#endif diff --git a/game-music-emu/gme/Sms_Apu.cpp b/game-music-emu/gme/Sms_Apu.cpp new file mode 100644 index 000000000..be226f8f9 --- /dev/null +++ b/game-music-emu/gme/Sms_Apu.cpp @@ -0,0 +1,330 @@ +// Sms_Snd_Emu 0.1.4. http://www.slack.net/~ant/ + +#include "Sms_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Sms_Osc + +Sms_Osc::Sms_Osc() +{ + output = 0; + outputs [0] = 0; // always stays NULL + outputs [1] = 0; + outputs [2] = 0; + outputs [3] = 0; +} + +void Sms_Osc::reset() +{ + delay = 0; + last_amp = 0; + volume = 0; + output_select = 3; + output = outputs [3]; +} + +// Sms_Square + +inline void Sms_Square::reset() +{ + period = 0; + phase = 0; + Sms_Osc::reset(); +} + +void Sms_Square::run( blip_time_t time, blip_time_t end_time ) +{ + if ( !volume || period <= 128 ) + { + // ignore 16kHz and higher + if ( last_amp ) + { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + time += delay; + if ( !period ) + { + time = end_time; + } + else if ( time < end_time ) + { + // keep calculating phase + int count = (end_time - time + period - 1) / period; + phase = (phase + count) & 1; + time += count * period; + } + } + else + { + int amp = phase ? volume : -volume; + { + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth->offset( time, delta, output ); + } + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + int delta = amp * 2; + do + { + delta = -delta; + synth->offset_inline( time, delta, output ); + time += period; + phase ^= 1; + } + while ( time < end_time ); + this->last_amp = phase ? volume : -volume; + } + } + delay = time - end_time; +} + +// Sms_Noise + +static int const noise_periods [3] = { 0x100, 0x200, 0x400 }; + +inline void Sms_Noise::reset() +{ + period = &noise_periods [0]; + shifter = 0x8000; + feedback = 0x9000; + Sms_Osc::reset(); +} + +void Sms_Noise::run( blip_time_t time, blip_time_t end_time ) +{ + int amp = volume; + if ( shifter & 1 ) + amp = -amp; + + { + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset( time, delta, output ); + } + } + + time += delay; + if ( !volume ) + time = end_time; + + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + unsigned shifter = this->shifter; + int delta = amp * 2; + int period = *this->period * 2; + if ( !period ) + period = 16; + + do + { + int changed = shifter + 1; + shifter = (feedback & (unsigned)-(signed)(shifter & 1)) ^ (shifter >> 1); + if ( changed & 2 ) // true if bits 0 and 1 differ + { + delta = -delta; + synth.offset_inline( time, delta, output ); + } + time += period; + } + while ( time < end_time ); + + this->shifter = shifter; + this->last_amp = delta >> 1; + } + delay = time - end_time; +} + +// Sms_Apu + +Sms_Apu::Sms_Apu() +{ + for ( int i = 0; i < 3; i++ ) + { + squares [i].synth = &square_synth; + oscs [i] = &squares [i]; + } + oscs [3] = &noise; + + volume( 1.0 ); + reset(); +} + +Sms_Apu::~Sms_Apu() +{ +} + +void Sms_Apu::volume( double vol ) +{ + vol *= 0.85 / (osc_count * 64 * 2); + square_synth.volume( vol ); + noise.synth.volume( vol ); +} + +void Sms_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); +} + +void Sms_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + require( (center && left && right) || (!center && !left && !right) ); + Sms_Osc& osc = *oscs [index]; + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Sms_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Sms_Apu::reset( unsigned feedback, int noise_width ) +{ + last_time = 0; + latch = 0; + + if ( !feedback || !noise_width ) + { + feedback = 0x0009; + noise_width = 16; + } + // convert to "Galios configuration" + looped_feedback = 1 << (noise_width - 1); + noise_feedback = 0; + while ( noise_width-- ) + { + noise_feedback = (noise_feedback << 1) | (feedback & 1); + feedback >>= 1; + } + + squares [0].reset(); + squares [1].reset(); + squares [2].reset(); + noise.reset(); +} + +void Sms_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + + if ( end_time > last_time ) + { + // run oscillators + for ( int i = 0; i < osc_count; ++i ) + { + Sms_Osc& osc = *oscs [i]; + if ( osc.output ) + { + osc.output->set_modified(); + if ( i < 3 ) + squares [i].run( last_time, end_time ); + else + noise.run( last_time, end_time ); + } + } + + last_time = end_time; + } +} + +void Sms_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + assert( last_time >= end_time ); + last_time -= end_time; +} + +void Sms_Apu::write_ggstereo( blip_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + for ( int i = 0; i < osc_count; i++ ) + { + Sms_Osc& osc = *oscs [i]; + int flags = data >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = (flags >> 3 & 2) | (flags & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output && osc.last_amp ) + { + if ( old_output ) + { + old_output->set_modified(); + square_synth.offset( time, -osc.last_amp, old_output ); + } + osc.last_amp = 0; + } + } +} + +// volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) +static unsigned char const volumes [16] = { + 64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0 +}; + +void Sms_Apu::write_data( blip_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + if ( data & 0x80 ) + latch = data; + + int index = (latch >> 5) & 3; + if ( latch & 0x10 ) + { + oscs [index]->volume = volumes [data & 15]; + } + else if ( index < 3 ) + { + Sms_Square& sq = squares [index]; + if ( data & 0x80 ) + sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF); + else + sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00); + } + else + { + int select = data & 3; + if ( select < 3 ) + noise.period = &noise_periods [select]; + else + noise.period = &squares [2].period; + + noise.feedback = (data & 0x04) ? noise_feedback : looped_feedback; + noise.shifter = 0x8000; + } +} diff --git a/game-music-emu/gme/Sms_Apu.h b/game-music-emu/gme/Sms_Apu.h new file mode 100644 index 000000000..3c11a9c3c --- /dev/null +++ b/game-music-emu/gme/Sms_Apu.h @@ -0,0 +1,75 @@ +// Sega Master System SN76489 PSG sound chip emulator + +// Sms_Snd_Emu 0.1.4 +#ifndef SMS_APU_H +#define SMS_APU_H + +#include "Sms_Oscs.h" + +class Sms_Apu { +public: + // Set overall volume of all oscillators, where 1.0 is full volume + void volume( double ); + + // Set treble equalization + void treble_eq( const blip_eq_t& ); + + // Outputs can be assigned to a single buffer for mono output, or to three + // buffers for stereo output (using Stereo_Buffer to do the mixing). + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silences all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, + // which refer to Square 1, Square 2, Square 3, and Noise. If buffer is NULL, + // silences oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reset oscillators and internal state + void reset( unsigned noise_feedback = 0, int noise_width = 0 ); + + // Write GameGear left/right assignment byte + void write_ggstereo( blip_time_t, int ); + + // Write to data port + void write_data( blip_time_t, int ); + + // Run all oscillators up to specified time, end current frame, then + // start a new frame at time 0. + void end_frame( blip_time_t ); + +public: + Sms_Apu(); + ~Sms_Apu(); +private: + // noncopyable + Sms_Apu( const Sms_Apu& ); + Sms_Apu& operator = ( const Sms_Apu& ); + + Sms_Osc* oscs [osc_count]; + Sms_Square squares [3]; + Sms_Square::Synth square_synth; // used by squares + blip_time_t last_time; + int latch; + Sms_Noise noise; + unsigned noise_feedback; + unsigned looped_feedback; + + void run_until( blip_time_t ); +}; + +struct sms_apu_state_t +{ + unsigned char regs [8] [2]; + unsigned char latch; +}; + +inline void Sms_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } + +inline void Sms_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } + +#endif diff --git a/game-music-emu/gme/Sms_Oscs.h b/game-music-emu/gme/Sms_Oscs.h new file mode 100644 index 000000000..2a896fef3 --- /dev/null +++ b/game-music-emu/gme/Sms_Oscs.h @@ -0,0 +1,49 @@ +// Private oscillators used by Sms_Apu + +// Sms_Snd_Emu 0.1.4 +#ifndef SMS_OSCS_H +#define SMS_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct Sms_Osc +{ + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + + int delay; + int last_amp; + int volume; + + Sms_Osc(); + void reset(); +}; + +struct Sms_Square : Sms_Osc +{ + int period; + int phase; + + typedef Blip_Synth Synth; + const Synth* synth; + + void reset(); + void run( blip_time_t, blip_time_t ); +}; + +struct Sms_Noise : Sms_Osc +{ + const int* period; + unsigned shifter; + unsigned feedback; + + typedef Blip_Synth Synth; + Synth synth; + + void reset(); + void run( blip_time_t, blip_time_t ); +}; + +#endif diff --git a/snes_spc/snes_spc/SNES_SPC_misc.cpp b/game-music-emu/gme/Snes_Spc.cpp similarity index 88% rename from snes_spc/snes_spc/SNES_SPC_misc.cpp rename to game-music-emu/gme/Snes_Spc.cpp index 0ef0275e5..d23e2bbe9 100644 --- a/snes_spc/snes_spc/SNES_SPC_misc.cpp +++ b/game-music-emu/gme/Snes_Spc.cpp @@ -2,7 +2,7 @@ // snes_spc 0.9.0. http://www.slack.net/~ant/ -#include "SNES_SPC.h" +#include "Snes_Spc.h" #include @@ -29,7 +29,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ //// Init -blargg_err_t SNES_SPC::init() +blargg_err_t Snes_Spc::init() { memset( &m, 0, sizeof m ); dsp.init( RAM ); @@ -77,12 +77,12 @@ blargg_err_t SNES_SPC::init() return 0; } -void SNES_SPC::init_rom( uint8_t const in [rom_size] ) +void Snes_Spc::init_rom( uint8_t const in [rom_size] ) { memcpy( m.rom, in, sizeof m.rom ); } -void SNES_SPC::set_tempo( int t ) +void Snes_Spc::set_tempo( int t ) { m.tempo = t; int const timer2_shift = 4; // 64 kHz @@ -107,7 +107,7 @@ void SNES_SPC::set_tempo( int t ) // Timer registers have been loaded. Applies these to the timers. Does not // reset timer prescalers or dividers. -void SNES_SPC::timers_loaded() +void Snes_Spc::timers_loaded() { int i; for ( i = 0; i < timer_count; i++ ) @@ -122,7 +122,7 @@ void SNES_SPC::timers_loaded() } // Loads registers from unified 16-byte format -void SNES_SPC::load_regs( uint8_t const in [reg_count] ) +void Snes_Spc::load_regs( uint8_t const in [reg_count] ) { memcpy( REGS, in, reg_count ); memcpy( REGS_IN, REGS, reg_count ); @@ -137,7 +137,7 @@ void SNES_SPC::load_regs( uint8_t const in [reg_count] ) // RAM was just loaded from SPC, with $F0-$FF containing SMP registers // and timer counts. Copies these to proper registers. -void SNES_SPC::ram_loaded() +void Snes_Spc::ram_loaded() { m.rom_enabled = 0; load_regs( &RAM [0xF0] ); @@ -148,13 +148,13 @@ void SNES_SPC::ram_loaded() } // Registers were just loaded. Applies these new values. -void SNES_SPC::regs_loaded() +void Snes_Spc::regs_loaded() { enable_rom( REGS [r_control] & 0x80 ); timers_loaded(); } -void SNES_SPC::reset_time_regs() +void Snes_Spc::reset_time_regs() { m.cpu_error = 0; m.echo_accessed = 0; @@ -177,7 +177,7 @@ void SNES_SPC::reset_time_regs() reset_buf(); } -void SNES_SPC::reset_common( int timer_counter_init ) +void Snes_Spc::reset_common( int timer_counter_init ) { int i; for ( i = 0; i < timer_count; i++ ) @@ -195,13 +195,13 @@ void SNES_SPC::reset_common( int timer_counter_init ) reset_time_regs(); } -void SNES_SPC::soft_reset() +void Snes_Spc::soft_reset() { reset_common( 0 ); dsp.soft_reset(); } -void SNES_SPC::reset() +void Snes_Spc::reset() { memset( RAM, 0xFF, 0x10000 ); ram_loaded(); @@ -209,10 +209,10 @@ void SNES_SPC::reset() dsp.reset(); } -char const SNES_SPC::signature [signature_size + 1] = +char const Snes_Spc::signature [signature_size + 1] = "SNES-SPC700 Sound File Data v0.30\x1A\x1A"; -blargg_err_t SNES_SPC::load_spc( void const* data, long size ) +blargg_err_t Snes_Spc::load_spc( void const* data, long size ) { spc_file_t const* const spc = (spc_file_t const*) data; @@ -246,12 +246,12 @@ blargg_err_t SNES_SPC::load_spc( void const* data, long size ) return 0; } -void SNES_SPC::clear_echo() +void Snes_Spc::clear_echo() { - if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) ) + if ( !(dsp.read( Spc_Dsp::r_flg ) & 0x20) ) { - int addr = 0x100 * dsp.read( SPC_DSP::r_esa ); - int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F); + int addr = 0x100 * dsp.read( Spc_Dsp::r_esa ); + int end = addr + 0x800 * (dsp.read( Spc_Dsp::r_edl ) & 0x0F); if ( end > 0x10000 ) end = 0x10000; memset( &RAM [addr], 0xFF, end - addr ); @@ -261,7 +261,7 @@ void SNES_SPC::clear_echo() //// Sample output -void SNES_SPC::reset_buf() +void Snes_Spc::reset_buf() { // Start with half extra buffer of silence sample_t* out = m.extra_buf; @@ -274,7 +274,7 @@ void SNES_SPC::reset_buf() dsp.set_output( 0, 0 ); } -void SNES_SPC::set_output( sample_t* out, int size ) +void Snes_Spc::set_output( sample_t* out, int size ) { require( (size & 1) == 0 ); // size must be even @@ -311,7 +311,7 @@ void SNES_SPC::set_output( sample_t* out, int size ) } } -void SNES_SPC::save_extra() +void Snes_Spc::save_extra() { // Get end pointers sample_t const* main_end = m.buf_end; // end of data written to buf @@ -334,7 +334,7 @@ void SNES_SPC::save_extra() assert( out <= &m.extra_buf [extra_size] ); } -blargg_err_t SNES_SPC::play( int count, sample_t* out ) +blargg_err_t Snes_Spc::play( int count, sample_t* out ) { require( (count & 1) == 0 ); // must be even if ( count ) @@ -348,7 +348,7 @@ blargg_err_t SNES_SPC::play( int count, sample_t* out ) return err; } -blargg_err_t SNES_SPC::skip( int count ) +blargg_err_t Snes_Spc::skip( int count ) { #if SPC_LESS_ACCURATE if ( count > 2 * sample_rate * 2 ) @@ -370,8 +370,8 @@ blargg_err_t SNES_SPC::skip( int count ) end_frame( end ); m.dsp_time = m.dsp_time - skipping_time + old_dsp_time; - dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon ); - dsp.write( SPC_DSP::r_kon , m.skipped_kon ); + dsp.write( Spc_Dsp::r_koff, m.skipped_koff & ~m.skipped_kon ); + dsp.write( Spc_Dsp::r_kon , m.skipped_kon ); clear_echo(); } #endif diff --git a/snes_spc/snes_spc/SNES_SPC.h b/game-music-emu/gme/Snes_Spc.h similarity index 91% rename from snes_spc/snes_spc/SNES_SPC.h rename to game-music-emu/gme/Snes_Spc.h index 2bacfa8a8..93a593cc0 100644 --- a/snes_spc/snes_spc/SNES_SPC.h +++ b/game-music-emu/gme/Snes_Spc.h @@ -4,10 +4,10 @@ #ifndef SNES_SPC_H #define SNES_SPC_H -#include "SPC_DSP.h" +#include "Spc_Dsp.h" #include "blargg_endian.h" -struct SNES_SPC { +struct Snes_Spc { public: typedef BOOST::uint8_t uint8_t; @@ -66,7 +66,10 @@ public: // Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. enum { tempo_unit = 0x100 }; void set_tempo( int ); - + + enum { gain_unit = Spc_Dsp::gain_unit }; + void set_gain( int gain ); + // SPC music files // Loads SPC data into emulator @@ -89,7 +92,7 @@ public: #if !SPC_NO_COPY_STATE_FUNCS // Saves/loads state enum { state_size = 67 * 1024L }; // maximum space needed when saving - typedef SPC_DSP::copy_func_t copy_func_t; + typedef Spc_Dsp::copy_func_t copy_func_t; void copy_state( unsigned char** io, copy_func_t ); // Writes minimal header to spc_out @@ -125,13 +128,12 @@ public: }; enum { reg_count = 0x10 }; enum { timer_count = 3 }; - enum { extra_size = SPC_DSP::extra_size }; + enum { extra_size = Spc_Dsp::extra_size }; enum { signature_size = 35 }; - static char const signature [signature_size + 1]; - + private: - SPC_DSP dsp; + Spc_Dsp dsp; #if SPC_LESS_ACCURATE static signed char const reg_times_ [256]; @@ -248,31 +250,35 @@ private: uint8_t ipl_rom [0x40]; }; + static char const signature [signature_size + 1]; + void save_regs( uint8_t out [reg_count] ); }; #include -inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; } +inline int Snes_Spc::sample_count() const { return (m.extra_clocks >> 5) * 2; } -inline int SNES_SPC::read_port( time_t t, int port ) +inline int Snes_Spc::read_port( time_t t, int port ) { assert( (unsigned) port < port_count ); return run_until_( t ) [port]; } -inline void SNES_SPC::write_port( time_t t, int port, int data ) +inline void Snes_Spc::write_port( time_t t, int port, int data ) { assert( (unsigned) port < port_count ); run_until_( t ) [0x10 + port] = data; } -inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); } +inline void Snes_Spc::set_gain( int gain ) { dsp.set_gain( gain ); } + +inline void Snes_Spc::mute_voices( int mask ) { dsp.mute_voices( mask ); } -inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); } +inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); } #if !SPC_NO_COPY_STATE_FUNCS -inline bool SNES_SPC::check_kon() { return dsp.check_kon(); } +inline bool Snes_Spc::check_kon() { return dsp.check_kon(); } #endif #endif diff --git a/snes_spc/snes_spc/SNES_SPC.cpp b/game-music-emu/gme/Spc_Cpu.cpp similarity index 93% rename from snes_spc/snes_spc/SNES_SPC.cpp rename to game-music-emu/gme/Spc_Cpu.cpp index fb3b147ae..d59f778da 100644 --- a/snes_spc/snes_spc/SNES_SPC.cpp +++ b/game-music-emu/gme/Spc_Cpu.cpp @@ -2,7 +2,7 @@ // snes_spc 0.9.0. http://www.slack.net/~ant/ -#include "SNES_SPC.h" +#include "Snes_Spc.h" #include @@ -47,7 +47,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define TIMER_MUL( t, n ) ((n) * t->prescaler) #endif -SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time ) +Snes_Spc::Timer* Snes_Spc::run_timer_( Timer* t, rel_time_t time ) { int elapsed = TIMER_DIV( t, time - t->next_time ) + 1; t->next_time += TIMER_MUL( t, elapsed ); @@ -68,7 +68,7 @@ SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time ) return t; } -inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time ) +inline Snes_Spc::Timer* Snes_Spc::run_timer( Timer* t, rel_time_t time ) { if ( time >= t->next_time ) t = run_timer_( t, time ); @@ -78,7 +78,7 @@ inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time ) //// ROM -void SNES_SPC::enable_rom( int enable ) +void Snes_Spc::enable_rom( int enable ) { if ( m.rom_enabled != enable ) { @@ -96,7 +96,7 @@ void SNES_SPC::enable_rom( int enable ) #if SPC_LESS_ACCURATE int const max_reg_time = 29; - signed char const SNES_SPC::reg_times_ [256] = + signed char const Snes_Spc::reg_times_ [256] = { -1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22, 2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23, @@ -138,7 +138,7 @@ void SNES_SPC::enable_rom( int enable ) } #endif -int SNES_SPC::dsp_read( rel_time_t time ) +int Snes_Spc::dsp_read( rel_time_t time ) { RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] ); @@ -151,17 +151,17 @@ int SNES_SPC::dsp_read( rel_time_t time ) return result; } -inline void SNES_SPC::dsp_write( int data, rel_time_t time ) +inline void Snes_Spc::dsp_write( int data, rel_time_t time ) { RUN_DSP( time, reg_times [REGS [r_dspaddr]] ) #if SPC_LESS_ACCURATE else if ( m.dsp_time == skipping_time ) { int r = REGS [r_dspaddr]; - if ( r == SPC_DSP::r_kon ) - m.skipped_kon |= data & ~dsp.read( SPC_DSP::r_koff ); + if ( r == Spc_Dsp::r_kon ) + m.skipped_kon |= data & ~dsp.read( Spc_Dsp::r_koff ); - if ( r == SPC_DSP::r_koff ) + if ( r == Spc_Dsp::r_koff ) { m.skipped_koff |= data; m.skipped_kon &= ~data; @@ -194,12 +194,12 @@ inline void SNES_SPC::dsp_write( int data, rel_time_t time ) // Debug-only check for read/write within echo buffer, since this might result in // inaccurate emulation due to the DSP not being caught up to the present. - bool SNES_SPC::check_echo_access( int addr ) + bool Snes_Spc::check_echo_access( int addr ) { - if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) ) + if ( !(dsp.read( Spc_Dsp::r_flg ) & 0x20) ) { - int start = 0x100 * dsp.read( SPC_DSP::r_esa ); - int size = 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F); + int start = 0x100 * dsp.read( Spc_Dsp::r_esa ); + int size = 0x800 * (dsp.read( Spc_Dsp::r_edl ) & 0x0F); int end = start + (size ? size : 4); if ( start <= addr && addr < end ) { @@ -283,7 +283,7 @@ static unsigned char const glitch_probs [3] [256] = // If write isn't preceded by read, data has this added to it int const no_read_before_write = 0x2000; -void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) +void Snes_Spc::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) { switch ( addr ) { @@ -384,7 +384,7 @@ void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) } } -void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr ) +void Snes_Spc::cpu_write_smp_reg( int data, rel_time_t time, int addr ) { if ( addr == r_dspdata ) // 99% dsp_write( data, time ); @@ -392,7 +392,7 @@ void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr ) cpu_write_smp_reg_( data, time, addr ); } -void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time ) +void Snes_Spc::cpu_write_high( int data, int i, rel_time_t time ) { if ( i < rom_size ) { @@ -410,7 +410,7 @@ void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time ) int const bits_in_int = CHAR_BIT * sizeof (int); -void SNES_SPC::cpu_write( int data, int addr, rel_time_t time ) +void Snes_Spc::cpu_write( int data, int addr, rel_time_t time ) { MEM_ACCESS( time, addr ) @@ -450,7 +450,7 @@ void SNES_SPC::cpu_write( int data, int addr, rel_time_t time ) //// CPU read -inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time ) +inline int Snes_Spc::cpu_read_smp_reg( int reg, rel_time_t time ) { int result = REGS_IN [reg]; reg -= r_dspaddr; @@ -464,7 +464,7 @@ inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time ) return result; } -int SNES_SPC::cpu_read( int addr, rel_time_t time ) +int Snes_Spc::cpu_read( int addr, rel_time_t time ) { MEM_ACCESS( time, addr ) @@ -508,7 +508,7 @@ int SNES_SPC::cpu_read( int addr, rel_time_t time ) // Prefix and suffix for CPU emulator function #define SPC_CPU_RUN_FUNC \ -BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\ +BOOST::uint8_t* Snes_Spc::run_until_( time_t end_time )\ {\ rel_time_t rel_time = m.spc_time - end_time;\ assert( rel_time <= 0 );\ @@ -530,7 +530,7 @@ BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\ int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks -void SNES_SPC::end_frame( time_t end_time ) +void Snes_Spc::end_frame( time_t end_time ) { // Catch CPU up to as close to end as possible. If final instruction // would exceed end, does NOT execute it and leaves m.spc_time < end. @@ -561,4 +561,4 @@ void SNES_SPC::end_frame( time_t end_time ) } // Inclusion here allows static memory access functions and better optimization -#include "SPC_CPU.h" +#include "Spc_Cpu.h" diff --git a/snes_spc/snes_spc/SPC_CPU.h b/game-music-emu/gme/Spc_Cpu.h similarity index 99% rename from snes_spc/snes_spc/SPC_CPU.h rename to game-music-emu/gme/Spc_Cpu.h index 957a40152..e1b4b17b8 100644 --- a/snes_spc/snes_spc/SPC_CPU.h +++ b/game-music-emu/gme/Spc_Cpu.h @@ -121,7 +121,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define MEM_BIT( rel ) CPU_mem_bit( pc, rel_time + rel ) -unsigned SNES_SPC::CPU_mem_bit( uint8_t const* pc, rel_time_t rel_time ) +unsigned Snes_Spc::CPU_mem_bit( uint8_t const* pc, rel_time_t rel_time ) { unsigned addr = READ_PC16( pc ); unsigned t = READ( 0, addr & 0x1FFF ) >> (addr >> 13); diff --git a/snes_spc/fast_dsp/SPC_DSP.cpp b/game-music-emu/gme/Spc_Dsp.cpp similarity index 96% rename from snes_spc/fast_dsp/SPC_DSP.cpp rename to game-music-emu/gme/Spc_Dsp.cpp index 967196832..41c79c152 100644 --- a/snes_spc/fast_dsp/SPC_DSP.cpp +++ b/game-music-emu/gme/Spc_Dsp.cpp @@ -1,6 +1,6 @@ // snes_spc 0.9.0. http://www.slack.net/~ant/ -#include "SPC_DSP.h" +#include "Spc_Dsp.h" #include "blargg_endian.h" #include @@ -32,7 +32,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define GET_LE16A( addr ) GET_LE16( addr ) #define SET_LE16A( addr, data ) SET_LE16( addr, data ) -static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = +static BOOST::uint8_t const initial_regs [Spc_Dsp::register_count] = { 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, @@ -73,7 +73,7 @@ static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = }\ }\ -void SPC_DSP::set_output( sample_t* out, int size ) +void Spc_Dsp::set_output( sample_t* out, int size ) { require( (size & 1) == 0 ); // must be even if ( !out ) @@ -150,12 +150,12 @@ static unsigned const counter_mask [32] = }; #undef RATE -inline void SPC_DSP::init_counter() +inline void Spc_Dsp::init_counter() { // counters start out with this synchronization m.counters [0] = 1; m.counters [1] = 0; - m.counters [2] = -0x20u; + m.counters [2] = -0x20; m.counters [3] = 0x0B; int n = 2; @@ -169,7 +169,7 @@ inline void SPC_DSP::init_counter() m.counter_select [30] = &m.counters [2]; } -inline void SPC_DSP::run_counter( int i ) +inline void Spc_Dsp::run_counter( int i ) { int n = m.counters [i]; if ( !(n-- & 7) ) @@ -183,7 +183,7 @@ inline void SPC_DSP::run_counter( int i ) //// Emulation -void SPC_DSP::run( int clock_count ) +void Spc_Dsp::run( int clock_count ) { int new_phase = m.phase + clock_count; int count = new_phase >> 5; @@ -606,8 +606,8 @@ skip_brr: } // Sound out - int l = (main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14; - int r = (main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14; + int l = (((main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14) * m.gain) >> 8; + int r = (((main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14) * m.gain) >> 8; CLAMP16( l ); CLAMP16( r ); @@ -628,7 +628,7 @@ skip_brr: //// Setup -void SPC_DSP::mute_voices( int mask ) +void Spc_Dsp::mute_voices( int mask ) { m.mute_mask = mask; for ( int i = 0; i < voice_count; i++ ) @@ -638,9 +638,10 @@ void SPC_DSP::mute_voices( int mask ) } } -void SPC_DSP::init( void* ram_64k ) +void Spc_Dsp::init( void* ram_64k ) { m.ram = (uint8_t*) ram_64k; + set_gain( gain_unit ); mute_voices( 0 ); disable_surround( false ); set_output( 0, 0 ); @@ -662,7 +663,7 @@ void SPC_DSP::init( void* ram_64k ) #endif } -void SPC_DSP::soft_reset_common() +void Spc_Dsp::soft_reset_common() { require( m.ram ); // init() must have been called already @@ -675,13 +676,13 @@ void SPC_DSP::soft_reset_common() init_counter(); } -void SPC_DSP::soft_reset() +void Spc_Dsp::soft_reset() { REG(flg) = 0xE0; soft_reset_common(); } -void SPC_DSP::load( uint8_t const regs [register_count] ) +void Spc_Dsp::load( uint8_t const regs [register_count] ) { memcpy( m.regs, regs, sizeof m.regs ); memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); @@ -700,4 +701,4 @@ void SPC_DSP::load( uint8_t const regs [register_count] ) soft_reset_common(); } -void SPC_DSP::reset() { load( initial_regs ); } +void Spc_Dsp::reset() { load( initial_regs ); } diff --git a/snes_spc/snes_spc/SPC_DSP.h b/game-music-emu/gme/Spc_Dsp.h similarity index 92% rename from snes_spc/snes_spc/SPC_DSP.h rename to game-music-emu/gme/Spc_Dsp.h index 045ce3c2c..14b131ac1 100644 --- a/snes_spc/snes_spc/SPC_DSP.h +++ b/game-music-emu/gme/Spc_Dsp.h @@ -6,7 +6,7 @@ #include "blargg_common.h" -struct SPC_DSP { +class Spc_Dsp { public: typedef BOOST::uint8_t uint8_t; @@ -51,7 +51,10 @@ public: // If true, prevents channels and global volumes from being phase-negated void disable_surround( bool disable = true ); - + + enum { gain_unit = 0x100 }; + void set_gain( int gain ); + // State // Resets DSP and uses supplied values to initialize registers @@ -137,6 +140,7 @@ private: // non-emulation state uint8_t* ram; // 64K shared RAM between DSP and SMP int mute_mask; + int gain; int surround_threshold; sample_t* out; sample_t* out_end; @@ -154,15 +158,15 @@ private: #include -inline int SPC_DSP::sample_count() const { return int(m.out - m.out_begin); } +inline int Spc_Dsp::sample_count() const { return int(m.out - m.out_begin); } -inline int SPC_DSP::read( int addr ) const +inline int Spc_Dsp::read( int addr ) const { assert( (unsigned) addr < register_count ); return m.regs [addr]; } -inline void SPC_DSP::update_voice_vol( int addr ) +inline void Spc_Dsp::update_voice_vol( int addr ) { int l = (int8_t) m.regs [addr + v_voll]; int r = (int8_t) m.regs [addr + v_volr]; @@ -180,7 +184,7 @@ inline void SPC_DSP::update_voice_vol( int addr ) v.volume [1] = r & enabled; } -inline void SPC_DSP::write( int addr, int data ) +inline void Spc_Dsp::write( int addr, int data ) { assert( (unsigned) addr < register_count ); @@ -200,7 +204,9 @@ inline void SPC_DSP::write( int addr, int data ) } } -inline void SPC_DSP::disable_surround( bool disable ) +inline void Spc_Dsp::set_gain( int gain ) { m.gain = gain; } + +inline void Spc_Dsp::disable_surround( bool disable ) { m.surround_threshold = disable ? 0 : -0x4000; } diff --git a/game-music-emu/gme/Spc_Emu.cpp b/game-music-emu/gme/Spc_Emu.cpp new file mode 100644 index 000000000..7f6e72faf --- /dev/null +++ b/game-music-emu/gme/Spc_Emu.cpp @@ -0,0 +1,328 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Spc_Emu.h" + +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Spc_Emu::Spc_Emu() +{ + set_type( gme_spc_type ); + + static const char* const names [Snes_Spc::voice_count] = { + "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" + }; + set_voice_names( names ); + + set_gain( 1.4 ); +} + +Spc_Emu::~Spc_Emu() { } + +// Track info + +long const trailer_offset = 0x10200; + +byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, trailer_offset )]; } + +long Spc_Emu::trailer_size() const { return max( 0L, file_size - trailer_offset ); } + +static void get_spc_xid6( byte const* begin, long size, track_info_t* out ) +{ + // header + byte const* end = begin + size; + if ( size < 8 || memcmp( begin, "xid6", 4 ) ) + { + check( false ); + return; + } + long info_size = get_le32( begin + 4 ); + byte const* in = begin + 8; + if ( end - in > info_size ) + { + dprintf( "Extra data after SPC xid6 info\n" ); + end = in + info_size; + } + + int year = 0; + char copyright [256 + 5]; + int copyright_len = 0; + int const year_len = 5; + + while ( end - in >= 4 ) + { + // header + int id = in [0]; + int data = in [3] * 0x100 + in [2]; + int type = in [1]; + int len = type ? data : 0; + in += 4; + if ( len > end - in ) + { + check( false ); + break; // block goes past end of data + } + + // handle specific block types + char* field = 0; + switch ( id ) + { + case 0x01: field = out->song; break; + case 0x02: field = out->game; break; + case 0x03: field = out->author; break; + case 0x04: field = out->dumper; break; + case 0x07: field = out->comment; break; + case 0x14: year = data; break; + + //case 0x30: // intro length + // Many SPCs have intro length set wrong for looped tracks, making it useless + /* + case 0x30: + check( len == 4 ); + if ( len >= 4 ) + { + out->intro_length = get_le32( in ) / 64; + if ( out->length > 0 ) + { + long loop = out->length - out->intro_length; + if ( loop >= 2000 ) + out->loop_length = loop; + } + } + break; + */ + + case 0x13: + copyright_len = min( len, (int) sizeof copyright - year_len ); + memcpy( ©right [year_len], in, copyright_len ); + break; + + default: + if ( id < 0x01 || (id > 0x07 && id < 0x10) || + (id > 0x14 && id < 0x30) || id > 0x36 ) + dprintf( "Unknown SPC xid6 block: %X\n", (int) id ); + break; + } + if ( field ) + { + check( type == 1 ); + Gme_File::copy_field_( field, (char const*) in, len ); + } + + // skip to next block + in += len; + + // blocks are supposed to be 4-byte aligned with zero-padding... + byte const* unaligned = in; + while ( (in - begin) & 3 && in < end ) + { + if ( *in++ != 0 ) + { + // ...but some files have no padding + in = unaligned; + dprintf( "SPC info tag wasn't properly padded to align\n" ); + break; + } + } + } + + char* p = ©right [year_len]; + if ( year ) + { + *--p = ' '; + for ( int n = 4; n--; ) + { + *--p = char (year % 10 + '0'); + year /= 10; + } + copyright_len += year_len; + } + if ( copyright_len ) + Gme_File::copy_field_( out->copyright, p, copyright_len ); + + check( in == end ); +} + +static void get_spc_info( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size, + track_info_t* out ) +{ + // decode length (can be in text or binary format, sometimes ambiguous ugh) + long len_secs = 0; + for ( int i = 0; i < 3; i++ ) + { + unsigned n = h.len_secs [i] - '0'; + if ( n > 9 ) + { + // ignore single-digit text lengths + // (except if author field is present and begins at offset 1, ugh) + if ( i == 1 && (h.author [0] || !h.author [1]) ) + len_secs = 0; + break; + } + len_secs *= 10; + len_secs += n; + } + if ( !len_secs || len_secs > 0x1FFF ) + len_secs = get_le16( h.len_secs ); + if ( len_secs < 0x1FFF ) + out->length = len_secs * 1000; + + int offset = (h.author [0] < ' ' || unsigned (h.author [0] - '0') <= 9); + Gme_File::copy_field_( out->author, &h.author [offset], sizeof h.author - offset ); + + GME_COPY_FIELD( h, out, song ); + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, dumper ); + GME_COPY_FIELD( h, out, comment ); + + if ( xid6_size ) + get_spc_xid6( xid6, xid6_size, out ); +} + +blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const +{ + get_spc_info( header(), trailer(), trailer_size(), out ); + return 0; +} + +static blargg_err_t check_spc_header( void const* header ) +{ + if ( memcmp( header, "SNES-SPC700 Sound File Data", 27 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Spc_File : Gme_Info_ +{ + Spc_Emu::header_t header; + blargg_vector xid6; + + Spc_File() { set_type( gme_spc_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + long file_size = in.remain(); + if ( file_size < Snes_Spc::spc_min_file_size ) + return gme_wrong_file_type; + RETURN_ERR( in.read( &header, Spc_Emu::header_size ) ); + RETURN_ERR( check_spc_header( header.tag ) ); + long const xid6_offset = 0x10200; + long xid6_size = file_size - xid6_offset; + if ( xid6_size > 0 ) + { + RETURN_ERR( xid6.resize( xid6_size ) ); + RETURN_ERR( in.skip( xid6_offset - Spc_Emu::header_size ) ); + RETURN_ERR( in.read( xid6.begin(), (long)xid6.size() ) ); + } + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + get_spc_info( header, xid6.begin(), (long)xid6.size(), out ); + return 0; + } +}; + +static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; } +static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; } + +static gme_type_t_ const gme_spc_type_ = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 }; +gme_type_t const gme_spc_type = &gme_spc_type_; + +// Setup + +blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate ) +{ + RETURN_ERR( apu.init() ); + apu.set_gain( (int) (gain() * Snes_Spc::gain_unit) ); + if ( sample_rate != native_sample_rate ) + { + RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); + resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 ); + } + return 0; +} + +void Spc_Emu::mute_voices_( int m ) +{ + Music_Emu::mute_voices_( m ); + apu.mute_voices( m ); +} + +blargg_err_t Spc_Emu::load_mem_( byte const* in, long size ) +{ + assert( offsetof (header_t,unused2 [46]) == header_size ); + file_data = in; + file_size = size; + set_voice_count( Snes_Spc::voice_count ); + if ( size < Snes_Spc::spc_min_file_size ) + return gme_wrong_file_type; + return check_spc_header( in ); +} + +// Emulation + +void Spc_Emu::set_tempo_( double t ) { apu.set_tempo( (int) (t * Snes_Spc::tempo_unit) ); } + +blargg_err_t Spc_Emu::start_track_( int track ) +{ + RETURN_ERR( Music_Emu::start_track_( track ) ); + resampler.clear(); + RETURN_ERR( apu.load_spc( file_data, file_size ) ); + apu.clear_echo(); + return 0; +} + +blargg_err_t Spc_Emu::skip_( long count ) +{ + if ( sample_rate() != native_sample_rate ) + { + count = long (count * resampler.ratio()) & ~1; + count -= resampler.skip_input( count ); + } + + // TODO: shouldn't skip be adjusted for the 64 samples read afterwards? + + if ( count > 0 ) + RETURN_ERR( apu.skip( count ) ); + + // eliminate pop due to resampler + const int resampler_latency = 64; + sample_t buf [resampler_latency]; + return play_( resampler_latency, buf ); +} + +blargg_err_t Spc_Emu::play_( long count, sample_t* out ) +{ + if ( sample_rate() == native_sample_rate ) + return apu.play( count, out ); + + long remain = count; + while ( remain > 0 ) + { + remain -= resampler.read( &out [count - remain], remain ); + if ( remain > 0 ) + { + long n = resampler.max_write(); + RETURN_ERR( apu.play( n, resampler.buffer() ) ); + resampler.write( n ); + } + } + check( remain == 0 ); + return 0; +} diff --git a/game-music-emu/gme/Spc_Emu.h b/game-music-emu/gme/Spc_Emu.h new file mode 100644 index 000000000..44b54c309 --- /dev/null +++ b/game-music-emu/gme/Spc_Emu.h @@ -0,0 +1,77 @@ +// Super Nintendo SPC music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef SPC_EMU_H +#define SPC_EMU_H + +#include "Fir_Resampler.h" +#include "Music_Emu.h" +#include "Snes_Spc.h" + +class Spc_Emu : public Music_Emu { +public: + // The Super Nintendo hardware samples at 32kHz. Other sample rates are + // handled by resampling the 32kHz output; emulation accuracy is not affected. + enum { native_sample_rate = 32000 }; + + // SPC file header + enum { header_size = 0x100 }; + struct header_t + { + char tag [35]; + byte format; + byte version; + byte pc [2]; + byte a, x, y, psw, sp; + byte unused [2]; + char song [32]; + char game [32]; + char dumper [16]; + char comment [32]; + byte date [11]; + byte len_secs [3]; + byte fade_msec [4]; + char author [32]; // sometimes first char should be skipped (see official SPC spec) + byte mute_mask; + byte emulator; + byte unused2 [46]; + }; + + // Header for currently loaded file + header_t const& header() const { return *(header_t const*) file_data; } + + // Prevents channels and global volumes from being phase-negated + void disable_surround( bool disable = true ); + + static gme_type_t static_type() { return gme_spc_type; } + +public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + byte const* trailer() const; // use track_info() + long trailer_size() const; + +public: + Spc_Emu(); + ~Spc_Emu(); +protected: + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t set_sample_rate_( long ); + blargg_err_t start_track_( int ); + blargg_err_t play_( long, sample_t* ); + blargg_err_t skip_( long ); + void mute_voices_( int ); + void set_tempo_( double ); +private: + byte const* file_data; + long file_size; + Fir_Resampler<24> resampler; + Snes_Spc apu; +}; + +inline void Spc_Emu::disable_surround( bool b ) { apu.disable_surround( b ); } + +#endif diff --git a/game-music-emu/gme/Vgm_Emu.cpp b/game-music-emu/gme/Vgm_Emu.cpp new file mode 100644 index 000000000..fc5394480 --- /dev/null +++ b/game-music-emu/gme/Vgm_Emu.cpp @@ -0,0 +1,416 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Vgm_Emu.h" + +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +double const fm_gain = 3.0; // FM emulators are internally quieter to avoid 16-bit overflow +double const rolloff = 0.990; +double const oversample_factor = 1.5; + +Vgm_Emu::Vgm_Emu() +{ + disable_oversampling_ = false; + psg_rate = 0; + set_type( gme_vgm_type ); + + static int const types [8] = { + wave_type | 1, wave_type | 0, wave_type | 2, noise_type | 0 + }; + set_voice_types( types ); + + set_silence_lookahead( 1 ); // tracks should already be trimmed + + static equalizer_t const eq = { -14.0, 80 }; + set_equalizer( eq ); +} + +Vgm_Emu::~Vgm_Emu() { } + +// Track info + +static byte const* skip_gd3_str( byte const* in, byte const* end ) +{ + while ( end - in >= 2 ) + { + in += 2; + if ( !(in [-2] | in [-1]) ) + break; + } + return in; +} + +static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) +{ + byte const* mid = skip_gd3_str( in, end ); + int len = int(mid - in) / 2 - 1; + if ( len > 0 ) + { + len = min( len, (int) Gme_File::max_field_ ); + field [len] = 0; + for ( int i = 0; i < len; i++ ) + field [i] = (in [i * 2 + 1] ? '?' : in [i * 2]); // TODO: convert to utf-8 + } + return mid; +} + +static byte const* get_gd3_pair( byte const* in, byte const* end, char* field ) +{ + return skip_gd3_str( get_gd3_str( in, end, field ), end ); +} + +static void parse_gd3( byte const* in, byte const* end, track_info_t* out ) +{ + in = get_gd3_pair( in, end, out->song ); + in = get_gd3_pair( in, end, out->game ); + in = get_gd3_pair( in, end, out->system ); + in = get_gd3_pair( in, end, out->author ); + in = get_gd3_str ( in, end, out->copyright ); + in = get_gd3_pair( in, end, out->dumper ); + in = get_gd3_str ( in, end, out->comment ); +} + +int const gd3_header_size = 12; + +static long check_gd3_header( byte const* h, long remain ) +{ + if ( remain < gd3_header_size ) return 0; + if ( memcmp( h, "Gd3 ", 4 ) ) return 0; + if ( get_le32( h + 4 ) >= 0x200 ) return 0; + + long gd3_size = get_le32( h + 8 ); + if ( gd3_size > remain - gd3_header_size ) return 0; + + return gd3_size; +} + +byte const* Vgm_Emu::gd3_data( int* size ) const +{ + if ( size ) + *size = 0; + + long gd3_offset = get_le32( header().gd3_offset ) - 0x2C; + if ( gd3_offset < 0 ) + return 0; + + byte const* gd3 = data + header_size + gd3_offset; + long gd3_size = check_gd3_header( gd3, long(data_end - gd3) ); + if ( !gd3_size ) + return 0; + + if ( size ) + *size = gd3_size + gd3_header_size; + + return gd3; +} + +static void get_vgm_length( Vgm_Emu::header_t const& h, track_info_t* out ) +{ + long length = get_le32( h.track_duration ) * 10 / 441; + if ( length > 0 ) + { + long loop = get_le32( h.loop_duration ); + if ( loop > 0 && get_le32( h.loop_offset ) ) + { + out->loop_length = loop * 10 / 441; + out->intro_length = length - out->loop_length; + } + else + { + out->length = length; // 1000 / 44100 (VGM files used 44100 as timebase) + out->intro_length = length; // make it clear that track is no longer than length + out->loop_length = 0; + } + } +} + +blargg_err_t Vgm_Emu::track_info_( track_info_t* out, int ) const +{ + get_vgm_length( header(), out ); + + int size; + byte const* gd3 = gd3_data( &size ); + if ( gd3 ) + parse_gd3( gd3 + gd3_header_size, gd3 + size, out ); + + return 0; +} + +static blargg_err_t check_vgm_header( Vgm_Emu::header_t const& h ) +{ + if ( memcmp( h.tag, "Vgm ", 4 ) ) + return gme_wrong_file_type; + return 0; +} + +struct Vgm_File : Gme_Info_ +{ + Vgm_Emu::header_t h; + blargg_vector gd3; + + Vgm_File() { set_type( gme_vgm_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + long file_size = in.remain(); + if ( file_size <= Vgm_Emu::header_size ) + return gme_wrong_file_type; + + RETURN_ERR( in.read( &h, Vgm_Emu::header_size ) ); + RETURN_ERR( check_vgm_header( h ) ); + + long gd3_offset = get_le32( h.gd3_offset ) - 0x2C; + long remain = file_size - Vgm_Emu::header_size - gd3_offset; + byte gd3_h [gd3_header_size]; + if ( gd3_offset > 0 && remain >= gd3_header_size ) + { + RETURN_ERR( in.skip( gd3_offset ) ); + RETURN_ERR( in.read( gd3_h, sizeof gd3_h ) ); + long gd3_size = check_gd3_header( gd3_h, remain ); + if ( gd3_size ) + { + RETURN_ERR( gd3.resize( gd3_size ) ); + RETURN_ERR( in.read( gd3.begin(), (long)gd3.size() ) ); + } + } + return 0; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + get_vgm_length( h, out ); + if ( gd3.size() ) + parse_gd3( gd3.begin(), gd3.end(), out ); + return 0; + } +}; + +static Music_Emu* new_vgm_emu () { return BLARGG_NEW Vgm_Emu ; } +static Music_Emu* new_vgm_file() { return BLARGG_NEW Vgm_File; } + +static gme_type_t_ const gme_vgm_type_ = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGM", 1 }; +gme_type_t const gme_vgm_type = &gme_vgm_type_; + +static gme_type_t_ const gme_vgz_type_ = { "Sega SMS/Genesis", 1, &new_vgm_emu, &new_vgm_file, "VGZ", 1 }; +gme_type_t const gme_vgz_type = &gme_vgz_type_; + + +// Setup + +void Vgm_Emu::set_tempo_( double t ) +{ + if ( psg_rate ) + { + vgm_rate = (long) (44100 * t + 0.5); + blip_time_factor = (long) floor( double (1L << blip_time_bits) / vgm_rate * psg_rate + 0.5 ); + //dprintf( "blip_time_factor: %ld\n", blip_time_factor ); + //dprintf( "vgm_rate: %ld\n", vgm_rate ); + // TODO: remove? calculates vgm_rate more accurately (above differs at most by one Hz only) + //blip_time_factor = (long) floor( double (1L << blip_time_bits) * psg_rate / 44100 / t + 0.5 ); + //vgm_rate = (long) floor( double (1L << blip_time_bits) * psg_rate / blip_time_factor + 0.5 ); + + fm_time_factor = 2 + (long) floor( fm_rate * (1L << fm_time_bits) / vgm_rate + 0.5 ); + } +} + +blargg_err_t Vgm_Emu::set_sample_rate_( long sample_rate ) +{ + RETURN_ERR( blip_buf.set_sample_rate( sample_rate, 1000 / 30 ) ); + return Classic_Emu::set_sample_rate_( sample_rate ); +} + +void Vgm_Emu::update_eq( blip_eq_t const& eq ) +{ + psg.treble_eq( eq ); + dac_synth.treble_eq( eq ); +} + +void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + if ( i < psg.osc_count ) + psg.osc_output( i, c, l, r ); +} + +void Vgm_Emu::mute_voices_( int mask ) +{ + Classic_Emu::mute_voices_( mask ); + dac_synth.output( &blip_buf ); + if ( uses_fm ) + { + psg.output( (mask & 0x80) ? 0 : &blip_buf ); + if ( ym2612.enabled() ) + { + dac_synth.volume( (mask & 0x40) ? 0.0 : 0.1115 / 256 * fm_gain * gain() ); + ym2612.mute_voices( mask ); + } + + if ( ym2413.enabled() ) + { + int m = mask & 0x3F; + if ( mask & 0x20 ) + m |= 0x01E0; // channels 5-8 + if ( mask & 0x40 ) + m |= 0x3E00; + ym2413.mute_voices( m ); + } + } +} + +blargg_err_t Vgm_Emu::load_mem_( byte const* new_data, long new_size ) +{ + assert( offsetof (header_t,unused2 [8]) == header_size ); + + if ( new_size <= header_size ) + return gme_wrong_file_type; + + header_t const& h = *(header_t const*) new_data; + + RETURN_ERR( check_vgm_header( h ) ); + + check( get_le32( h.version ) <= 0x150 ); + + // psg rate + psg_rate = get_le32( h.psg_rate ); + if ( !psg_rate ) + psg_rate = 3579545; + blip_buf.clock_rate( psg_rate ); + + data = new_data; + data_end = new_data + new_size; + + // get loop + loop_begin = data_end; + if ( get_le32( h.loop_offset ) ) + loop_begin = &data [get_le32( h.loop_offset ) + offsetof (header_t,loop_offset)]; + + set_voice_count( psg.osc_count ); + + RETURN_ERR( setup_fm() ); + + static const char* const fm_names [] = { + "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" + }; + static const char* const psg_names [] = { "Square 1", "Square 2", "Square 3", "Noise" }; + set_voice_names( uses_fm ? fm_names : psg_names ); + + // do after FM in case output buffer is changed + return Classic_Emu::setup_buffer( psg_rate ); +} + +blargg_err_t Vgm_Emu::setup_fm() +{ + long ym2612_rate = get_le32( header().ym2612_rate ); + long ym2413_rate = get_le32( header().ym2413_rate ); + if ( ym2413_rate && get_le32( header().version ) < 0x110 ) + update_fm_rates( &ym2413_rate, &ym2612_rate ); + + uses_fm = false; + + fm_rate = blip_buf.sample_rate() * oversample_factor; + + if ( ym2612_rate ) + { + uses_fm = true; + if ( disable_oversampling_ ) + fm_rate = ym2612_rate / 144.0; + Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, fm_gain * gain() ); + RETURN_ERR( ym2612.set_rate( fm_rate, ym2612_rate ) ); + ym2612.enable( true ); + set_voice_count( 8 ); + } + + if ( !uses_fm && ym2413_rate ) + { + uses_fm = true; + if ( disable_oversampling_ ) + fm_rate = ym2413_rate / 72.0; + Dual_Resampler::setup( fm_rate / blip_buf.sample_rate(), rolloff, fm_gain * gain() ); + int result = ym2413.set_rate( fm_rate, ym2413_rate ); + if ( result == 2 ) + return "YM2413 FM sound isn't supported"; + CHECK_ALLOC( !result ); + ym2413.enable( true ); + set_voice_count( 8 ); + } + + if ( uses_fm ) + { + RETURN_ERR( Dual_Resampler::reset( blip_buf.length() * blip_buf.sample_rate() / 1000 ) ); + psg.volume( 0.135 * fm_gain * gain() ); + } + else + { + ym2612.enable( false ); + ym2413.enable( false ); + psg.volume( gain() ); + } + + return 0; +} + +// Emulation + +blargg_err_t Vgm_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + psg.reset( get_le16( header().noise_feedback ), header().noise_width ); + + dac_disabled = -1; + pos = data + header_size; + pcm_data = pos; + pcm_pos = pos; + dac_amp = -1; + vgm_time = 0; + if ( get_le32( header().version ) >= 0x150 ) + { + long data_offset = get_le32( header().data_offset ); + check( data_offset ); + if ( data_offset ) + pos += data_offset + offsetof (header_t,data_offset) - 0x40; + } + + if ( uses_fm ) + { + if ( ym2413.enabled() ) + ym2413.reset(); + + if ( ym2612.enabled() ) + ym2612.reset(); + + fm_time_offset = 0; + blip_buf.clear(); + Dual_Resampler::clear(); + } + return 0; +} + +blargg_err_t Vgm_Emu::run_clocks( blip_time_t& time_io, int msec ) +{ + time_io = run_commands( msec * vgm_rate / 1000 ); + psg.end_frame( time_io ); + return 0; +} + +blargg_err_t Vgm_Emu::play_( long count, sample_t* out ) +{ + if ( !uses_fm ) + return Classic_Emu::play_( count, out ); + + Dual_Resampler::dual_play( count, out, blip_buf ); + return 0; +} diff --git a/game-music-emu/gme/Vgm_Emu.h b/game-music-emu/gme/Vgm_Emu.h new file mode 100644 index 000000000..bcb784d56 --- /dev/null +++ b/game-music-emu/gme/Vgm_Emu.h @@ -0,0 +1,84 @@ +// Sega Master System/Mark III, Sega Genesis/Mega Drive, BBC Micro VGM music file emulator + +// Game_Music_Emu 0.5.2 +#ifndef VGM_EMU_H +#define VGM_EMU_H + +#include "Vgm_Emu_Impl.h" + +// Emulates VGM music using SN76489/SN76496 PSG, YM2612, and YM2413 FM sound chips. +// Supports custom sound buffer and frequency equalization when VGM uses just the PSG. +// FM sound chips can be run at their proper rates, or slightly higher to reduce +// aliasing on high notes. Currently YM2413 support requires that you supply a +// YM2413 sound chip emulator. I can provide one I've modified to work with the library. +class Vgm_Emu : public Vgm_Emu_Impl { +public: + // True if custom buffer and custom equalization are supported + // TODO: move into Music_Emu and rename to something like supports_custom_buffer() + bool is_classic_emu() const { return !uses_fm; } + + // Disable running FM chips at higher than normal rate. Will result in slightly + // more aliasing of high notes. + void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; } + + // VGM header format + enum { header_size = 0x40 }; + struct header_t + { + char tag [4]; + byte data_size [4]; + byte version [4]; + byte psg_rate [4]; + byte ym2413_rate [4]; + byte gd3_offset [4]; + byte track_duration [4]; + byte loop_offset [4]; + byte loop_duration [4]; + byte frame_rate [4]; + byte noise_feedback [2]; + byte noise_width; + byte unused1; + byte ym2612_rate [4]; + byte ym2151_rate [4]; + byte data_offset [4]; + byte unused2 [8]; + }; + + // Header for currently loaded file + header_t const& header() const { return *(header_t const*) data; } + + static gme_type_t static_type() { return gme_vgm_type; } + +public: + // deprecated + Music_Emu::load; + blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader + { return load_remaining_( &h, sizeof h, in ); } + byte const* gd3_data( int* size_out = 0 ) const; // use track_info() + +public: + Vgm_Emu(); + ~Vgm_Emu(); +protected: + blargg_err_t track_info_( track_info_t*, int track ) const; + blargg_err_t load_mem_( byte const*, long ); + blargg_err_t set_sample_rate_( long sample_rate ); + blargg_err_t start_track_( int ); + blargg_err_t play_( long count, sample_t* ); + blargg_err_t run_clocks( blip_time_t&, int ); + void set_tempo_( double ); + void mute_voices_( int mask ); + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); +private: + // removed; use disable_oversampling() and set_tempo() instead + Vgm_Emu( bool oversample, double tempo = 1.0 ); + double fm_rate; + long psg_rate; + long vgm_rate; + bool disable_oversampling_; + bool uses_fm; + blargg_err_t setup_fm(); +}; + +#endif diff --git a/game-music-emu/gme/Vgm_Emu_Impl.cpp b/game-music-emu/gme/Vgm_Emu_Impl.cpp new file mode 100644 index 000000000..a2d7c93e5 --- /dev/null +++ b/game-music-emu/gme/Vgm_Emu_Impl.cpp @@ -0,0 +1,314 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Vgm_Emu.h" + +#include +#include +#include "blargg_endian.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +enum { + cmd_gg_stereo = 0x4F, + cmd_psg = 0x50, + cmd_ym2413 = 0x51, + cmd_ym2612_port0 = 0x52, + cmd_ym2612_port1 = 0x53, + cmd_ym2151 = 0x54, + cmd_delay = 0x61, + cmd_delay_735 = 0x62, + cmd_delay_882 = 0x63, + cmd_byte_delay = 0x64, + cmd_end = 0x66, + cmd_data_block = 0x67, + cmd_short_delay = 0x70, + cmd_pcm_delay = 0x80, + cmd_pcm_seek = 0xE0, + + pcm_block_type = 0x00, + ym2612_dac_port = 0x2A +}; + +inline int command_len( int command ) +{ + switch ( command >> 4 ) + { + case 0x03: + case 0x04: + return 2; + + case 0x05: + case 0x0A: + case 0x0B: + return 3; + + case 0x0C: + case 0x0D: + return 4; + + case 0x0E: + case 0x0F: + return 5; + } + + check( false ); + return 1; +} + +template +inline void Ym_Emu::begin_frame( short* p ) +{ + require( enabled() ); + out = p; + last_time = 0; +} + +template +inline int Ym_Emu::run_until( int time ) +{ + int count = time - last_time; + if ( count > 0 ) + { + if ( last_time < 0 ) + return false; + last_time = time; + short* p = out; + out += count * Emu::out_chan_count; + Emu::run( count, p ); + } + return true; +} + +inline Vgm_Emu_Impl::fm_time_t Vgm_Emu_Impl::to_fm_time( vgm_time_t t ) const +{ + return (t * fm_time_factor + fm_time_offset) >> fm_time_bits; +} + +inline blip_time_t Vgm_Emu_Impl::to_blip_time( vgm_time_t t ) const +{ + return (t * blip_time_factor) >> blip_time_bits; +} + +void Vgm_Emu_Impl::write_pcm( vgm_time_t vgm_time, int amp ) +{ + blip_time_t blip_time = to_blip_time( vgm_time ); + int old = dac_amp; + int delta = amp - old; + dac_amp = amp; + if ( old >= 0 ) + dac_synth.offset_inline( blip_time, delta, &blip_buf ); + else + dac_amp |= dac_disabled; +} + +blip_time_t Vgm_Emu_Impl::run_commands( vgm_time_t end_time ) +{ + vgm_time_t vgm_time = this->vgm_time; + byte const* pos = this->pos; + if ( pos >= data_end ) + { + set_track_ended(); + if ( pos > data_end ) + set_warning( "Stream lacked end event" ); + } + + while ( vgm_time < end_time && pos < data_end ) + { + // TODO: be sure there are enough bytes left in stream for particular command + // so we don't read past end + switch ( *pos++ ) + { + case cmd_end: + pos = loop_begin; // if not looped, loop_begin == data_end + break; + + case cmd_delay_735: + vgm_time += 735; + break; + + case cmd_delay_882: + vgm_time += 882; + break; + + case cmd_gg_stereo: + psg.write_ggstereo( to_blip_time( vgm_time ), *pos++ ); + break; + + case cmd_psg: + psg.write_data( to_blip_time( vgm_time ), *pos++ ); + break; + + case cmd_delay: + vgm_time += pos [1] * 0x100L + pos [0]; + pos += 2; + break; + + case cmd_byte_delay: + vgm_time += *pos++; + break; + + case cmd_ym2413: + if ( ym2413.run_until( to_fm_time( vgm_time ) ) ) + ym2413.write( pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_port0: + if ( pos [0] == ym2612_dac_port ) + { + write_pcm( vgm_time, pos [1] ); + } + else if ( ym2612.run_until( to_fm_time( vgm_time ) ) ) + { + if ( pos [0] == 0x2B ) + { + dac_disabled = (pos [1] >> 7 & 1) - 1; + dac_amp |= dac_disabled; + } + ym2612.write0( pos [0], pos [1] ); + } + pos += 2; + break; + + case cmd_ym2612_port1: + if ( ym2612.run_until( to_fm_time( vgm_time ) ) ) + ym2612.write1( pos [0], pos [1] ); + pos += 2; + break; + + case cmd_data_block: { + check( *pos == cmd_end ); + int type = pos [1]; + long size = get_le32( pos + 2 ); + pos += 6; + if ( type == pcm_block_type ) + pcm_data = pos; + pos += size; + break; + } + + case cmd_pcm_seek: + pcm_pos = pcm_data + pos [3] * 0x1000000L + pos [2] * 0x10000L + + pos [1] * 0x100L + pos [0]; + pos += 4; + break; + + default: + int cmd = pos [-1]; + switch ( cmd & 0xF0 ) + { + case cmd_pcm_delay: + write_pcm( vgm_time, *pcm_pos++ ); + vgm_time += cmd & 0x0F; + break; + + case cmd_short_delay: + vgm_time += (cmd & 0x0F) + 1; + break; + + case 0x50: + pos += 2; + break; + + default: + pos += command_len( cmd ) - 1; + set_warning( "Unknown stream event" ); + } + } + } + vgm_time -= end_time; + this->pos = pos; + this->vgm_time = vgm_time; + + return to_blip_time( end_time ); +} + +int Vgm_Emu_Impl::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ) +{ + // to do: timing is working mostly by luck + + int min_pairs = sample_count >> 1; + int vgm_time = ((long) min_pairs << fm_time_bits) / fm_time_factor - 1; + assert( to_fm_time( vgm_time ) <= min_pairs ); + int pairs = min_pairs; + while ( (pairs = to_fm_time( vgm_time )) < min_pairs ) + vgm_time++; + //dprintf( "pairs: %d, min_pairs: %d\n", pairs, min_pairs ); + + if ( ym2612.enabled() ) + { + ym2612.begin_frame( buf ); + memset( buf, 0, pairs * stereo * sizeof *buf ); + } + else if ( ym2413.enabled() ) + { + ym2413.begin_frame( buf ); + } + + run_commands( vgm_time ); + ym2612.run_until( pairs ); + ym2413.run_until( pairs ); + + fm_time_offset = (vgm_time * fm_time_factor + fm_time_offset) - + ((long) pairs << fm_time_bits); + + psg.end_frame( blip_time ); + + return pairs * stereo; +} + +// Update pre-1.10 header FM rates by scanning commands +void Vgm_Emu_Impl::update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const +{ + byte const* p = data + 0x40; + while ( p < data_end ) + { + switch ( *p ) + { + case cmd_end: + return; + + case cmd_psg: + case cmd_byte_delay: + p += 2; + break; + + case cmd_delay: + p += 3; + break; + + case cmd_data_block: + p += 7 + get_le32( p + 3 ); + break; + + case cmd_ym2413: + *ym2612_rate = 0; + return; + + case cmd_ym2612_port0: + case cmd_ym2612_port1: + *ym2612_rate = *ym2413_rate; + *ym2413_rate = 0; + return; + + case cmd_ym2151: + *ym2413_rate = 0; + *ym2612_rate = 0; + return; + + default: + p += command_len( *p ); + } + } +} diff --git a/game-music-emu/gme/Vgm_Emu_Impl.h b/game-music-emu/gme/Vgm_Emu_Impl.h new file mode 100644 index 000000000..4d387d090 --- /dev/null +++ b/game-music-emu/gme/Vgm_Emu_Impl.h @@ -0,0 +1,71 @@ +// Low-level parts of Vgm_Emu + +// Game_Music_Emu 0.5.2 +#ifndef VGM_EMU_IMPL_H +#define VGM_EMU_IMPL_H + +#include "Dual_Resampler.h" +#include "Classic_Emu.h" +#include "Ym2413_Emu.h" +#include "Ym2612_Emu.h" +#include "Sms_Apu.h" + +template +class Ym_Emu : public Emu { +protected: + int last_time; + short* out; + enum { disabled_time = -1 }; +public: + Ym_Emu() : last_time( disabled_time ), out( NULL ) { } + void enable( bool b ) { last_time = b ? 0 : disabled_time; } + bool enabled() const { return last_time != disabled_time; } + void begin_frame( short* p ); + int run_until( int time ); +}; + +class Vgm_Emu_Impl : public Classic_Emu, private Dual_Resampler { +public: + typedef Classic_Emu::sample_t sample_t; +protected: + enum { stereo = 2 }; + + typedef int vgm_time_t; + + enum { fm_time_bits = 12 }; + typedef int fm_time_t; + long fm_time_offset; + int fm_time_factor; + fm_time_t to_fm_time( vgm_time_t ) const; + + enum { blip_time_bits = 12 }; + int blip_time_factor; + blip_time_t to_blip_time( vgm_time_t ) const; + + byte const* data; + byte const* loop_begin; + byte const* data_end; + void update_fm_rates( long* ym2413_rate, long* ym2612_rate ) const; + + vgm_time_t vgm_time; + byte const* pos; + blip_time_t run_commands( vgm_time_t ); + int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf ); + + byte const* pcm_data; + byte const* pcm_pos; + int dac_amp; + int dac_disabled; // -1 if disabled + void write_pcm( vgm_time_t, int amp ); + + Ym_Emu ym2612; + Ym_Emu ym2413; + + Blip_Buffer blip_buf; + Sms_Apu psg; + Blip_Synth dac_synth; + + friend class Vgm_Emu; +}; + +#endif diff --git a/game-music-emu/gme/Ym2413_Emu.cpp b/game-music-emu/gme/Ym2413_Emu.cpp new file mode 100644 index 000000000..ede673045 --- /dev/null +++ b/game-music-emu/gme/Ym2413_Emu.cpp @@ -0,0 +1,21 @@ + +// Use in place of Ym2413_Emu.cpp and ym2413.c to disable support for this chip + +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#include "Ym2413_Emu.h" + +Ym2413_Emu::Ym2413_Emu() { } + +Ym2413_Emu::~Ym2413_Emu() { } + +int Ym2413_Emu::set_rate( double, double ) { return 2; } + +void Ym2413_Emu::reset() { } + +void Ym2413_Emu::write( int, int ) { } + +void Ym2413_Emu::mute_voices( int ) { } + +void Ym2413_Emu::run( int, sample_t* ) { } + diff --git a/game-music-emu/gme/Ym2413_Emu.h b/game-music-emu/gme/Ym2413_Emu.h new file mode 100644 index 000000000..98a2a48e1 --- /dev/null +++ b/game-music-emu/gme/Ym2413_Emu.h @@ -0,0 +1,33 @@ +// YM2413 FM sound chip emulator interface + +// Game_Music_Emu 0.5.2 +#ifndef YM2413_EMU_H +#define YM2413_EMU_H + +class Ym2413_Emu { + struct OPLL* opll; +public: + Ym2413_Emu(); + ~Ym2413_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 14 }; + void mute_voices( int mask ); + + // Write 'data' to 'addr' + void write( int addr, int data ); + + // Run and write pair_count samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/game-music-emu/gme/Ym2612_Emu.cpp b/game-music-emu/gme/Ym2612_Emu.cpp new file mode 100644 index 000000000..41ebb093d --- /dev/null +++ b/game-music-emu/gme/Ym2612_Emu.cpp @@ -0,0 +1,1319 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +// Based on Gens 2.10 ym2612.c + +#include "Ym2612_Emu.h" + +#include +#include +#include +#include +#include +#include + +/* Copyright (C) 2002 Stéphane Dallongeville (gens AT consolemul.com) */ +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// This is mostly the original source in its C style and all. +// +// Somewhat optimized and simplified. Uses a template to generate the many +// variants of Update_Chan. Rewrote header file. In need of full rewrite by +// someone more familiar with FM sound and the YM2612. Has some inaccuracies +// compared to the Sega Genesis sound, particularly being mixed at such a +// high sample accuracy (the Genesis sounds like it has only 8 bit samples). +// - Shay + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +const int output_bits = 14; + +struct slot_t +{ + const int *DT; // parametre detune + int MUL; // parametre "multiple de frequence" + int TL; // Total Level = volume lorsque l'enveloppe est au plus haut + int TLL; // Total Level ajusted + int SLL; // Sustin Level (ajusted) = volume où l'enveloppe termine sa premiere phase de regression + int KSR_S; // Key Scale Rate Shift = facteur de prise en compte du KSL dans la variations de l'enveloppe + int KSR; // Key Scale Rate = cette valeur est calculee par rapport à la frequence actuelle, elle va influer + // sur les differents parametres de l'enveloppe comme l'attaque, le decay ... comme dans la realite ! + int SEG; // Type enveloppe SSG + int env_xor; + int env_max; + + const int *AR; // Attack Rate (table pointeur) = Taux d'attaque (AR[KSR]) + const int *DR; // Decay Rate (table pointeur) = Taux pour la regression (DR[KSR]) + const int *SR; // Sustin Rate (table pointeur) = Taux pour le maintien (SR[KSR]) + const int *RR; // Release Rate (table pointeur) = Taux pour le rel'chement (RR[KSR]) + int Fcnt; // Frequency Count = compteur-frequence pour determiner l'amplitude actuelle (SIN[Finc >> 16]) + int Finc; // frequency step = pas d'incrementation du compteur-frequence + // plus le pas est grand, plus la frequence est aïgu (ou haute) + int Ecurp; // Envelope current phase = cette variable permet de savoir dans quelle phase + // de l'enveloppe on se trouve, par exemple phase d'attaque ou phase de maintenue ... + // en fonction de la valeur de cette variable, on va appeler une fonction permettant + // de mettre à jour l'enveloppe courante. + int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir où l'on se trouve dans l'enveloppe + int Einc; // Envelope step courant + int Ecmp; // Envelope counter limite pour la prochaine phase + int EincA; // Envelope step for Attack = pas d'incrementation du compteur durant la phase d'attaque + // cette valeur est egal à AR[KSR] + int EincD; // Envelope step for Decay = pas d'incrementation du compteur durant la phase de regression + // cette valeur est egal à DR[KSR] + int EincS; // Envelope step for Sustain = pas d'incrementation du compteur durant la phase de maintenue + // cette valeur est egal à SR[KSR] + int EincR; // Envelope step for Release = pas d'incrementation du compteur durant la phase de rel'chement + // cette valeur est egal à RR[KSR] + int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot à l'entree + // d'un autre ou carrement à la sortie de la voie + int INd; // input data of the slot = donnees en entree du slot + int ChgEnM; // Change envelop mask. + int AMS; // AMS depth level of this SLOT = degre de modulation de l'amplitude par le LFO + int AMSon; // AMS enable flag = drapeau d'activation de l'AMS +}; + +struct channel_t +{ + int S0_OUT[4]; // anciennes sorties slot 0 (pour le feed back) + int LEFT; // LEFT enable flag + int RIGHT; // RIGHT enable flag + int ALGO; // Algorythm = determine les connections entre les operateurs + int FB; // shift count of self feed back = degre de "Feed-Back" du SLOT 1 (il est son unique entree) + int FMS; // Frequency Modulation Sensitivity of channel = degre de modulation de la frequence sur la voie par le LFO + int AMS; // Amplitude Modulation Sensitivity of channel = degre de modulation de l'amplitude sur la voie par le LFO + int FNUM[4]; // hauteur frequence de la voie (+ 3 pour le mode special) + int FOCT[4]; // octave de la voie (+ 3 pour le mode special) + int KC[4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S) + slot_t SLOT[4]; // four slot.operators = les 4 slots de la voie + int FFlag; // Frequency step recalculation flag +}; + +struct state_t +{ + int TimerBase; // TimerBase calculation + int Status; // YM2612 Status (timer overflow) + int TimerA; // timerA limit = valeur jusqu'à laquelle le timer A doit compter + int TimerAL; + int TimerAcnt; // timerA counter = valeur courante du Timer A + int TimerB; // timerB limit = valeur jusqu'à laquelle le timer B doit compter + int TimerBL; + int TimerBcnt; // timerB counter = valeur courante du Timer B + int Mode; // Mode actuel des voie 3 et 6 (normal / special) + int DAC; // DAC enabled flag + channel_t CHANNEL[Ym2612_Emu::channel_count]; // Les 6 voies du YM2612 + int REG[2][0x100]; // Sauvegardes des valeurs de tout les registres, c'est facultatif + // cela nous rend le debuggage plus facile +}; + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define ATTACK 0 +#define DECAY 1 +#define SUBSTAIN 2 +#define RELEASE 3 + +// SIN_LBITS <= 16 +// LFO_HBITS <= 16 +// (SIN_LBITS + SIN_HBITS) <= 26 +// (ENV_LBITS + ENV_HBITS) <= 28 +// (LFO_LBITS + LFO_HBITS) <= 28 + +#define SIN_HBITS 12 // Sinus phase counter int part +#define SIN_LBITS (26 - SIN_HBITS) // Sinus phase counter float part (best setting) + +#if (SIN_LBITS > 16) +#define SIN_LBITS 16 // Can't be greater than 16 bits +#endif + +#define ENV_HBITS 12 // Env phase counter int part +#define ENV_LBITS (28 - ENV_HBITS) // Env phase counter float part (best setting) + +#define LFO_HBITS 10 // LFO phase counter int part +#define LFO_LBITS (28 - LFO_HBITS) // LFO phase counter float part (best setting) + +#define SIN_LENGHT (1 << SIN_HBITS) +#define ENV_LENGHT (1 << ENV_HBITS) +#define LFO_LENGHT (1 << LFO_HBITS) + +#define TL_LENGHT (ENV_LENGHT * 3) // Env + TL scaling + LFO + +#define SIN_MASK (SIN_LENGHT - 1) +#define ENV_MASK (ENV_LENGHT - 1) +#define LFO_MASK (LFO_LENGHT - 1) + +#define ENV_STEP (96.0 / ENV_LENGHT) // ENV_MAX = 96 dB + +#define ENV_ATTACK ((ENV_LENGHT * 0) << ENV_LBITS) +#define ENV_DECAY ((ENV_LENGHT * 1) << ENV_LBITS) +#define ENV_END ((ENV_LENGHT * 2) << ENV_LBITS) + +#define MAX_OUT_BITS (SIN_HBITS + SIN_LBITS + 2) // Modulation = -4 <--> +4 +#define MAX_OUT ((1 << MAX_OUT_BITS) - 1) + +#define PG_CUT_OFF ((int) (78.0 / ENV_STEP)) +#define ENV_CUT_OFF ((int) (68.0 / ENV_STEP)) + +#define AR_RATE 399128 +#define DR_RATE 5514396 + +//#define AR_RATE 426136 +//#define DR_RATE (AR_RATE * 12) + +#define LFO_FMS_LBITS 9 // FIXED (LFO_FMS_BASE gives somethink as 1) +#define LFO_FMS_BASE ((int) (0.05946309436 * 0.0338 * (double) (1 << LFO_FMS_LBITS))) + +#define S0 0 // Stupid typo of the YM2612 +#define S1 2 +#define S2 1 +#define S3 3 + +inline void set_seg( slot_t& s, int seg ) +{ + s.env_xor = 0; + s.env_max = INT_MAX; + s.SEG = seg; + if ( seg & 4 ) + { + s.env_xor = ENV_MASK; + s.env_max = ENV_MASK; + } +} + +struct tables_t +{ + short SIN_TAB [SIN_LENGHT]; // SINUS TABLE (offset into TL TABLE) + int LFOcnt; // LFO counter = compteur-frequence pour le LFO + int LFOinc; // LFO step counter = pas d'incrementation du compteur-frequence du LFO + // plus le pas est grand, plus la frequence est grande + unsigned int AR_TAB [128]; // Attack rate table + unsigned int DR_TAB [96]; // Decay rate table + unsigned int DT_TAB [8] [32]; // Detune table + unsigned int SL_TAB [16]; // Substain level table + unsigned int NULL_RATE [32]; // Table for NULL rate + int LFO_INC_TAB [8]; // LFO step table + + short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) + + short LFO_ENV_TAB [LFO_LENGHT]; // LFO AMS TABLE (adjusted for 11.8 dB) + short LFO_FREQ_TAB [LFO_LENGHT]; // LFO FMS TABLE + int TL_TAB [TL_LENGHT * 2]; // TOTAL LEVEL TABLE (positif and minus) + unsigned int DECAY_TO_ATTACK [ENV_LENGHT]; // Conversion from decay to attack phase + unsigned int FINC_TAB [2048]; // Frequency step table +}; + +static const unsigned char DT_DEF_TAB [4 * 32] = +{ +// FD = 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +// FD = 1 + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + +// FD = 2 + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 16, 16, 16, + +// FD = 3 + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 20, 22, 22, 22, 22 +}; + +static const unsigned char FKEY_TAB [16] = +{ + 0, 0, 0, 0, + 0, 0, 0, 1, + 2, 3, 3, 3, + 3, 3, 3, 3 +}; + +static const unsigned char LFO_AMS_TAB [4] = +{ + 31, 4, 1, 0 +}; + +static const unsigned char LFO_FMS_TAB [8] = +{ + LFO_FMS_BASE * 0, LFO_FMS_BASE * 1, + LFO_FMS_BASE * 2, LFO_FMS_BASE * 3, + LFO_FMS_BASE * 4, LFO_FMS_BASE * 6, + LFO_FMS_BASE * 12, LFO_FMS_BASE * 24 +}; + +inline void YM2612_Special_Update() { } + +struct Ym2612_Impl +{ + enum { channel_count = Ym2612_Emu::channel_count }; + + state_t YM2612; + int mute_mask; + tables_t g; + + void KEY_ON( channel_t&, int ); + void KEY_OFF( channel_t&, int ); + int SLOT_SET( int, int ); + int CHANNEL_SET( int, int ); + int YM_SET( int, int ); + + void set_rate( double sample_rate, double clock_factor ); + void reset(); + void write0( int addr, int data ); + void write1( int addr, int data ); + void run_timer( int ); + void run( int pair_count, Ym2612_Emu::sample_t* ); +}; + +void Ym2612_Impl::KEY_ON( channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp == RELEASE) // la touche est-elle rel'chee ? + { + SL->Fcnt = 0; + + // Fix Ecco 2 splash sound + + SL->Ecnt = (g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK) & SL->ChgEnM; + SL->ChgEnM = ~0; + +// SL->Ecnt = g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK; +// SL->Ecnt = 0; + + SL->Einc = SL->EincA; + SL->Ecmp = ENV_DECAY; + SL->Ecurp = ATTACK; + } +} + + +void Ym2612_Impl::KEY_OFF(channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp != RELEASE) // la touche est-elle appuyee ? + { + if (SL->Ecnt < ENV_DECAY) // attack phase ? + { + SL->Ecnt = (g.ENV_TAB [SL->Ecnt >> ENV_LBITS] << ENV_LBITS) + ENV_DECAY; + } + + SL->Einc = SL->EincR; + SL->Ecmp = ENV_END; + SL->Ecurp = RELEASE; + } +} + + +int Ym2612_Impl::SLOT_SET( int Adr, int data ) +{ + int nch = Adr & 3; + if ( nch == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [nch + (Adr & 0x100 ? 3 : 0)]; + slot_t& sl = ch.SLOT [(Adr >> 2) & 3]; + + switch ( Adr & 0xF0 ) + { + case 0x30: + if ( (sl.MUL = (data & 0x0F)) != 0 ) sl.MUL <<= 1; + else sl.MUL = 1; + + sl.DT = (int*) g.DT_TAB [(data >> 4) & 7]; + + ch.SLOT [0].Finc = -1; + + break; + + case 0x40: + sl.TL = data & 0x7F; + + // SOR2 do a lot of TL adjustement and this fix R.Shinobi jump sound... + YM2612_Special_Update(); + +#if ((ENV_HBITS - 7) < 0) + sl.TLL = sl.TL >> (7 - ENV_HBITS); +#else + sl.TLL = sl.TL << (ENV_HBITS - 7); +#endif + + break; + + case 0x50: + sl.KSR_S = 3 - (data >> 6); + + ch.SLOT [0].Finc = -1; + + if (data &= 0x1F) sl.AR = (int*) &g.AR_TAB [data << 1]; + else sl.AR = (int*) &g.NULL_RATE [0]; + + sl.EincA = sl.AR [sl.KSR]; + if (sl.Ecurp == ATTACK) sl.Einc = sl.EincA; + break; + + case 0x60: + if ( (sl.AMSon = (data & 0x80)) != 0 ) sl.AMS = ch.AMS; + else sl.AMS = 31; + + if (data &= 0x1F) sl.DR = (int*) &g.DR_TAB [data << 1]; + else sl.DR = (int*) &g.NULL_RATE [0]; + + sl.EincD = sl.DR [sl.KSR]; + if (sl.Ecurp == DECAY) sl.Einc = sl.EincD; + break; + + case 0x70: + if (data &= 0x1F) sl.SR = (int*) &g.DR_TAB [data << 1]; + else sl.SR = (int*) &g.NULL_RATE [0]; + + sl.EincS = sl.SR [sl.KSR]; + if ((sl.Ecurp == SUBSTAIN) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincS; + break; + + case 0x80: + sl.SLL = g.SL_TAB [data >> 4]; + + sl.RR = (int*) &g.DR_TAB [((data & 0xF) << 2) + 2]; + + sl.EincR = sl.RR [sl.KSR]; + if ((sl.Ecurp == RELEASE) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincR; + break; + + case 0x90: + // SSG-EG envelope shapes : + /* + E At Al H + + 1 0 0 0 \\\\ + 1 0 0 1 \___ + 1 0 1 0 \/\/ + 1 0 1 1 \ + 1 1 0 0 //// + 1 1 0 1 / + 1 1 1 0 /\/\ + 1 1 1 1 /___ + + E = SSG-EG enable + At = Start negate + Al = Altern + H = Hold */ + + set_seg( sl, (data & 8) ? (data & 0x0F) : 0 ); + break; + } + + return 0; +} + + +int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) +{ + int num = Adr & 3; + if ( num == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [num + (Adr & 0x100 ? 3 : 0)]; + + switch ( Adr & 0xFC ) + { + case 0xA0: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x700) + data; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA4: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x0FF) + ((data & 0x07) << 8); + ch.FOCT [0] = (data & 0x38) >> 3; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA8: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x700) + data; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xAC: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x0FF) + ((data & 0x07) << 8); + YM2612.CHANNEL [2].FOCT [num] = (data & 0x38) >> 3; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xB0: + if ( ch.ALGO != (data & 7) ) + { + // Fix VectorMan 2 heli sound (level 1) + YM2612_Special_Update(); + + ch.ALGO = data & 7; + + ch.SLOT [0].ChgEnM = 0; + ch.SLOT [1].ChgEnM = 0; + ch.SLOT [2].ChgEnM = 0; + ch.SLOT [3].ChgEnM = 0; + } + + ch.FB = 9 - ((data >> 3) & 7); // Real thing ? + +// if (ch.FB = ((data >> 3) & 7)) ch.FB = 9 - ch.FB; // Thunder force 4 (music stage 8), Gynoug, Aladdin bug sound... +// else ch.FB = 31; + break; + + case 0xB4: { + YM2612_Special_Update(); + + ch.LEFT = 0 - ((data >> 7) & 1); + ch.RIGHT = 0 - ((data >> 6) & 1); + + ch.AMS = LFO_AMS_TAB [(data >> 4) & 3]; + ch.FMS = LFO_FMS_TAB [data & 7]; + + for ( int i = 0; i < 4; i++ ) + { + slot_t& sl = ch.SLOT [i]; + sl.AMS = (sl.AMSon ? ch.AMS : 31); + } + break; + } + } + + return 0; +} + + +int Ym2612_Impl::YM_SET(int Adr, int data) +{ + switch ( Adr ) + { + case 0x22: + if (data & 8) // LFO enable + { + // Cool Spot music 1, LFO modified severals time which + // distord the sound, have to check that on a real genesis... + + g.LFOinc = g.LFO_INC_TAB [data & 7]; + } + else + { + g.LFOinc = g.LFOcnt = 0; + } + break; + + case 0x24: + YM2612.TimerA = (YM2612.TimerA & 0x003) | (((int) data) << 2); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x25: + YM2612.TimerA = (YM2612.TimerA & 0x3FC) | (data & 3); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x26: + YM2612.TimerB = data; + + if (YM2612.TimerBL != (256 - YM2612.TimerB) << (4 + 12)) + { + YM2612.TimerBcnt = YM2612.TimerBL = (256 - YM2612.TimerB) << (4 + 12); + } + break; + + case 0x27: + // Parametre divers + // b7 = CSM MODE + // b6 = 3 slot mode + // b5 = reset b + // b4 = reset a + // b3 = timer enable b + // b2 = timer enable a + // b1 = load b + // b0 = load a + + if ((data ^ YM2612.Mode) & 0x40) + { + // We changed the channel 2 mode, so recalculate phase step + // This fix the punch sound in Street of Rage 2 + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; // recalculate phase step + } + +// if ((data & 2) && (YM2612.Status & 2)) YM2612.TimerBcnt = YM2612.TimerBL; +// if ((data & 1) && (YM2612.Status & 1)) YM2612.TimerAcnt = YM2612.TimerAL; + +// YM2612.Status &= (~data >> 4); // Reset du Status au cas ou c'est demande + YM2612.Status &= (~data >> 4) & (data >> 2); // Reset Status + + YM2612.Mode = data; + break; + + case 0x28: { + int nch = data & 3; + if ( nch == 3 ) + return 1; + if ( data & 4 ) + nch += 3; + channel_t& ch = YM2612.CHANNEL [nch]; + + YM2612_Special_Update(); + + if (data & 0x10) KEY_ON(ch, S0); // On appuie sur la touche pour le slot 1 + else KEY_OFF(ch, S0); // On rel'che la touche pour le slot 1 + if (data & 0x20) KEY_ON(ch, S1); // On appuie sur la touche pour le slot 3 + else KEY_OFF(ch, S1); // On rel'che la touche pour le slot 3 + if (data & 0x40) KEY_ON(ch, S2); // On appuie sur la touche pour le slot 2 + else KEY_OFF(ch, S2); // On rel'che la touche pour le slot 2 + if (data & 0x80) KEY_ON(ch, S3); // On appuie sur la touche pour le slot 4 + else KEY_OFF(ch, S3); // On rel'che la touche pour le slot 4 + break; + } + + case 0x2B: + if (YM2612.DAC ^ (data & 0x80)) YM2612_Special_Update(); + + YM2612.DAC = data & 0x80; // activation/desactivation du DAC + break; + } + + return 0; +} + +void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) +{ + assert( sample_rate ); + assert( clock_rate > sample_rate ); + + int i; + + // 144 = 12 * (prescale * 2) = 12 * 6 * 2 + // prescale set to 6 by default + + double Frequence = clock_rate / sample_rate / 144.0; + if ( fabs( Frequence - 1.0 ) < 0.0000001 ) + Frequence = 1.0; + YM2612.TimerBase = int (Frequence * 4096.0); + + // Tableau TL : + // [0 - 4095] = +output [4095 - ...] = +output overflow (fill with 0) + // [12288 - 16383] = -output [16384 - ...] = -output overflow (fill with 0) + + for(i = 0; i < TL_LENGHT; i++) + { + if (i >= PG_CUT_OFF) // YM2612 cut off sound after 78 dB (14 bits output ?) + { + g.TL_TAB [TL_LENGHT + i] = g.TL_TAB [i] = 0; + } + else + { + double x = MAX_OUT; // Max output + x /= pow( 10.0, (ENV_STEP * i) / 20.0 ); // Decibel -> Voltage + + g.TL_TAB [i] = (int) x; + g.TL_TAB [TL_LENGHT + i] = -g.TL_TAB [i]; + } + } + + // Tableau SIN : + // g.SIN_TAB [x] [y] = sin(x) * y; + // x = phase and y = volume + + g.SIN_TAB [0] = g.SIN_TAB [SIN_LENGHT / 2] = PG_CUT_OFF; + + for(i = 1; i <= SIN_LENGHT / 4; i++) + { + double x = sin(2.0 * PI * (double) (i) / (double) (SIN_LENGHT)); // Sinus + x = 20 * log10(1 / x); // convert to dB + + int j = (int) (x / ENV_STEP); // Get TL range + + if (j > PG_CUT_OFF) j = (int) PG_CUT_OFF; + + g.SIN_TAB [i] = g.SIN_TAB [(SIN_LENGHT / 2) - i] = j; + g.SIN_TAB [(SIN_LENGHT / 2) + i] = g.SIN_TAB [SIN_LENGHT - i] = TL_LENGHT + j; + } + + // Tableau LFO (LFO wav) : + + for(i = 0; i < LFO_LENGHT; i++) + { + double x = sin(2.0 * PI * (double) (i) / (double) (LFO_LENGHT)); // Sinus + x += 1.0; + x /= 2.0; // positive only + x *= 11.8 / ENV_STEP; // ajusted to MAX enveloppe modulation + + g.LFO_ENV_TAB [i] = (int) x; + + x = sin(2.0 * PI * (double) (i) / (double) (LFO_LENGHT)); // Sinus + x *= (double) ((1 << (LFO_HBITS - 1)) - 1); + + g.LFO_FREQ_TAB [i] = (int) x; + + } + + // Tableau Enveloppe : + // g.ENV_TAB [0] -> g.ENV_TAB [ENV_LENGHT - 1] = attack curve + // g.ENV_TAB [ENV_LENGHT] -> g.ENV_TAB [2 * ENV_LENGHT - 1] = decay curve + + for(i = 0; i < ENV_LENGHT; i++) + { + // Attack curve (x^8 - music level 2 Vectorman 2) + double x = pow(((double) ((ENV_LENGHT - 1) - i) / (double) (ENV_LENGHT)), 8); + x *= ENV_LENGHT; + + g.ENV_TAB [i] = (int) x; + + // Decay curve (just linear) + x = pow(((double) (i) / (double) (ENV_LENGHT)), 1); + x *= ENV_LENGHT; + + g.ENV_TAB [ENV_LENGHT + i] = (int) x; + } + for ( i = 0; i < 8; i++ ) + g.ENV_TAB [i + ENV_LENGHT * 2] = 0; + + g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state + + // Tableau pour la conversion Attack -> Decay and Decay -> Attack + + int j = ENV_LENGHT - 1; + for ( i = 0; i < ENV_LENGHT; i++ ) + { + while ( j && g.ENV_TAB [j] < i ) + j--; + + g.DECAY_TO_ATTACK [i] = j << ENV_LBITS; + } + + // Tableau pour le Substain Level + + for(i = 0; i < 15; i++) + { + double x = i * 3; // 3 and not 6 (Mickey Mania first music for test) + x /= ENV_STEP; + + g.SL_TAB [i] = ((int) x << ENV_LBITS) + ENV_DECAY; + } + + g.SL_TAB [15] = ((ENV_LENGHT - 1) << ENV_LBITS) + ENV_DECAY; // special case : volume off + + // Tableau Frequency Step + + for(i = 0; i < 2048; i++) + { + double x = (double) (i) * Frequence; + +#if ((SIN_LBITS + SIN_HBITS - (21 - 7)) < 0) + x /= (double) (1 << ((21 - 7) - SIN_LBITS - SIN_HBITS)); +#else + x *= (double) (1 << (SIN_LBITS + SIN_HBITS - (21 - 7))); +#endif + + x /= 2.0; // because MUL = value * 2 + + g.FINC_TAB [i] = (unsigned int) x; + } + + // Tableaux Attack & Decay Rate + + for(i = 0; i < 4; i++) + { + g.AR_TAB [i] = 0; + g.DR_TAB [i] = 0; + } + + for(i = 0; i < 60; i++) + { + double x = Frequence; + + x *= 1.0 + ((i & 3) * 0.25); // bits 0-1 : x1.00, x1.25, x1.50, x1.75 + x *= (double) (1 << ((i >> 2))); // bits 2-5 : shift bits (x2^0 - x2^15) + x *= (double) (ENV_LENGHT << ENV_LBITS); // on ajuste pour le tableau g.ENV_TAB + + g.AR_TAB [i + 4] = (unsigned int) (x / AR_RATE); + g.DR_TAB [i + 4] = (unsigned int) (x / DR_RATE); + } + + for(i = 64; i < 96; i++) + { + g.AR_TAB [i] = g.AR_TAB [63]; + g.DR_TAB [i] = g.DR_TAB [63]; + + g.NULL_RATE [i - 64] = 0; + } + + for ( i = 96; i < 128; i++ ) + g.AR_TAB [i] = 0; + + // Tableau Detune + + for(i = 0; i < 4; i++) + { + for (int j = 0; j < 32; j++) + { +#if ((SIN_LBITS + SIN_HBITS - 21) < 0) + double y = (double) DT_DEF_TAB [(i << 5) + j] * Frequence / (double) (1 << (21 - SIN_LBITS - SIN_HBITS)); +#else + double y = (double) DT_DEF_TAB [(i << 5) + j] * Frequence * (double) (1 << (SIN_LBITS + SIN_HBITS - 21)); +#endif + + g.DT_TAB [i + 0] [j] = (int) y; + g.DT_TAB [i + 4] [j] = (int) -y; + } + } + + // Tableau LFO + g.LFO_INC_TAB [0] = (unsigned int) (3.98 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [1] = (unsigned int) (5.56 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [2] = (unsigned int) (6.02 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [3] = (unsigned int) (6.37 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [4] = (unsigned int) (6.88 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [5] = (unsigned int) (9.63 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [6] = (unsigned int) (48.1 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [7] = (unsigned int) (72.2 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + + reset(); +} + +const char* Ym2612_Emu::set_rate( double sample_rate, double clock_rate ) +{ + if ( !impl ) + { + impl = (Ym2612_Impl*) malloc( sizeof *impl ); + if ( !impl ) + return "Out of memory"; + impl->mute_mask = 0; + } + memset( &impl->YM2612, 0, sizeof impl->YM2612 ); + + impl->set_rate( sample_rate, clock_rate ); + + return 0; +} + +Ym2612_Emu::~Ym2612_Emu() +{ + free( impl ); +} + +inline void Ym2612_Impl::write0( int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr < 0x30 ) + { + YM2612.REG [0] [opn_addr] = data; + YM_SET( opn_addr, data ); + } + else if ( YM2612.REG [0] [opn_addr] != data ) + { + YM2612.REG [0] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr, data ); + else + CHANNEL_SET( opn_addr, data ); + } +} + +inline void Ym2612_Impl::write1( int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr >= 0x30 && YM2612.REG [1] [opn_addr] != data ) + { + YM2612.REG [1] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr + 0x100, data ); + else + CHANNEL_SET( opn_addr + 0x100, data ); + } +} + +void Ym2612_Emu::reset() +{ + impl->reset(); +} + +void Ym2612_Impl::reset() +{ + g.LFOcnt = 0; + YM2612.TimerA = 0; + YM2612.TimerAL = 0; + YM2612.TimerAcnt = 0; + YM2612.TimerB = 0; + YM2612.TimerBL = 0; + YM2612.TimerBcnt = 0; + YM2612.DAC = 0; + + YM2612.Status = 0; + + int i; + for ( i = 0; i < channel_count; i++ ) + { + channel_t& ch = YM2612.CHANNEL [i]; + + ch.LEFT = ~0; + ch.RIGHT = ~0; + ch.ALGO = 0; + ch.FB = 31; + ch.FMS = 0; + ch.AMS = 0; + + for ( int j = 0 ;j < 4 ; j++ ) + { + ch.S0_OUT [j] = 0; + ch.FNUM [j] = 0; + ch.FOCT [j] = 0; + ch.KC [j] = 0; + + ch.SLOT [j].Fcnt = 0; + ch.SLOT [j].Finc = 0; + ch.SLOT [j].Ecnt = ENV_END; // Put it at the end of Decay phase... + ch.SLOT [j].Einc = 0; + ch.SLOT [j].Ecmp = 0; + ch.SLOT [j].Ecurp = RELEASE; + + ch.SLOT [j].ChgEnM = 0; + } + } + + for ( i = 0; i < 0x100; i++ ) + { + YM2612.REG [0] [i] = -1; + YM2612.REG [1] [i] = -1; + } + + for ( i = 0xB6; i >= 0xB4; i-- ) + { + write0( i, 0xC0 ); + write1( i, 0xC0 ); + } + + for ( i = 0xB2; i >= 0x22; i-- ) + { + write0( i, 0 ); + write1( i, 0 ); + } + + write0( 0x2A, 0x80 ); +} + +void Ym2612_Emu::write0( int addr, int data ) +{ + impl->write0( addr, data ); +} + +void Ym2612_Emu::write1( int addr, int data ) +{ + impl->write1( addr, data ); +} + +void Ym2612_Emu::mute_voices( int mask ) { impl->mute_mask = mask; } + +static void update_envelope_( slot_t* sl ) +{ + switch ( sl->Ecurp ) + { + case 0: + // Env_Attack_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = ENV_DECAY; + + sl->Einc = sl->EincD; + sl->Ecmp = sl->SLL; + sl->Ecurp = DECAY; + break; + + case 1: + // Env_Decay_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = sl->SLL; + + sl->Einc = sl->EincS; + sl->Ecmp = ENV_END; + sl->Ecurp = SUBSTAIN; + break; + + case 2: + // Env_Substain_Next(slot_t *SL) + if (sl->SEG & 8) // SSG envelope type + { + int release = sl->SEG & 1; + + if ( !release ) + { + // re KEY ON + + // sl->Fcnt = 0; + // sl->ChgEnM = ~0; + + sl->Ecnt = 0; + sl->Einc = sl->EincA; + sl->Ecmp = ENV_DECAY; + sl->Ecurp = ATTACK; + } + + set_seg( *sl, (sl->SEG << 1) & 4 ); + + if ( !release ) + break; + } + // fall through + + case 3: + // Env_Release_Next + sl->Ecnt = ENV_END; + sl->Einc = 0; + sl->Ecmp = ENV_END + 1; + break; + + // default: no op + } +} + +inline void update_envelope( slot_t& sl ) +{ + int ecmp = sl.Ecmp; + if ( (sl.Ecnt += sl.Einc) >= ecmp ) + update_envelope_( &sl ); +} + +template +struct ym2612_update_chan { + static void func( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); +}; + +typedef void (*ym2612_update_chan_t)( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); + +template +void ym2612_update_chan::func( tables_t& g, channel_t& ch, + Ym2612_Emu::sample_t* buf, int length ) +{ + int not_end = ch.SLOT [S3].Ecnt - ENV_END; + + // algo is a compile-time constant, so all conditions based on it are resolved + // during compilation + + // special cases + if ( algo == 7 ) + not_end |= ch.SLOT [S0].Ecnt - ENV_END; + + if ( algo >= 5 ) + not_end |= ch.SLOT [S2].Ecnt - ENV_END; + + if ( algo >= 4 ) + not_end |= ch.SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch.S0_OUT [1]; + + int in0 = ch.SLOT [S0].Fcnt; + int in1 = ch.SLOT [S1].Fcnt; + int in2 = ch.SLOT [S2].Fcnt; + int in3 = ch.SLOT [S3].Fcnt; + + int YM2612_LFOinc = g.LFOinc; + int YM2612_LFOcnt = g.LFOcnt + YM2612_LFOinc; + + if ( !not_end ) + return; + + do + { + // envelope + int const env_LFO = g.LFO_ENV_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK]; + + short const* const ENV_TAB = g.ENV_TAB; + + #define CALC_EN( x ) \ + int temp##x = ENV_TAB [ch.SLOT [S##x].Ecnt >> ENV_LBITS] + ch.SLOT [S##x].TLL; \ + int en##x = ((temp##x ^ ch.SLOT [S##x].env_xor) + (env_LFO >> ch.SLOT [S##x].AMS)) & \ + ((temp##x - ch.SLOT [S##x].env_max) >> 31); + + CALC_EN( 0 ) + CALC_EN( 1 ) + CALC_EN( 2 ) + CALC_EN( 3 ) + + int const* const TL_TAB = g.TL_TAB; + + #define SINT( i, o ) (TL_TAB [g.SIN_TAB [(i)] + (o)]) + + // feedback + int CH_S0_OUT_0 = ch.S0_OUT [0]; + { + int temp = in0 + ((CH_S0_OUT_0 + CH_S0_OUT_1) >> ch.FB); + CH_S0_OUT_1 = CH_S0_OUT_0; + CH_S0_OUT_0 = SINT( (temp >> SIN_LBITS) & SIN_MASK, en0 ); + } + + int CH_OUTd; + if ( algo == 0 ) + { + int temp = in1 + CH_S0_OUT_1; + temp = in2 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 1 ) + { + int temp = in2 + CH_S0_OUT_1 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 2 ) + { + int temp = in2 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + CH_S0_OUT_1 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 3 ) + { + int temp = in1 + CH_S0_OUT_1; + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 4 ) + { + int temp = in3 + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ); + //DO_LIMIT + } + else if ( algo == 5 ) + { + int temp = CH_S0_OUT_1; + CH_OUTd = SINT( ((in3 + temp) >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + temp) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( ((in2 + temp) >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 6 ) + { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 7 ) + { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ) + CH_S0_OUT_1; + //DO_LIMIT + } + + CH_OUTd >>= MAX_OUT_BITS - output_bits + 2; + + // update phase + unsigned freq_LFO = ((g.LFO_FREQ_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK] * + ch.FMS) >> (LFO_HBITS - 1 + 1)) + (1L << (LFO_FMS_LBITS - 1)); + YM2612_LFOcnt += YM2612_LFOinc; + in0 += (ch.SLOT [S0].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in1 += (ch.SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + + int t0 = buf [0] + (CH_OUTd & ch.LEFT); + int t1 = buf [1] + (CH_OUTd & ch.RIGHT); + + update_envelope( ch.SLOT [0] ); + update_envelope( ch.SLOT [1] ); + update_envelope( ch.SLOT [2] ); + update_envelope( ch.SLOT [3] ); + + ch.S0_OUT [0] = CH_S0_OUT_0; + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + while ( --length ); + + ch.S0_OUT [1] = CH_S0_OUT_1; + + ch.SLOT [S0].Fcnt = in0; + ch.SLOT [S1].Fcnt = in1; + ch.SLOT [S2].Fcnt = in2; + ch.SLOT [S3].Fcnt = in3; +} + +static const ym2612_update_chan_t UPDATE_CHAN [8] = { + &ym2612_update_chan<0>::func, + &ym2612_update_chan<1>::func, + &ym2612_update_chan<2>::func, + &ym2612_update_chan<3>::func, + &ym2612_update_chan<4>::func, + &ym2612_update_chan<5>::func, + &ym2612_update_chan<6>::func, + &ym2612_update_chan<7>::func +}; + +void Ym2612_Impl::run_timer( int length ) +{ + int const step = 6; + int remain = length; + do + { + int n = step; + if ( n > remain ) + n = remain; + remain -= n; + + long i = n * YM2612.TimerBase; + if (YM2612.Mode & 1) // Timer A ON ? + { + // if ((YM2612.TimerAcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerAcnt -= i) <= 0) + { + // timer a overflow + + YM2612.Status |= (YM2612.Mode & 0x04) >> 2; + YM2612.TimerAcnt += YM2612.TimerAL; + + if (YM2612.Mode & 0x80) + { + KEY_ON( YM2612.CHANNEL [2], 0 ); + KEY_ON( YM2612.CHANNEL [2], 1 ); + KEY_ON( YM2612.CHANNEL [2], 2 ); + KEY_ON( YM2612.CHANNEL [2], 3 ); + } + } + } + + if (YM2612.Mode & 2) // Timer B ON ? + { + // if ((YM2612.TimerBcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerBcnt -= i) <= 0) + { + // timer b overflow + YM2612.Status |= (YM2612.Mode & 0x08) >> 2; + YM2612.TimerBcnt += YM2612.TimerBL; + } + } + } + while ( remain > 0 ); +} + +void Ym2612_Impl::run( int pair_count, Ym2612_Emu::sample_t* out ) +{ + if ( pair_count <= 0 ) + return; + + if ( YM2612.Mode & 3 ) + run_timer( pair_count ); + + // Mise à jour des pas des compteurs-frequences s'ils ont ete modifies + + for ( int chi = 0; chi < channel_count; chi++ ) + { + channel_t& ch = YM2612.CHANNEL [chi]; + if ( ch.SLOT [0].Finc != -1 ) + continue; + + int i2 = 0; + if ( chi == 2 && (YM2612.Mode & 0x40) ) + i2 = 2; + + for ( int i = 0; i < 4; i++ ) + { + // static int seq [4] = { 2, 1, 3, 0 }; + // if ( i2 ) i2 = seq [i]; + + slot_t& sl = ch.SLOT [i]; + int finc = g.FINC_TAB [ch.FNUM [i2]] >> (7 - ch.FOCT [i2]); + int ksr = ch.KC [i2] >> sl.KSR_S; // keycode attenuation + sl.Finc = (finc + sl.DT [ch.KC [i2]]) * sl.MUL; + if (sl.KSR != ksr) // si le KSR a change alors + { // les differents taux pour l'enveloppe sont mis à jour + sl.KSR = ksr; + + sl.EincA = sl.AR [ksr]; + sl.EincD = sl.DR [ksr]; + sl.EincS = sl.SR [ksr]; + sl.EincR = sl.RR [ksr]; + + if (sl.Ecurp == ATTACK) + { + sl.Einc = sl.EincA; + } + else if (sl.Ecurp == DECAY) + { + sl.Einc = sl.EincD; + } + else if (sl.Ecnt < ENV_END) + { + if (sl.Ecurp == SUBSTAIN) + sl.Einc = sl.EincS; + else if (sl.Ecurp == RELEASE) + sl.Einc = sl.EincR; + } + } + + if ( i2 ) + i2 = (i2 ^ 2) ^ (i2 >> 1); + } + } + + for ( int i = 0; i < channel_count; i++ ) + { + if ( !(mute_mask & (1 << i)) && (i != 5 || !YM2612.DAC) ) + UPDATE_CHAN [YM2612.CHANNEL [i].ALGO]( g, YM2612.CHANNEL [i], out, pair_count ); + } + + g.LFOcnt += g.LFOinc * pair_count; +} + +void Ym2612_Emu::run( int pair_count, sample_t* out ) { impl->run( pair_count, out ); } diff --git a/game-music-emu/gme/Ym2612_Emu.h b/game-music-emu/gme/Ym2612_Emu.h new file mode 100644 index 000000000..383ac72d7 --- /dev/null +++ b/game-music-emu/gme/Ym2612_Emu.h @@ -0,0 +1,38 @@ +// YM2612 FM sound chip emulator interface + +// Game_Music_Emu 0.5.2 +#ifndef YM2612_EMU_H +#define YM2612_EMU_H + +struct Ym2612_Impl; + +class Ym2612_Emu { + Ym2612_Impl* impl; +public: + Ym2612_Emu() { impl = 0; } + ~Ym2612_Emu(); + + // Set output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + const char* set_rate( double sample_rate, double clock_rate ); + + // Reset to power-up state + void reset(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { channel_count = 6 }; + void mute_voices( int mask ); + + // Write addr to register 0 then data to register 1 + void write0( int addr, int data ); + + // Write addr to register 2 then data to register 3 + void write1( int addr, int data ); + + // Run and add pair_count samples into current output buffer contents + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/snes_spc/snes_spc/blargg_common.h b/game-music-emu/gme/blargg_common.h similarity index 87% rename from snes_spc/snes_spc/blargg_common.h rename to game-music-emu/gme/blargg_common.h index 75edff391..e48d6469b 100644 --- a/snes_spc/snes_spc/blargg_common.h +++ b/game-music-emu/gme/blargg_common.h @@ -1,7 +1,6 @@ // Sets up common environment for Shay Green's libraries. // To change configuration options, modify blargg_config.h, not this file. -// snes_spc 0.9.0 #ifndef BLARGG_COMMON_H #define BLARGG_COMMON_H @@ -16,13 +15,6 @@ #ifndef BLARGG_COMMON_H #define BLARGG_COMMON_H -// BLARGG_RESTRICT: equivalent to restrict, where supported -#if defined (__GNUC__) || _MSC_VER >= 1100 - #define BLARGG_RESTRICT __restrict -#else - #define BLARGG_RESTRICT -#endif - // STATIC_CAST(T,expr): Used in place of static_cast (expr) #ifndef STATIC_CAST #define STATIC_CAST(T,expr) ((T) (expr)) @@ -46,12 +38,10 @@ public: T* end() const { return begin_ + size_; } blargg_err_t resize( size_t n ) { - // TODO: blargg_common.cpp to hold this as an outline function, ugh void* p = realloc( begin_, n * sizeof (T) ); - if ( p ) - begin_ = (T*) p; - else if ( n > size_ ) // realloc failure only a problem if expanding + if ( !p && n ) return "Out of memory"; + begin_ = (T*) p; size_ = n; return 0; } @@ -64,11 +54,10 @@ public: }; #ifndef BLARGG_DISABLE_NOTHROW - // throw spec mandatory in ISO C++ if operator new can return NULL - #if __cplusplus >= 199711 || defined (__GNUC__) - #define BLARGG_THROWS( spec ) throw spec - #else + #if __cplusplus < 199711 #define BLARGG_THROWS( spec ) + #else + #define BLARGG_THROWS( spec ) throw spec #endif #define BLARGG_DISABLE_NOTHROW \ void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ @@ -79,7 +68,6 @@ public: #define BLARGG_NEW new (std::nothrow) #endif -// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) #define BLARGG_4CHAR( a, b, c, d ) \ ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) @@ -122,17 +110,18 @@ public: #endif // blargg_long/blargg_ulong = at least 32 bits, int if it's big enough +#include -#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF - typedef long blargg_long; -#else +#if INT_MAX >= 0x7FFFFFFF typedef int blargg_long; +#else + typedef long blargg_long; #endif -#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF - typedef unsigned long blargg_ulong; -#else +#if UINT_MAX >= 0xFFFFFFFF typedef unsigned blargg_ulong; +#else + typedef unsigned long blargg_ulong; #endif // BOOST::int8_t etc. diff --git a/game-music-emu/gme/blargg_config.h b/game-music-emu/gme/blargg_config.h new file mode 100644 index 000000000..5967b0fbe --- /dev/null +++ b/game-music-emu/gme/blargg_config.h @@ -0,0 +1,31 @@ +// Library configuration. Modify this file as necessary. + +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to use zlib for transparent decompression of gzipped files +//#define HAVE_ZLIB_H + +// Uncomment to support only the listed game music types. See gme_type_list.cpp +// for a list of all types. +//#define GME_TYPE_LIST gme_nsf_type, gme_gbs_type + +// Uncomment to enable platform-specific optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment to use faster, lower quality sound synthesis +//#define BLIP_BUFFER_FAST 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + + +#endif diff --git a/snes_spc/snes_spc/blargg_endian.h b/game-music-emu/gme/blargg_endian.h similarity index 67% rename from snes_spc/snes_spc/blargg_endian.h rename to game-music-emu/gme/blargg_endian.h index f2daca641..671655654 100644 --- a/snes_spc/snes_spc/blargg_endian.h +++ b/game-music-emu/gme/blargg_endian.h @@ -1,6 +1,6 @@ // CPU Byte Order Utilities -// snes_spc 0.9.0 +// Game_Music_Emu 0.5.2 #ifndef BLARGG_ENDIAN #define BLARGG_ENDIAN @@ -15,7 +15,6 @@ #if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) #define BLARGG_CPU_POWERPC 1 - #define BLARGG_CPU_RISC 1 #endif // BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only @@ -37,10 +36,10 @@ #endif #if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ - defined (__sparc__) || BLARGG_CPU_POWERPC || \ + defined (__mips__) || defined (__sparc__) || BLARGG_CPU_POWERPC || \ (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) #define BLARGG_BIG_ENDIAN 1 -#elif !defined (__mips__) +#else // No endian specified; assume little-endian, since it's most common #define BLARGG_LITTLE_ENDIAN 1 #endif @@ -65,60 +64,45 @@ inline void blargg_verify_byte_order() #endif } -inline unsigned get_le16( void const* p ) -{ - return (unsigned) ((unsigned char const*) p) [1] << 8 | - (unsigned) ((unsigned char const*) p) [0]; +inline unsigned get_le16( void const* p ) { + return ((unsigned char const*) p) [1] * 0x100u + + ((unsigned char const*) p) [0]; } - -inline unsigned get_be16( void const* p ) -{ - return (unsigned) ((unsigned char const*) p) [0] << 8 | - (unsigned) ((unsigned char const*) p) [1]; +inline unsigned get_be16( void const* p ) { + return ((unsigned char const*) p) [0] * 0x100u + + ((unsigned char const*) p) [1]; } - -inline blargg_ulong get_le32( void const* p ) -{ - return (blargg_ulong) ((unsigned char const*) p) [3] << 24 | - (blargg_ulong) ((unsigned char const*) p) [2] << 16 | - (blargg_ulong) ((unsigned char const*) p) [1] << 8 | - (blargg_ulong) ((unsigned char const*) p) [0]; +inline blargg_ulong get_le32( void const* p ) { + return ((unsigned char const*) p) [3] * 0x01000000u + + ((unsigned char const*) p) [2] * 0x00010000u + + ((unsigned char const*) p) [1] * 0x00000100u + + ((unsigned char const*) p) [0]; } - -inline blargg_ulong get_be32( void const* p ) -{ - return (blargg_ulong) ((unsigned char const*) p) [0] << 24 | - (blargg_ulong) ((unsigned char const*) p) [1] << 16 | - (blargg_ulong) ((unsigned char const*) p) [2] << 8 | - (blargg_ulong) ((unsigned char const*) p) [3]; +inline blargg_ulong get_be32( void const* p ) { + return ((unsigned char const*) p) [0] * 0x01000000u + + ((unsigned char const*) p) [1] * 0x00010000u + + ((unsigned char const*) p) [2] * 0x00000100u + + ((unsigned char const*) p) [3]; } - -inline void set_le16( void* p, unsigned n ) -{ +inline void set_le16( void* p, unsigned n ) { ((unsigned char*) p) [1] = (unsigned char) (n >> 8); ((unsigned char*) p) [0] = (unsigned char) n; } - -inline void set_be16( void* p, unsigned n ) -{ +inline void set_be16( void* p, unsigned n ) { ((unsigned char*) p) [0] = (unsigned char) (n >> 8); ((unsigned char*) p) [1] = (unsigned char) n; } - -inline void set_le32( void* p, blargg_ulong n ) -{ - ((unsigned char*) p) [0] = (unsigned char) n; - ((unsigned char*) p) [1] = (unsigned char) (n >> 8); - ((unsigned char*) p) [2] = (unsigned char) (n >> 16); +inline void set_le32( void* p, blargg_ulong n ) { ((unsigned char*) p) [3] = (unsigned char) (n >> 24); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; } - -inline void set_be32( void* p, blargg_ulong n ) -{ - ((unsigned char*) p) [3] = (unsigned char) n; - ((unsigned char*) p) [2] = (unsigned char) (n >> 8); - ((unsigned char*) p) [1] = (unsigned char) (n >> 16); +inline void set_be32( void* p, blargg_ulong n ) { ((unsigned char*) p) [0] = (unsigned char) (n >> 24); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [3] = (unsigned char) n; } #if BLARGG_NONPORTABLE @@ -133,41 +117,30 @@ inline void set_be32( void* p, blargg_ulong n ) #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) - - #if BLARGG_CPU_POWERPC - // PowerPC has special byte-reversed instructions - #if defined (__MWERKS__) - #define GET_LE16( addr ) (__lhbrx( addr, 0 )) - #define GET_LE32( addr ) (__lwbrx( addr, 0 )) - #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) - #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) - #elif defined (__GNUC__) - #define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;}) - #define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;}) - #define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );}) - #define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );}) - #endif - #endif + #endif + + #if BLARGG_CPU_POWERPC && defined (__MWERKS__) + // PowerPC has special byte-reversed instructions + // to do: assumes that PowerPC is running in big-endian mode + // to do: implement for other compilers which don't support these macros + #define GET_LE16( addr ) (__lhbrx( (addr), 0 )) + #define GET_LE32( addr ) (__lwbrx( (addr), 0 )) + #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) + #define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 )) #endif #endif #ifndef GET_LE16 #define GET_LE16( addr ) get_le16( addr ) - #define SET_LE16( addr, data ) set_le16( addr, data ) -#endif - -#ifndef GET_LE32 #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) #define SET_LE32( addr, data ) set_le32( addr, data ) #endif #ifndef GET_BE16 #define GET_BE16( addr ) get_be16( addr ) - #define SET_BE16( addr, data ) set_be16( addr, data ) -#endif - -#ifndef GET_BE32 #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) #define SET_BE32( addr, data ) set_be32( addr, data ) #endif diff --git a/snes_spc/snes_spc/blargg_source.h b/game-music-emu/gme/blargg_source.h similarity index 75% rename from snes_spc/snes_spc/blargg_source.h rename to game-music-emu/gme/blargg_source.h index 5e45c4fb4..945bf3498 100644 --- a/snes_spc/snes_spc/blargg_source.h +++ b/game-music-emu/gme/blargg_source.h @@ -1,9 +1,4 @@ -/* Included at the beginning of library source files, after all other #include lines. -Sets up helpful macros and services used in my source code. They don't need -module an annoying module prefix on their names since they are defined after -all other #include lines. */ - -// snes_spc 0.9.0 +// Included at the beginning of library source files, after all other #include lines #ifndef BLARGG_SOURCE_H #define BLARGG_SOURCE_H @@ -22,7 +17,7 @@ all other #include lines. */ // Like printf() except output goes to debug log file. Might be defined to do // nothing (not even evaluate its arguments). // void dprintf( const char* format, ... ); -static inline void blargg_dprintf_( const char*, ... ) { } +inline void blargg_dprintf_( const char*, ... ) { } #undef dprintf #define dprintf (1) ? (void) 0 : blargg_dprintf_ @@ -47,25 +42,9 @@ static inline void blargg_dprintf_( const char*, ... ) { } #undef min #undef max -#define DEF_MIN_MAX( type ) \ - static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\ - static inline type max( type x, type y ) { if ( y < x ) return x; return y; } - -DEF_MIN_MAX( int ) -DEF_MIN_MAX( unsigned ) -DEF_MIN_MAX( long ) -DEF_MIN_MAX( unsigned long ) -DEF_MIN_MAX( float ) -DEF_MIN_MAX( double ) - -#undef DEF_MIN_MAX - -/* // using const references generates crappy code, and I am currenly only using these // for built-in types, so they take arguments by value -// TODO: remove -inline int min( int x, int y ) template inline T min( T x, T y ) { @@ -81,7 +60,6 @@ inline T max( T x, T y ) return y; return x; } -*/ // TODO: good idea? bad idea? #undef byte diff --git a/game-music-emu/gme/gb_cpu_io.h b/game-music-emu/gme/gb_cpu_io.h new file mode 100644 index 000000000..ada99eadc --- /dev/null +++ b/game-music-emu/gme/gb_cpu_io.h @@ -0,0 +1,72 @@ + +#include "Gbs_Emu.h" + +#include "blargg_source.h" + +int Gbs_Emu::cpu_read( gb_addr_t addr ) +{ + int result = *cpu::get_code( addr ); + if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) + result = apu.read_register( clock(), addr ); +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr ); + else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 ) + dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr ); +#endif + return result; +} + +void Gbs_Emu::cpu_write( gb_addr_t addr, int data ) +{ + unsigned offset = addr - ram_addr; + if ( offset <= 0xFFFF - ram_addr ) + { + ram [offset] = data; + if ( (addr ^ 0xE000) <= 0x1F80 - 1 ) + { + if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) + { + GME_APU_HOOK( this, addr - Gb_Apu::start_addr, data ); + apu.write_register( clock(), addr, data ); + } + else if ( (addr ^ 0xFF06) < 2 ) + update_timer(); + else if ( addr == joypad_addr ) + ram [offset] = 0; // keep joypad return value 0 + else + ram [offset] = 0xFF; + + //if ( addr == 0xFFFF ) + // dprintf( "Wrote interrupt mask\n" ); + } + } + else if ( (addr ^ 0x2000) <= 0x2000 - 1 ) + { + set_bank( data ); + } +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + { + dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr ); + } +#endif +} + +#define CPU_READ_FAST( cpu, addr, time, out ) \ + CPU_READ_FAST_( STATIC_CAST(Gbs_Emu*,cpu), addr, time, out ) + +#define CPU_READ_FAST_( emu, addr, time, out ) \ +{\ + out = READ_PROG( addr );\ + if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count )\ + out = emu->apu.read_register( emu->cpu_time - time * clocks_per_instr, addr );\ + else\ + check( out == emu->cpu_read( addr ) );\ +} + +#define CPU_READ( cpu, addr, time ) \ + STATIC_CAST(Gbs_Emu*,cpu)->cpu_read( addr ) + +#define CPU_WRITE( cpu, addr, data, time ) \ + STATIC_CAST(Gbs_Emu*,cpu)->cpu_write( addr, data ) diff --git a/game-music-emu/gme/gme.cpp b/game-music-emu/gme/gme.cpp new file mode 100644 index 000000000..23e7198d3 --- /dev/null +++ b/game-music-emu/gme/gme.cpp @@ -0,0 +1,350 @@ +// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ + +#define IN_GME 1 + +#include "Music_Emu.h" + +#if !GME_DISABLE_STEREO_DEPTH +#include "Effects_Buffer.h" +#endif +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifndef GME_TYPE_LIST + +// Default list of all supported game music types (copy this to blargg_config.h +// if you want to modify it) +#define GME_TYPE_LIST \ + gme_ay_type,\ + gme_gbs_type,\ + gme_gym_type,\ + gme_hes_type,\ + gme_kss_type,\ + gme_nsf_type,\ + gme_nsfe_type,\ + gme_sap_type,\ + gme_spc_type,\ + gme_vgm_type,\ + gme_vgz_type + +#endif + +gme_type_t const* GMEAPI gme_type_list() +{ + static gme_type_t const gme_type_list_ [] = { GME_TYPE_LIST, 0 }; + return gme_type_list_; +} + +const char* GMEAPI gme_identify_header( void const* header ) +{ + switch ( get_be32( header ) ) + { + case BLARGG_4CHAR('Z','X','A','Y'): return "AY"; + case BLARGG_4CHAR('G','B','S',0x01): return "GBS"; + case BLARGG_4CHAR('G','Y','M','X'): return "GYM"; + case BLARGG_4CHAR('H','E','S','M'): return "HES"; + case BLARGG_4CHAR('K','S','C','C'): + case BLARGG_4CHAR('K','S','S','X'): return "KSS"; + case BLARGG_4CHAR('N','E','S','M'): return "NSF"; + case BLARGG_4CHAR('N','S','F','E'): return "NSFE"; + case BLARGG_4CHAR('S','A','P',0x0D): return "SAP"; + case BLARGG_4CHAR('S','N','E','S'): return "SPC"; + case BLARGG_4CHAR('V','g','m',' '): return "VGM"; + } + return ""; +} + +static void to_uppercase( const char* in, int len, char* out ) +{ + for ( int i = 0; i < len; i++ ) + { + if ( !(out [i] = toupper( in [i] )) ) + return; + } + *out = 0; // extension too long +} + +gme_type_t GMEAPI gme_identify_extension( const char* extension_ ) +{ + char const* end = strrchr( extension_, '.' ); + if ( end ) + extension_ = end + 1; + + char extension [6]; + to_uppercase( extension_, sizeof extension, extension ); + + for ( gme_type_t const* types = gme_type_list(); *types; types++ ) + if ( !strcmp( extension, (*types)->extension_ ) ) + return *types; + return 0; +} + +gme_err_t GMEAPI gme_identify_file( const char* path, gme_type_t* type_out ) +{ + *type_out = gme_identify_extension( path ); + // TODO: don't examine header if file has extension? + if ( !*type_out ) + { + char header [4]; + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + RETURN_ERR( in.read( header, sizeof header ) ); + *type_out = gme_identify_extension( gme_identify_header( header ) ); + } + return 0; +} + +gme_err_t GMEAPI gme_open_data( void const* data, long size, Music_Emu** out, int sample_rate ) +{ + require( (data || !size) && out ); + *out = 0; + + gme_type_t file_type = 0; + if ( size >= 4 ) + file_type = gme_identify_extension( gme_identify_header( data ) ); + if ( !file_type ) + return gme_wrong_file_type; + + Music_Emu* emu = gme_new_emu( file_type, sample_rate ); + CHECK_ALLOC( emu ); + + gme_err_t err = gme_load_data( emu, data, size ); + + if ( err ) + delete emu; + else + *out = emu; + + return err; +} + +GMEEXPORT gme_err_t GMEAPI gme_open_file( const char* path, Music_Emu** out, int sample_rate ) +{ + require( path && out ); + *out = 0; + + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + + char header [4]; + int header_size = 0; + + gme_type_t file_type = gme_identify_extension( path ); + if ( !file_type ) + { + header_size = sizeof header; + RETURN_ERR( in.read( header, sizeof header ) ); + file_type = gme_identify_extension( gme_identify_header( header ) ); + } + if ( !file_type ) + return gme_wrong_file_type; + + Music_Emu* emu = gme_new_emu( file_type, sample_rate ); + CHECK_ALLOC( emu ); + + // optimization: avoids seeking/re-reading header + Remaining_Reader rem( header, header_size, &in ); + gme_err_t err = emu->load( rem ); + in.close(); + + if ( err ) + delete emu; + else + *out = emu; + + return err; +} + +Music_Emu* GMEAPI gme_new_emu( gme_type_t type, int rate ) +{ + if ( type ) + { + if ( rate == gme_info_only ) + return type->new_info(); + + Music_Emu* me = type->new_emu(); + if ( me ) + { + #if !GME_DISABLE_STEREO_DEPTH + if ( type->flags_ & 1 ) + { + me->effects_buffer = BLARGG_NEW Effects_Buffer; + if ( me->effects_buffer ) + me->set_buffer( me->effects_buffer ); + } + + if ( !(type->flags_ & 1) || me->effects_buffer ) + #endif + { + if ( !me->set_sample_rate( rate ) ) + { + check( me->type() == type ); + return me; + } + } + delete me; + } + } + return 0; +} + +gme_err_t GMEAPI gme_load_file( Music_Emu* me, const char* path ) { return me->load_file( path ); } + +gme_err_t GMEAPI gme_load_data( Music_Emu* me, void const* data, long size ) +{ + Mem_File_Reader in( data, size ); + return me->load( in ); +} + +gme_err_t GMEAPI gme_load_custom( Music_Emu* me, gme_reader_t func, long size, void* data ) +{ + Callback_Reader in( func, size, data ); + return me->load( in ); +} + +void GMEAPI gme_delete( Music_Emu* me ) { delete me; } + +gme_type_t GMEAPI gme_type( Music_Emu const* me ) { return me->type(); } + +const char* GMEAPI gme_warning( Music_Emu* me ) { return me->warning(); } + +int GMEAPI gme_track_count( Music_Emu const* me ) { return me->track_count(); } + +struct gme_info_t_ : gme_info_t +{ + track_info_t info; + + BLARGG_DISABLE_NOTHROW +}; + +gme_err_t GMEAPI gme_track_info( Music_Emu const* me, gme_info_t** out, int track ) +{ + *out = NULL; + + gme_info_t_* info = BLARGG_NEW gme_info_t_; + CHECK_ALLOC( info ); + + gme_err_t err = me->track_info( &info->info, track ); + if ( err ) + { + gme_free_info( info ); + return err; + } + + #define COPY(name) info->name = info->info.name; + + COPY( length ); + COPY( intro_length ); + COPY( loop_length ); + + info->i4 = -1; + info->i5 = -1; + info->i6 = -1; + info->i7 = -1; + info->i8 = -1; + info->i9 = -1; + info->i10 = -1; + info->i11 = -1; + info->i12 = -1; + info->i13 = -1; + info->i14 = -1; + info->i15 = -1; + + info->s7 = ""; + info->s8 = ""; + info->s9 = ""; + info->s10 = ""; + info->s11 = ""; + info->s12 = ""; + info->s13 = ""; + info->s14 = ""; + info->s15 = ""; + + COPY( system ); + COPY( game ); + COPY( song ); + COPY( author ); + COPY( copyright ); + COPY( comment ); + COPY( dumper ); + + #undef COPY + + info->play_length = info->length; + if ( info->play_length <= 0 ) + { + info->play_length = info->intro_length + 2 * info->loop_length; // intro + 2 loops + if ( info->play_length <= 0 ) + info->play_length = 150 * 1000; // 2.5 minutes + } + + *out = info; + + return 0; +} + +void GMEAPI gme_free_info( gme_info_t* info ) +{ + delete STATIC_CAST(gme_info_t_*,info); +} + +void GMEAPI gme_set_stereo_depth( Music_Emu* me, double depth ) +{ +#if !GME_DISABLE_STEREO_DEPTH + if ( me->effects_buffer ) + STATIC_CAST(Effects_Buffer*,me->effects_buffer)->set_depth( depth ); +#endif +} + +void* GMEAPI gme_user_data ( Music_Emu const* me ) { return me->user_data(); } +void GMEAPI gme_set_user_data ( Music_Emu* me, void* new_user_data ) { me->set_user_data( new_user_data ); } +void GMEAPI gme_set_user_cleanup(Music_Emu* me, gme_user_cleanup_t func ) { me->set_user_cleanup( func ); } + +gme_err_t GMEAPI gme_start_track ( Music_Emu* me, int index ) { return me->start_track( index ); } +gme_err_t GMEAPI gme_play ( Music_Emu* me, int n, short* p ) { return me->play( n, p ); } +gme_err_t GMEAPI gme_skip ( Music_Emu* me, long n ) { return me->skip( n ); } +void GMEAPI gme_set_fade ( Music_Emu* me, int start_msec ) { me->set_fade( start_msec ); } +int GMEAPI gme_track_ended ( Music_Emu const* me ) { return me->track_ended(); } +int GMEAPI gme_tell ( Music_Emu const* me ) { return me->tell(); } +gme_err_t GMEAPI gme_seek ( Music_Emu* me, int msec ) { return me->seek( msec ); } +int GMEAPI gme_voice_count ( Music_Emu const* me ) { return me->voice_count(); } +void GMEAPI gme_ignore_silence ( Music_Emu* me, int disable ) { me->ignore_silence( disable != 0 ); } +void GMEAPI gme_set_tempo ( Music_Emu* me, double t ) { me->set_tempo( t ); } +void GMEAPI gme_mute_voice ( Music_Emu* me, int index, int mute ) { me->mute_voice( index, mute != 0 ); } +void GMEAPI gme_mute_voices ( Music_Emu* me, int mask ) { me->mute_voices( mask ); } + +void GMEAPI gme_set_equalizer ( Music_Emu* me, gme_equalizer_t const* eq ) +{ + Music_Emu::equalizer_t e = me->equalizer(); + e.treble = eq->treble; + e.bass = eq->bass; + me->set_equalizer( e ); +} + +void GMEAPI gme_equalizer( Music_Emu const* me, gme_equalizer_t* out ) +{ + gme_equalizer_t e = { }; + e.treble = me->equalizer().treble; + e.bass = me->equalizer().bass; + *out = e; +} + +const char* GMEAPI gme_voice_name( Music_Emu const* me, int i ) +{ + assert( (unsigned) i < (unsigned) me->voice_count() ); + return me->voice_names() [i]; +} diff --git a/game-music-emu/gme/gme.h b/game-music-emu/gme/gme.h new file mode 100644 index 000000000..5e9470d02 --- /dev/null +++ b/game-music-emu/gme/gme.h @@ -0,0 +1,259 @@ +/* Game music emulator library C interface (also usable from C++) */ + +/* Game_Music_Emu 0.5.2 */ +#ifndef GME_H +#define GME_H + +#ifdef _MSC_VER +#define GMEAPI __stdcall +#else +#define GMEAPI +#endif + +#if defined(_MSC_VER) && defined(GME_DLL) +#define GMEEXPORT __declspec(dllexport) +#define GMEIMPORT __declspec(dllimport) +#if IN_GME +#define GMEDLL GMEEXPORT +#else +#define GMEDLL GMEIMPORT +#endif +#else +#define GMEEXPORT +#define GMEIMPORT +#define GMEDLL +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/* Error string returned by library functions, or NULL if no error (success) */ +typedef const char* gme_err_t; + +/* First parameter of most gme_ functions is a pointer to the Music_Emu */ +typedef struct Music_Emu Music_Emu; + + +/******** Basic operations ********/ + +/* Create emulator and load game music file/data into it. Sets *out to new emulator. */ +GMEDLL gme_err_t GMEAPI gme_open_file( const char path [], Music_Emu** out, int sample_rate ); + +/* Number of tracks available */ +GMEDLL int GMEAPI gme_track_count( Music_Emu const* ); + +/* Start a track, where 0 is the first track */ +GMEDLL gme_err_t GMEAPI gme_start_track( Music_Emu*, int index ); + +/* Generate 'count' 16-bit signed samples info 'out'. Output is in stereo. */ +GMEDLL gme_err_t GMEAPI gme_play( Music_Emu*, int count, short out [] ); + +/* Skip n samples */ +GMEDLL gme_err_t GMEAPI gme_skip( Music_Emu*, long n ); + +/* Finish using emulator and free memory */ +GMEDLL void GMEAPI gme_delete( Music_Emu* ); + + +/******** Track position/length ********/ + +/* Set time to start fading track out. Once fade ends track_ended() returns true. +Fade time can be changed while track is playing. */ +GMEDLL void GMEAPI gme_set_fade( Music_Emu*, int start_msec ); + +/* True if a track has reached its end */ +GMEDLL int GMEAPI gme_track_ended( Music_Emu const* ); + +/* Number of milliseconds (1000 = one second) played since beginning of track */ +GMEDLL int GMEAPI gme_tell( Music_Emu const* ); + +/* Seek to new time in track. Seeking backwards or far forward can take a while. */ +GMEDLL gme_err_t GMEAPI gme_seek( Music_Emu*, int msec ); + + +/******** Informational ********/ + +/* If you only need track information from a music file, pass gme_info_only for +sample_rate to open/load. */ +enum { gme_info_only = -1 }; + +/* Most recent warning string, or NULL if none. Clears current warning after returning. +Warning is also cleared when loading a file and starting a track. */ +GMEDLL const char* GMEAPI gme_warning( Music_Emu* ); + +/* Load m3u playlist file (must be done after loading music) */ +GMEDLL gme_err_t GMEAPI gme_load_m3u( Music_Emu*, const char path [] ); + +/* Clear any loaded m3u playlist and any internal playlist that the music format +supports (NSFE for example). */ +GMEDLL void GMEAPI gme_clear_playlist( Music_Emu* ); + +/* Gets information for a particular track (length, name, author, etc.). +Must be freed after use. */ +typedef struct gme_info_t gme_info_t; +GMEDLL gme_err_t GMEAPI gme_track_info( Music_Emu const*, gme_info_t** out, int track ); + +/* Frees track information */ +GMEDLL void GMEAPI gme_free_info( gme_info_t* ); + +struct gme_info_t +{ + /* times in milliseconds; -1 if unknown */ + int length; /* total length, if file specifies it */ + int intro_length; /* length of song up to looping section */ + int loop_length; /* length of looping section */ + + /* Length if available, otherwise intro_length+loop_length*2 if available, + otherwise a default of 150000 (2.5 minutes). */ + int play_length; + + int i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14,i15; /* reserved */ + + /* empty string ("") if not available */ + const char* system; + const char* game; + const char* song; + const char* author; + const char* copyright; + const char* comment; + const char* dumper; + + const char *s7,*s8,*s9,*s10,*s11,*s12,*s13,*s14,*s15; /* reserved */ +}; + + +/******** Advanced playback ********/ + +/* Adjust stereo echo depth, where 0.0 = off and 1.0 = maximum. Has no effect for +GYM, SPC, and Sega Genesis VGM music */ +GMEDLL void GMEAPI gme_set_stereo_depth( Music_Emu*, double depth ); + +/* Disable automatic end-of-track detection and skipping of silence at beginning +if ignore is true */ +GMEDLL void GMEAPI gme_ignore_silence( Music_Emu*, int ignore ); + +/* Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. +Track length as returned by track_info() assumes a tempo of 1.0. */ +GMEDLL void GMEAPI gme_set_tempo( Music_Emu*, double tempo ); + +/* Number of voices used by currently loaded file */ +GMEDLL int GMEAPI gme_voice_count( Music_Emu const* ); + +/* Name of voice i, from 0 to gme_voice_count() - 1 */ +GMEDLL const char* GMEAPI gme_voice_name( Music_Emu const*, int i ); + +/* Mute/unmute voice i, where voice 0 is first voice */ +GMEDLL void GMEAPI gme_mute_voice( Music_Emu*, int index, int mute ); + +/* Set muting state of all voices at once using a bit mask, where -1 mutes all +voices, 0 unmutes them all, 0x01 mutes just the first voice, etc. */ +GMEDLL void GMEAPI gme_mute_voices( Music_Emu*, int muting_mask ); + +/* Frequency equalizer parameters (see gme.txt) */ +typedef struct gme_equalizer_t +{ + double treble; /* -50.0 = muffled, 0 = flat, +5.0 = extra-crisp */ + double bass; /* 1 = full bass, 90 = average, 16000 = almost no bass */ + + double d2,d3,d4,d5,d6,d7,d8,d9; /* reserved */ +} gme_equalizer_t; + +/* Get current frequency equalizater parameters */ +GMEDLL void GMEAPI gme_equalizer( Music_Emu const*, gme_equalizer_t* out ); + +/* Change frequency equalizer parameters */ +GMEDLL void GMEAPI gme_set_equalizer( Music_Emu*, gme_equalizer_t const* eq ); + + + +/******** Game music types ********/ + +/* Music file type identifier. Can also hold NULL. */ +typedef const struct gme_type_t_* gme_type_t; + +/* Emulator type constants for each supported file type */ +extern const gme_type_t + gme_ay_type, + gme_gbs_type, + gme_gym_type, + gme_hes_type, + gme_kss_type, + gme_nsf_type, + gme_nsfe_type, + gme_sap_type, + gme_spc_type, + gme_vgm_type, + gme_vgz_type; + +/* Type of this emulator */ +GMEDLL gme_type_t GMEAPI gme_type( Music_Emu const* ); + +/* Pointer to array of all music types, with NULL entry at end. Allows a player linked +to this library to support new music types without having to be updated. */ +GMEDLL gme_type_t const* GMEAPI gme_type_list(); + +/* Name of game system for this music file type */ +GMEDLL const char* GMEAPI gme_type_system( gme_type_t ); + +/* True if this music file type supports multiple tracks */ +GMEDLL int GMEAPI gme_type_multitrack( gme_type_t ); + + +/******** Advanced file loading ********/ + +/* Error returned if file type is not supported */ +extern const char* const gme_wrong_file_type; + +/* Same as gme_open_file(), but uses file data already in memory. Makes copy of data. */ +GMEDLL gme_err_t GMEAPI gme_open_data( void const* data, long size, Music_Emu** out, int sample_rate ); + +/* Determine likely game music type based on first four bytes of file. Returns +string containing proper file suffix (i.e. "NSF", "SPC", etc.) or "" if +file header is not recognized. */ +GMEDLL const char* GMEAPI gme_identify_header( void const* header ); + +/* Get corresponding music type for file path or extension passed in. */ +GMEDLL gme_type_t GMEAPI gme_identify_extension( const char path_or_extension [] ); + +/* Determine file type based on file's extension or header (if extension isn't recognized). +Sets *type_out to type, or 0 if unrecognized or error. */ +GMEDLL gme_err_t GMEAPI gme_identify_file( const char path [], gme_type_t* type_out ); + +/* Create new emulator and set sample rate. Returns NULL if out of memory. If you only need +track information, pass gme_info_only for sample_rate. */ +GMEDLL Music_Emu* GMEAPI gme_new_emu( gme_type_t, int sample_rate ); + +/* Load music file into emulator */ +GMEDLL gme_err_t GMEAPI gme_load_file( Music_Emu*, const char path [] ); + +/* Load music file from memory into emulator. Makes a copy of data passed. */ +GMEDLL gme_err_t GMEAPI gme_load_data( Music_Emu*, void const* data, long size ); + +/* Load music file using custom data reader function that will be called to +read file data. Most emulators load the entire file in one read call. */ +typedef gme_err_t (GMEAPI *gme_reader_t)( void* your_data, void* out, int count ); +GMEDLL gme_err_t GMEAPI gme_load_custom( Music_Emu*, gme_reader_t, long file_size, void* your_data ); + +/* Load m3u playlist file from memory (must be done after loading music) */ +GMEDLL gme_err_t GMEAPI gme_load_m3u_data( Music_Emu*, void const* data, long size ); + + +/******** User data ********/ + +/* Set/get pointer to data you want to associate with this emulator. +You can use this for whatever you want. */ +GMEDLL void GMEAPI gme_set_user_data( Music_Emu*, void* new_user_data ); +GMEDLL void* GMEAPI gme_user_data( Music_Emu const* ); + +/* Register cleanup function to be called when deleting emulator, or NULL to +clear it. Passes user_data to cleanup function. */ +typedef void (GMEAPI *gme_user_cleanup_t)( void* user_data ); +GMEDLL void GMEAPI gme_set_user_cleanup( Music_Emu*, gme_user_cleanup_t func ); + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/game-music-emu/gme/hes_cpu_io.h b/game-music-emu/gme/hes_cpu_io.h new file mode 100644 index 000000000..b3d71dad4 --- /dev/null +++ b/game-music-emu/gme/hes_cpu_io.h @@ -0,0 +1,101 @@ + +#include "Hes_Emu.h" + +#include "blargg_source.h" + +int Hes_Emu::cpu_read( hes_addr_t addr ) +{ + check( addr <= 0xFFFF ); + int result = *cpu::get_code( addr ); + if ( mmr [addr >> page_shift] == 0xFF ) + result = cpu_read_( addr ); + return result; +} + +void Hes_Emu::cpu_write( hes_addr_t addr, int data ) +{ + check( addr <= 0xFFFF ); + byte* out = write_pages [addr >> page_shift]; + addr &= page_size - 1; + if ( out ) + out [addr] = data; + else if ( mmr [addr >> page_shift] == 0xFF ) + cpu_write_( addr, data ); +} + +inline byte const* Hes_Emu::cpu_set_mmr( int page, int bank ) +{ + write_pages [page] = 0; + if ( bank < 0x80 ) + return rom.at_addr( bank * (blargg_long) page_size ); + + byte* data = 0; + switch ( bank ) + { + case 0xF8: + data = cpu::ram; + break; + + case 0xF9: + case 0xFA: + case 0xFB: + data = &sgx [(bank - 0xF9) * page_size]; + break; + + default: + if ( bank != 0xFF ) + dprintf( "Unmapped bank $%02X\n", bank ); + return rom.unmapped(); + } + + write_pages [page] = data; + return data; +} + +#define CPU_READ_FAST( cpu, addr, time, out ) \ + CPU_READ_FAST_( STATIC_CAST(Hes_Emu*,cpu), addr, time, out ) + +#define CPU_READ_FAST_( cpu, addr, time, out ) \ +{\ + out = READ_PROG( addr );\ + if ( mmr [addr >> page_shift] == 0xFF )\ + {\ + FLUSH_TIME();\ + out = cpu->cpu_read_( addr );\ + CACHE_TIME();\ + }\ +} + +#define CPU_WRITE_FAST( cpu, addr, data, time ) \ + CPU_WRITE_FAST_( STATIC_CAST(Hes_Emu*,cpu), addr, data, time ) + +#define CPU_WRITE_FAST_( cpu, addr, data, time ) \ +{\ + byte* out = cpu->write_pages [addr >> page_shift];\ + addr &= page_size - 1;\ + if ( out )\ + {\ + out [addr] = data;\ + }\ + else if ( mmr [addr >> page_shift] == 0xFF )\ + {\ + FLUSH_TIME();\ + cpu->cpu_write_( addr, data );\ + CACHE_TIME();\ + }\ +} + +#define CPU_READ( cpu, addr, time ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_read( addr ) + +#define CPU_WRITE( cpu, addr, data, time ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_write( addr, data ) + +#define CPU_WRITE_VDP( cpu, addr, data, time ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_write_vdp( addr, data ) + +#define CPU_SET_MMR( cpu, page, bank ) \ + STATIC_CAST(Hes_Emu*,cpu)->cpu_set_mmr( page, bank ) + +#define CPU_DONE( cpu, time, result_out ) \ + result_out = STATIC_CAST(Hes_Emu*,cpu)->cpu_done() diff --git a/game-music-emu/gme/nes_cpu_io.h b/game-music-emu/gme/nes_cpu_io.h new file mode 100644 index 000000000..4bae37931 --- /dev/null +++ b/game-music-emu/gme/nes_cpu_io.h @@ -0,0 +1,83 @@ + +#include "Nsf_Emu.h" + +#if !NSF_EMU_APU_ONLY + #include "Nes_Namco_Apu.h" +#endif + +#include "blargg_source.h" + +int Nsf_Emu::cpu_read( nes_addr_t addr ) +{ + int result; + + result = cpu::low_mem [addr & 0x7FF]; + if ( !(addr & 0xE000) ) + goto exit; + + result = *cpu::get_code( addr ); + if ( addr > 0x7FFF ) + goto exit; + + result = sram [addr & (sizeof sram - 1)]; + if ( addr > 0x5FFF ) + goto exit; + + if ( addr == Nes_Apu::status_addr ) + return apu.read_status( cpu::time() ); + + #if !NSF_EMU_APU_ONLY + if ( addr == Nes_Namco_Apu::data_reg_addr && namco ) + return namco->read_data(); + #endif + + result = addr >> 8; // simulate open bus + + if ( addr != 0x2002 ) + dprintf( "Read unmapped $%.4X\n", (unsigned) addr ); + +exit: + return result; +} + +void Nsf_Emu::cpu_write( nes_addr_t addr, int data ) +{ + { + nes_addr_t offset = addr ^ sram_addr; + if ( offset < sizeof sram ) + { + sram [offset] = data; + return; + } + } + { + int temp = addr & 0x7FF; + if ( !(addr & 0xE000) ) + { + cpu::low_mem [temp] = data; + return; + } + } + + if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr ) + { + GME_APU_HOOK( this, addr - Nes_Apu::start_addr, data ); + apu.write_register( cpu::time(), addr, data ); + return; + } + + unsigned bank = addr - bank_select_addr; + if ( bank < bank_count ) + { + blargg_long offset = rom.mask_addr( data * (blargg_long) bank_size ); + if ( offset >= rom.size() ) + set_warning( "Invalid bank" ); + cpu::map_code( (bank + 8) * bank_size, bank_size, rom.at_addr( offset ) ); + return; + } + + cpu_write_misc( addr, data ); +} + +#define CPU_READ( cpu, addr, time ) STATIC_CAST(Nsf_Emu&,*cpu).cpu_read( addr ) +#define CPU_WRITE( cpu, addr, data, time ) STATIC_CAST(Nsf_Emu&,*cpu).cpu_write( addr, data ) diff --git a/game-music-emu/gme/sap_cpu_io.h b/game-music-emu/gme/sap_cpu_io.h new file mode 100644 index 000000000..8c2f6dd09 --- /dev/null +++ b/game-music-emu/gme/sap_cpu_io.h @@ -0,0 +1,26 @@ + +#include "Sap_Emu.h" + +#include "blargg_source.h" + +#define CPU_WRITE( cpu, addr, data, time ) STATIC_CAST(Sap_Emu&,*cpu).cpu_write( addr, data ) + +void Sap_Emu::cpu_write( sap_addr_t addr, int data ) +{ + mem.ram [addr] = data; + if ( (addr >> 8) == 0xD2 ) + cpu_write_( addr, data ); +} + +#ifdef NDEBUG + #define CPU_READ( cpu, addr, time ) READ_LOW( addr ) +#else + #define CPU_READ( cpu, addr, time ) STATIC_CAST(Sap_Emu&,*cpu).cpu_read( addr ) + + int Sap_Emu::cpu_read( sap_addr_t addr ) + { + if ( (addr & 0xF900) == 0xD000 ) + dprintf( "Unmapped read $%04X\n", addr ); + return mem.ram [addr]; + } +#endif diff --git a/snes_spc/license.txt b/game-music-emu/license.txt similarity index 100% rename from snes_spc/license.txt rename to game-music-emu/license.txt diff --git a/game-music-emu/readme.txt b/game-music-emu/readme.txt new file mode 100644 index 000000000..e3470bfa1 --- /dev/null +++ b/game-music-emu/readme.txt @@ -0,0 +1,205 @@ +Game_Music_Emu 0.5.2: Game Music Emulators +------------------------------------------ +Game_Music_Emu is a collection of video game music file emulators that +support the following formats and systems: + +AY ZX Spectrum/Amstrad CPC +GBS Nintendo Game Boy +GYM Sega Genesis/Mega Drive +HES NEC TurboGrafx-16/PC Engine +KSS MSX Home Computer/other Z80 systems (doesn't support FM sound) +NSF/NSFE Nintendo NES/Famicom (with VRC 6, Namco 106, and FME-7 sound) +SAP Atari systems using POKEY sound chip +SPC Super Nintendo/Super Famicom +VGM/VGZ Sega Master System/Mark III, Sega Genesis/Mega Drive,BBC Micro + +Features: +* Can be used in C and C++ code +* High emphasis has been placed on making the library very easy to use +* One set of common functions work with all emulators the same way +* Several code examples, including music player using SDL +* Portable code for use on any system with modern or older C++ compilers +* Adjustable output sample rate using quality band-limited resampling +* Uniform access to text information fields and track timing information +* End-of-track fading and automatic look ahead silence detection +* Treble/bass and stereo echo for AY/GBS/HES/KSS/NSF/NSFE/SAP/VGM +* Tempo can be adjusted and individual voices can be muted while playing +* Can read music data from file, memory, or custom reader function/class +* Can access track information without having to load into full emulator +* M3U track listing support for multi-track formats +* Modular design allows elimination of unneeded emulators/features + +This library has been used in game music players for Windows, Linux on +several architectures, Mac OS, MorphOS, Xbox, PlayStation Portable, +GP2X, and Nintendo DS. + +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) + + +Getting Started +--------------- +Build a program consisting of demo/basics.c, demo/Wave_Writer.cpp, and +all source files in gme/. Be sure "test.nsf" is in the same directory. +Running the program should generate the recording "out.wav". + +Read gme.txt for more information. Post to the discussion forum for +assistance. + + +Files +----- +gme.txt General notes about the library +changes.txt Changes made since previous releases +design.txt Library design notes +license.txt GNU Lesser General Public License + +test.nsf Test file for NSF emulator +test.m3u Test m3u playlist for features.c demo + +demo/ + basics.c Records NSF file to wave sound file + cpp_basics.cpp C++ version of basics.c + features.c Demonstrates many additional features + Wave_Writer.h WAVE sound file writer used for demo output + Wave_Writer.cpp + +player/ Player using the SDL multimedia library + player.cpp Simple music player with waveform display + Music_Player.cpp Stand alone player for background music + Music_Player.h + Audio_Scope.cpp Audio waveform scope + Audio_Scope.h + +gme/ + blargg_config.h Library configuration (modify this file as needed) + + gme.h C interface (also usable in C++, and simpler too) + gme.cpp + + Gme_File.h File loading and track information + Music_Emu.h Track playback and adjustments + Data_Reader.h Custom data readers + + Effects_Buffer.h Sound buffer with stereo echo and panning + Effects_Buffer.cpp + + M3u_Playlist.h M3U playlist support + M3u_Playlist.cpp + + Ay_Emu.h ZX Spectrum AY emulator + Ay_Emu.cpp + Ay_Apu.cpp + Ay_Apu.h + Ay_Cpu.cpp + Ay_Cpu.h + + Gbs_Emu.h Nintendo Game Boy GBS emulator + Gbs_Emu.cpp + Gb_Apu.cpp + Gb_Apu.h + Gb_Cpu.cpp + Gb_Cpu.h + gb_cpu_io.h + Gb_Oscs.cpp + Gb_Oscs.h + + Hes_Emu.h TurboGrafx-16/PC Engine HES emulator + Hes_Apu.cpp + Hes_Apu.h + Hes_Cpu.cpp + Hes_Cpu.h + hes_cpu_io.h + Hes_Emu.cpp + + Kss_Emu.h MSX Home Computer/other Z80 systems KSS emulator + Kss_Emu.cpp + Kss_Cpu.cpp + Kss_Cpu.h + Kss_Scc_Apu.cpp + Kss_Scc_Apu.h + Ay_Apu.h + Ay_Apu.cpp + Sms_Apu.h + Sms_Apu.cpp + Sms_Oscs.h + + Nsf_Emu.h Nintendo NES NSF/NSFE emulator + Nsf_Emu.cpp + Nes_Apu.cpp + Nes_Apu.h + Nes_Cpu.cpp + Nes_Cpu.h + nes_cpu_io.h + Nes_Oscs.cpp + Nes_Oscs.h + Nes_Fme7_Apu.cpp + Nes_Fme7_Apu.h + Nes_Namco_Apu.cpp + Nes_Namco_Apu.h + Nes_Vrc6_Apu.cpp + Nes_Vrc6_Apu.h + Nsfe_Emu.h NSFE support + Nsfe_Emu.cpp + + Spc_Emu.h Super Nintendo SPC emulator + Spc_Emu.cpp + Snes_Spc.cpp + Snes_Spc.h + Spc_Cpu.cpp + Spc_Cpu.h + Spc_Dsp.cpp + Spc_Dsp.h + Fir_Resampler.cpp + Fir_Resampler.h + + Sap_Emu.h Atari SAP emulator + Sap_Emu.cpp + Sap_Apu.cpp + Sap_Apu.h + Sap_Cpu.cpp + Sap_Cpu.h + sap_cpu_io.h + + Vgm_Emu.h Sega VGM emulator + Vgm_Emu_Impl.cpp + Vgm_Emu_Impl.h + Vgm_Emu.cpp + Ym2413_Emu.cpp + Ym2413_Emu.h + Gym_Emu.h Sega Genesis GYM emulator + Gym_Emu.cpp + Sms_Apu.cpp Common Sega emulator files + Sms_Apu.h + Sms_Oscs.h + Ym2612_Emu.cpp + Ym2612_Emu.h + Dual_Resampler.cpp + Dual_Resampler.h + Fir_Resampler.cpp + Fir_Resampler.h + + blargg_common.h Common files needed by all emulators + blargg_endian.h + blargg_source.h + Blip_Buffer.cpp + Blip_Buffer.h + Gme_File.cpp + Music_Emu.cpp + Classic_Emu.h + Classic_Emu.cpp + Multi_Buffer.h + Multi_Buffer.cpp + Data_Reader.cpp + + +Legal +----- +Game_Music_Emu library copyright (C) 2003-2006 Shay Green. +SNES SPC DSP emulator based on OpenSPC, copyright (C) 2002 Brad Martin. +Sega Genesis YM2612 emulator copyright (C) 2002 Stephane Dallongeville. + +-- +Shay Green diff --git a/snes_spc/CMakeLists.txt b/snes_spc/CMakeLists.txt deleted file mode 100644 index 13ef63b26..000000000 --- a/snes_spc/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required( VERSION 2.4 ) -include( CheckCXXCompilerFlag ) - -# I don't plan on debugging this, so make it a release build. -set( CMAKE_BUILD_TYPE "RelWithDebInfo" ) - -if( CMAKE_COMPILER_IS_GNUCXX ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) - if( NOT PROFILE ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer" ) - endif( NOT PROFILE ) - check_cxx_compiler_flag( -Wno-array-bounds HAVE_NO_ARRAY_BOUNDS ) - if( HAVE_NO_ARRAY_BOUNDS ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-array-bounds" ) - endif( HAVE_NO_ARRAY_BOUNDS ) -endif( CMAKE_COMPILER_IS_GNUCXX ) - -add_library( snes_spc - snes_spc/dsp.cpp - snes_spc/SNES_SPC.cpp - snes_spc/SNES_SPC_misc.cpp - snes_spc/SNES_SPC_state.cpp - snes_spc/spc.cpp - snes_spc/SPC_DSP.cpp - snes_spc/SPC_Filter.cpp ) -target_link_libraries( snes_spc ) diff --git a/snes_spc/changes.txt b/snes_spc/changes.txt deleted file mode 100644 index 33661832b..000000000 --- a/snes_spc/changes.txt +++ /dev/null @@ -1,107 +0,0 @@ -snes_spc Change Log -------------------- - -snes_spc 0.9.0 --------------- -- Improved documentation - -- SPC: Added spc_skip() function for quickly seeking in an SPC music -file. Runs 3-4x faster than normal playback using the fast DSP (or about -43-60X real-time on my 400 MHz Mac). - -- SPC: Added spc_set_tempo() to change tempo of SPC music playback. - -- SPC: Sample generation is now corrected to generate exactly one pair -of samples every 32 clocks without exception. Before it could generate a -few samples more or less depending on how far ahead or behind DSP was at -the moment. - -- SPC: Changed spc_reset() and spc_soft_reset() to also reset output -buffer (see spc.h). - -- SPC: Fixed minor timer counting bug. - -- SPC: Stack pointer wrap-around is now emulated (and without any -noticeable performance hit). - -- SPC: Runs about 5% faster due to various optimizations. - -- SPC: Found way to make fast DSP register accesses cycle-accurate in -most cases, without reducing performance. Allows fast DSP to pass most -of my validation tests. - -- DSP: Added surround disable support to fast DSP again. - -- DSP: Improved voice un-muting to take effect immediately on fast DSP. - -- DSP: Noise shift register now starts at 0x4000 instead of 0x4001 as it -incorrectly did before. - -- Converted library to C++ code internally. A C interface is still -included in spc.h and dsp.h. Note that these are different than the -previous interface, so your code will require minor changes: - - Old SPC code New SPC code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #include "spc/spc.h" #include "snes_spc/spc.h" - - snes_spc_t* spc; SNES_SPC* spc; - spc = malloc( sizeof (snes_spc_t) ); spc = spc_new(); - spc_init( spc ); - - spc_end_frame( time ); spc_end_frame( spc, time ); - /* etc. */ - - /* done using SPC */ spc_delete( spc ); - - - Old DSP code New DSP code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #include "spc/spc_dsp.h" #include "snes_spc/dsp.h" - - spc_dsp_init( ram ); SPC_DSP* dsp; - dsp = spc_dsp_new(); - spc_dsp_init( dsp, ram ); - - spc_dsp_run( count ); spc_dsp_run( dsp, count ); - /* etc. */ - - /* done using DSP */ spc_dsp_delete( dsp ); - - -snes_spc 0.8.0 --------------- -- Added several demos - -- Added high-pass/low-pass filter to better match SNES sound - -- Added save state functionality for SPC and accurate DSP (but not fast -DSP) - -- Added emulation of reset switch on NES (soft reset) - -- Made source more compatible with pre-C99 compilers by eliminating -mid-block declarations - -- SPC: Many S-SMP accuracy improvements, mostly in memory access times - -- SPC: S-SMP speed improvements - -- SPC: Added SPC load/save functions and KON checking to help trim -silence from beginning - -- SPC: Changed spc_init() to have you allocate most of the memory used -by the library so you have more control over it - -- DSP: New highly accurate DSP and faster version derived from same code - -- DSP: Changed prefix from dsp_ to spc_dsp_. Your DSP code will require -changes. - -- DSP: Removed surround disable and gain. Gain can now be done with the -dsp_filter module, and surround disable will probably only be -implemented in the fast DSP at some point. - -- DSP: Changed interface to work in clocks rather than samples, -necessary for the new accurate DSP. Sample output is now done with -separate functions. Your DSP code will require changes. diff --git a/snes_spc/demo/benchmark.c b/snes_spc/demo/benchmark.c deleted file mode 100644 index 6b91faaf1..000000000 --- a/snes_spc/demo/benchmark.c +++ /dev/null @@ -1,58 +0,0 @@ -/* Measures performance of SPC emulator. Takes about 4 seconds. -NOTE: This assumes that the program is getting all processor time; you might need to -arrange for this or else the performance will be reported lower than it really is. - -Usage: benchmark [test.spc] -*/ - -#include "snes_spc/spc.h" - -#include "demo_util.h" /* error(), load_file() */ -#include - -clock_t start_timing( int seconds ); - -int main( int argc, char** argv ) -{ - /* Load SPC */ - long spc_size; - void* spc = load_file( (argc > 1) ? argv [1] : "test.spc", &spc_size ); - SNES_SPC* snes_spc = spc_new(); - if ( !snes_spc ) error( "Out of memory" ); - spc_load_spc( snes_spc, spc, spc_size ); - free( spc ); - - { - /* Repeatedly fill buffer for 4 seconds */ - int const seconds = 4; - #define BUF_SIZE 4096 - clock_t end = start_timing( seconds ); - int count = 0; - while ( clock() < end ) - { - static short buf [BUF_SIZE]; - error( spc_play( snes_spc, BUF_SIZE, buf ) ); - count++; - } - - /* Report performance based on how many buffer fills were possible */ - { - double rate = (double) count * BUF_SIZE / (spc_sample_rate * 2 * seconds); - printf( "Performance: %.3fx real-time, or %.0f%% CPU for normal rate\n", - rate, 100.0 / rate ); - } - } - - return 0; -} - -/* Synchronizes with host clock and returns clock() time that is duration seconds from now */ -clock_t start_timing( int duration ) -{ - clock_t clock_dur = duration * CLOCKS_PER_SEC; - clock_t time = clock(); - while ( clock() == time ) { } - if ( clock() - time > clock_dur ) - error( "Insufficient clock() time resolution" ); - return clock() + clock_dur; -} diff --git a/snes_spc/demo/comm.c b/snes_spc/demo/comm.c deleted file mode 100644 index 4198a2de1..000000000 --- a/snes_spc/demo/comm.c +++ /dev/null @@ -1,70 +0,0 @@ -/* Communicates with SPC the way the SNES would. - -Note: You'll need an "spc_rom.h" file that contains the 64-byte IPL ROM contents */ - -#include "snes_spc/spc.h" - -#include "demo_util.h" -#include -#include - -static SNES_SPC* snes_spc; - -/* Make port access more convenient. Fakes time by simply incrementing it each call. */ -static spc_time_t stime; -static int pread ( int port ) { return spc_read_port( snes_spc, stime++, port ); } -static void pwrite( int port, int data ) { spc_write_port( snes_spc, stime++, port, data ); } - -static unsigned char const spc_rom [spc_rom_size] = { - /* ROM data not provided with emulator */ - #include "spc_rom.h" -}; - -int main() -{ - int i; - - /* Data to upload */ - static unsigned char const data [4] = "\xFA\xDE\xD1"; - unsigned const data_addr = 0xF5; /* second I/O port */ - - snes_spc = spc_new(); - if ( !snes_spc ) error( "Out of memory" ); - spc_init_rom( snes_spc, spc_rom ); - spc_reset( snes_spc ); - - /* Simulate reads and writes that SNES code would do */ - - /* Wait for SPC to be ready */ - while ( pread( 0 ) != 0xAA || pread( 1 ) != 0xBB ) { } - - /* Start address */ - pwrite( 2, data_addr & 0xFF ); - pwrite( 3, data_addr >> 8 ); - - /* Tell SPC to start transfer and wait for acknowledgement */ - pwrite( 0, 0xCC ); - pwrite( 1, 0x01 ); - while ( pread( 0 ) != 0xCC ) { } - - /* Send each byte and wait for acknowledgement */ - for ( i = 0; i < 4; i++ ) - { - printf( "%02X ", data [i] ); - pwrite( 1, data [i] ); - pwrite( 0, i ); - while ( pread( 0 ) != i ) { } - } - printf( "\n" ); - - /* Verify that data was transferred properly */ - for ( i = 0; i < 3; i++ ) - printf( "%02X ", pread( i + 1 ) ); - printf( "\n" ); - - printf( "Cycles: %ld\n", (long) stime ); - - spc_delete( snes_spc ); - - return 0; -} diff --git a/snes_spc/demo/demo_util.c b/snes_spc/demo/demo_util.c deleted file mode 100644 index 61f250d77..000000000 --- a/snes_spc/demo/demo_util.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "demo_util.h" - -#include -#include -#include -#include - -/* Copyright (C) 2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -unsigned char* load_file( const char* path, long* size_out ) -{ - size_t size; - unsigned char* data; - - FILE* in = fopen( path, "rb" ); - if ( !in ) error( "Couldn't open file" ); - - fseek( in, 0, SEEK_END ); - size = ftell( in ); - if ( size_out ) - *size_out = size; - rewind( in ); - - data = (unsigned char*) malloc( size ); - if ( !data ) error( "Out of memory" ); - - if ( fread( data, 1, size, in ) < size ) error( "Couldn't read file" ); - fclose( in ); - - return data; -} - -void write_file( const char* path, void const* in, long size ) -{ - FILE* out = fopen( path, "wb" ); - if ( !out ) error( "Couldn't create file" ); - if ( (long) fwrite( in, 1, size, out ) < size ) error( "Couldn't write file" ); - fclose( out ); -} - -void error( const char* str ) -{ - if ( str ) - { - fprintf( stderr, "Error: %s\n", str ); - exit( EXIT_FAILURE ); - } -} diff --git a/snes_spc/demo/demo_util.h b/snes_spc/demo/demo_util.h deleted file mode 100644 index d52e571b1..000000000 --- a/snes_spc/demo/demo_util.h +++ /dev/null @@ -1,31 +0,0 @@ -/* General-purpose utilities used by demos */ - -/* snes_spc 0.9.0 */ -#ifndef DEMO_UTIL_H -#define DEMO_UTIL_H - -/* commonly used headers */ -#include -#include -#include -#include - -#ifdef __cplusplus - extern "C" { -#endif - -/* If str is not NULL, prints it and exits program, otherwise returns */ -void error( const char* str ); - -/* Loads file and returns pointer to data in memory, allocated with malloc(). -If size_out != NULL, sets *size_out to size of data. */ -unsigned char* load_file( const char* path, long* size_out ); - -/* Writes data to file */ -void write_file( const char* path, void const* in, long size ); - -#ifdef __cplusplus - } -#endif - -#endif diff --git a/snes_spc/demo/play_spc.c b/snes_spc/demo/play_spc.c deleted file mode 100644 index f3e4d5aeb..000000000 --- a/snes_spc/demo/play_spc.c +++ /dev/null @@ -1,57 +0,0 @@ -/* Records SPC into wave file. Uses dsp_filter to give more authentic sound. - -Usage: play_spc [test.spc] -*/ - -#include "snes_spc/spc.h" - -#include "wave_writer.h" -#include "demo_util.h" /* error(), load_file() */ - -int main( int argc, char** argv ) -{ - /* Create emulator and filter */ - SNES_SPC* snes_spc = spc_new(); - SPC_Filter* filter = spc_filter_new(); - if ( !snes_spc || !filter ) error( "Out of memory" ); - - /* Load SPC */ - { - /* Load file into memory */ - long spc_size; - void* spc = load_file( (argc > 1) ? argv [1] : "test.spc", &spc_size ); - - /* Load SPC data into emulator */ - error( spc_load_spc( snes_spc, spc, spc_size ) ); - free( spc ); /* emulator makes copy of data */ - - /* Most SPC files have garbage data in the echo buffer, so clear that */ - spc_clear_echo( snes_spc ); - - /* Clear filter before playing */ - spc_filter_clear( filter ); - } - - /* Record 20 seconds to wave file */ - wave_open( spc_sample_rate, "out.wav" ); - wave_enable_stereo(); - while ( wave_sample_count() < 20 * spc_sample_rate * 2 ) - { - /* Play into buffer */ - #define BUF_SIZE 2048 - short buf [BUF_SIZE]; - error( spc_play( snes_spc, BUF_SIZE, buf ) ); - - /* Filter samples */ - spc_filter_run( filter, buf, BUF_SIZE ); - - wave_write( buf, BUF_SIZE ); - } - - /* Cleanup */ - spc_filter_delete( filter ); - spc_delete( snes_spc ); - wave_close(); - - return 0; -} diff --git a/snes_spc/demo/save_state.c b/snes_spc/demo/save_state.c deleted file mode 100644 index 1a6674ccc..000000000 --- a/snes_spc/demo/save_state.c +++ /dev/null @@ -1,107 +0,0 @@ -/* Loads "test.spc", skips 5 seconds, saves exact emulator state to "state.bin", -hen records 5 seconds to "first.wav". When run again, loads this state back into -emulator and records 5 seconds to "second.wav". These two wave files should -be identical. - -Usage: save_state [test.spc] -*/ - -#include "snes_spc/spc.h" - -#include "wave_writer.h" -#include "demo_util.h" /* error(), load_file() */ - -static SNES_SPC* snes_spc; - -void record_wav( const char* path, int secs ) -{ - /* Start writing wave file */ - wave_open( spc_sample_rate, path ); - wave_enable_stereo(); - while ( wave_sample_count() < secs * spc_sample_rate * 2 ) - { - /* Play into buffer */ - #define BUF_SIZE 2048 - short buf [BUF_SIZE]; - error( spc_play( snes_spc, BUF_SIZE, buf ) ); - - wave_write( buf, BUF_SIZE ); - } - wave_close(); -} - -void state_save( unsigned char** out, void* in, size_t size ) -{ - memcpy( *out, in, size ); - *out += size; -} - -void make_save_state( const char* path ) -{ - /* Load SPC */ - long spc_size; - void* spc = load_file( path, &spc_size ); - error( spc_load_spc( snes_spc, spc, spc_size ) ); - free( spc ); - spc_clear_echo( snes_spc ); - - /* Skip several seconds */ - error( spc_play( snes_spc, 5 * spc_sample_rate * 2, 0 ) ); - - /* Save state to file */ - { - static unsigned char state [spc_state_size]; /* buffer large enough for data */ - unsigned char* out = state; - spc_copy_state( snes_spc, &out, state_save ); - write_file( "state.bin", state, out - state ); - } - - record_wav( "first.wav", 5 ); -} - -void state_load( unsigned char** in, void* out, size_t size ) -{ - memcpy( out, *in, size ); - *in += size; -} - -void use_save_state() -{ - /* Load state into memory */ - long state_size; - unsigned char* state = load_file( "state.bin", &state_size ); - - /* Load into emulator */ - unsigned char* in = state; - spc_copy_state( snes_spc, &in, state_load ); - assert( in - state <= state_size ); /* be sure it didn't read past end */ - - record_wav( "second.wav", 5 ); -} - -int file_exists( const char* path ) -{ - FILE* file = fopen( path, "rb" ); - if ( !file ) - return 0; - - fclose( file ); - return 1; -} - -int main( int argc, char** argv ) -{ - snes_spc = spc_new(); - if ( !snes_spc ) error( "Out of memory" ); - - /* Make new state if it doesn't exist, otherwise load it and - record to wave file */ - if ( !file_exists( "state.bin" ) ) - make_save_state( (argc > 1) ? argv [1] : "test.spc" ); - else - use_save_state(); - - spc_delete( snes_spc ); - - return 0; -} diff --git a/snes_spc/demo/trim_spc.c b/snes_spc/demo/trim_spc.c deleted file mode 100644 index cf91c11e7..000000000 --- a/snes_spc/demo/trim_spc.c +++ /dev/null @@ -1,83 +0,0 @@ -/* Trims silence off beginning of SPC file. -Requires the accurate DSP; won't compile with the fast DSP. -Please note that the algorithm could be improved; this is just -a simple example showing the idea. - -Usage: trim_spc [test.spc [trimmed.spc]] -*/ - -#include "snes_spc/spc.h" - -#include "demo_util.h" /* error(), load_file() */ - -/* Change to 1 to have it trim to next key on event rather than trim silence */ -enum { use_kon_check = 0 }; - -/* True if all count samples from in are silent (or very close to it) */ -int is_silent( short const* in, int count ); - -int main( int argc, char** argv ) -{ - /* Load file into memory */ - long spc_size; - void* spc = load_file( (argc > 1) ? argv [1] : "test.spc", &spc_size ); - - /* Load into emulator */ - SNES_SPC* snes_spc = spc_new(); - if ( !snes_spc ) error( "Out of memory" ); - error( spc_load_spc( snes_spc, spc, spc_size ) ); - - /* Expand SPC data so there's enough room for emulator to save to. - We simply overwrite the emulator data in the old SPC file rather - than creating new SPC data. This preserves the text tags from - the old file. */ - if ( spc_size < spc_file_size ) - { - spc_size = spc_file_size; - spc = realloc( spc, spc_size ); /* leaks memory if allocation fails */ - if ( !spc ) error( "Out of memory" ); - } - - /* Keep saving SPC, then playing a little more. Once SPC becomes non-silent, - write the SPC data saved just before this. */ - { - long samples_trimmed = 0; - while ( 1 ) - { - #define BUF_SIZE 1024 - short buf [BUF_SIZE]; - - if ( samples_trimmed > 10 * spc_sample_rate * 2 ) - error( "Excess silence found" ); - - spc_clear_echo( snes_spc ); - spc_save_spc( snes_spc, spc ); - - /* Fill buffer */ - error( spc_play( snes_spc, BUF_SIZE, buf ) ); - samples_trimmed += BUF_SIZE; - - /* See if SPC became non-silent */ - if ( use_kon_check ? spc_check_kon( snes_spc ) : !is_silent( buf, BUF_SIZE ) ) - break; - } - - printf( "Trimmed %.1f seconds\n", (double) samples_trimmed / spc_sample_rate / 2 ); - } - - spc_delete( snes_spc ); - write_file( (argc > 2) ? argv [2] : "trimmed.spc", spc, spc_size ); - - return 0; -} - -int is_silent( short const* in, int count ) -{ - unsigned const threshold = 0x10; - while ( count-- ) - { - if ( (unsigned) (*in++ + threshold / 2) > threshold ) - return 0; - } - return 1; -} diff --git a/snes_spc/demo/wave_writer.c b/snes_spc/demo/wave_writer.c deleted file mode 100644 index b8da5d60f..000000000 --- a/snes_spc/demo/wave_writer.c +++ /dev/null @@ -1,153 +0,0 @@ -/* snes_spc 0.9.0. http://www.slack.net/~ant/ */ - -#include "wave_writer.h" - -#include -#include -#include - -/* Copyright (C) 2003-2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -enum { buf_size = 32768 * 2 }; -enum { header_size = 0x2C }; - -typedef short sample_t; - -static unsigned char* buf; -static FILE* file; -static long sample_count_; -static long sample_rate_; -static long buf_pos; -static int chan_count; - -static void exit_with_error( const char* str ) -{ - printf( "Error: %s\n", str ); getchar(); - exit( EXIT_FAILURE ); -} - -void wave_open( long sample_rate, const char* filename ) -{ - sample_count_ = 0; - sample_rate_ = sample_rate; - buf_pos = header_size; - chan_count = 1; - - buf = (unsigned char*) malloc( buf_size * sizeof *buf ); - if ( !buf ) - exit_with_error( "Out of memory" ); - - file = fopen( filename, "wb" ); - if ( !file ) - exit_with_error( "Couldn't open WAVE file for writing" ); - - setvbuf( file, 0, _IOFBF, 32 * 1024L ); -} - -void wave_enable_stereo( void ) -{ - chan_count = 2; -} - -static void flush_() -{ - if ( buf_pos && !fwrite( buf, buf_pos, 1, file ) ) - exit_with_error( "Couldn't write WAVE data" ); - buf_pos = 0; -} - -void wave_write( short const* in, long remain ) -{ - sample_count_ += remain; - while ( remain ) - { - if ( buf_pos >= buf_size ) - flush_(); - - { - unsigned char* p = &buf [buf_pos]; - long n = (buf_size - buf_pos) / sizeof (sample_t); - if ( n > remain ) - n = remain; - remain -= n; - - /* convert to LSB first format */ - while ( n-- ) - { - int s = *in++; - *p++ = (unsigned char) s; - *p++ = (unsigned char) (s >> 8); - } - - buf_pos = p - buf; - assert( buf_pos <= buf_size ); - } - } -} - -long wave_sample_count( void ) -{ - return sample_count_; -} - -static void set_le32( void* p, unsigned long n ) -{ - ((unsigned char*) p) [0] = (unsigned char) n; - ((unsigned char*) p) [1] = (unsigned char) (n >> 8); - ((unsigned char*) p) [2] = (unsigned char) (n >> 16); - ((unsigned char*) p) [3] = (unsigned char) (n >> 24); -} - -void wave_close( void ) -{ - if ( file ) - { - /* generate header */ - unsigned char h [header_size] = - { - 'R','I','F','F', - 0,0,0,0, /* length of rest of file */ - 'W','A','V','E', - 'f','m','t',' ', - 0x10,0,0,0, /* size of fmt chunk */ - 1,0, /* uncompressed format */ - 0,0, /* channel count */ - 0,0,0,0, /* sample rate */ - 0,0,0,0, /* bytes per second */ - 0,0, /* bytes per sample frame */ - 16,0, /* bits per sample */ - 'd','a','t','a', - 0,0,0,0, /* size of sample data */ - /* ... */ /* sample data */ - }; - long ds = sample_count_ * sizeof (sample_t); - int frame_size = chan_count * sizeof (sample_t); - - set_le32( h + 0x04, header_size - 8 + ds ); - h [0x16] = chan_count; - set_le32( h + 0x18, sample_rate_ ); - set_le32( h + 0x1C, sample_rate_ * frame_size ); - h [0x20] = frame_size; - set_le32( h + 0x28, ds ); - - flush_(); - - /* write header */ - fseek( file, 0, SEEK_SET ); - fwrite( h, sizeof h, 1, file ); - - fclose( file ); - file = 0; - free( buf ); - buf = 0; - } -} diff --git a/snes_spc/demo/wave_writer.h b/snes_spc/demo/wave_writer.h deleted file mode 100644 index 5472293bd..000000000 --- a/snes_spc/demo/wave_writer.h +++ /dev/null @@ -1,20 +0,0 @@ -/* WAVE sound file writer for recording 16-bit output during program development */ - -#ifndef WAVE_WRITER_H -#define WAVE_WRITER_H - -#ifdef __cplusplus - extern "C" { -#endif - -void wave_open( long sample_rate, const char* filename ); -void wave_enable_stereo( void ); -void wave_write( short const* in, long count ); -long wave_sample_count( void ); -void wave_close( void ); - -#ifdef __cplusplus - } -#endif - -#endif diff --git a/snes_spc/fast_dsp/SPC_DSP.h b/snes_spc/fast_dsp/SPC_DSP.h deleted file mode 100644 index 045ce3c2c..000000000 --- a/snes_spc/fast_dsp/SPC_DSP.h +++ /dev/null @@ -1,212 +0,0 @@ -// Fast SNES SPC-700 DSP emulator (about 3x speed of accurate one) - -// snes_spc 0.9.0 -#ifndef SPC_DSP_H -#define SPC_DSP_H - -#include "blargg_common.h" - -struct SPC_DSP { -public: - typedef BOOST::uint8_t uint8_t; - -// Setup - - // Initializes DSP and has it use the 64K RAM provided - void init( void* ram_64k ); - - // Sets destination for output samples. If out is NULL or out_size is 0, - // doesn't generate any. - typedef short sample_t; - void set_output( sample_t* out, int out_size ); - - // Number of samples written to output since it was last set, always - // a multiple of 2. Undefined if more samples were generated than - // output buffer could hold. - int sample_count() const; - -// Emulation - - // Resets DSP to power-on state - void reset(); - - // Emulates pressing reset switch on SNES - void soft_reset(); - - // Reads/writes DSP registers. For accuracy, you must first call spc_run_dsp() - // to catch the DSP up to present. - int read ( int addr ) const; - void write( int addr, int data ); - - // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks - // a pair of samples is be generated. - void run( int clock_count ); - -// Sound control - - // Mutes voices corresponding to non-zero bits in mask (overrides VxVOL with 0). - // Reduces emulation accuracy. - enum { voice_count = 8 }; - void mute_voices( int mask ); - - // If true, prevents channels and global volumes from being phase-negated - void disable_surround( bool disable = true ); - -// State - - // Resets DSP and uses supplied values to initialize registers - enum { register_count = 128 }; - void load( uint8_t const regs [register_count] ); - -// DSP register addresses - - // Global registers - enum { - r_mvoll = 0x0C, r_mvolr = 0x1C, - r_evoll = 0x2C, r_evolr = 0x3C, - r_kon = 0x4C, r_koff = 0x5C, - r_flg = 0x6C, r_endx = 0x7C, - r_efb = 0x0D, r_pmon = 0x2D, - r_non = 0x3D, r_eon = 0x4D, - r_dir = 0x5D, r_esa = 0x6D, - r_edl = 0x7D, - r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F - }; - - // Voice registers - enum { - v_voll = 0x00, v_volr = 0x01, - v_pitchl = 0x02, v_pitchh = 0x03, - v_srcn = 0x04, v_adsr0 = 0x05, - v_adsr1 = 0x06, v_gain = 0x07, - v_envx = 0x08, v_outx = 0x09 - }; - -public: - enum { extra_size = 16 }; - sample_t* extra() { return m.extra; } - sample_t const* out_pos() const { return m.out; } -public: - BLARGG_DISABLE_NOTHROW - - typedef BOOST::int8_t int8_t; - typedef BOOST::int16_t int16_t; - - enum { echo_hist_size = 8 }; - - enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; - enum { brr_buf_size = 12 }; - struct voice_t - { - int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) - int* buf_pos; // place in buffer where next samples will be decoded - int interp_pos; // relative fractional position in sample (0x1000 = 1.0) - int brr_addr; // address of current BRR block - int brr_offset; // current decoding offset in BRR block - int kon_delay; // KON delay/current setup phase - env_mode_t env_mode; - int env; // current envelope level - int hidden_env; // used by GAIN mode 7, very obscure quirk - int volume [2]; // copy of volume from DSP registers, with surround disabled - int enabled; // -1 if enabled, 0 if muted - }; -private: - struct state_t - { - uint8_t regs [register_count]; - - // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) - int echo_hist [echo_hist_size * 2] [2]; - int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] - - int every_other_sample; // toggles every sample - int kon; // KON value when last checked - int noise; - int echo_offset; // offset from ESA in echo buffer - int echo_length; // number of bytes that echo_offset will stop at - int phase; // next clock cycle to run (0-31) - unsigned counters [4]; - - int new_kon; - int t_koff; - - voice_t voices [voice_count]; - - unsigned* counter_select [32]; - - // non-emulation state - uint8_t* ram; // 64K shared RAM between DSP and SMP - int mute_mask; - int surround_threshold; - sample_t* out; - sample_t* out_end; - sample_t* out_begin; - sample_t extra [extra_size]; - }; - state_t m; - - void init_counter(); - void run_counter( int ); - void soft_reset_common(); - void write_outline( int addr, int data ); - void update_voice_vol( int addr ); -}; - -#include - -inline int SPC_DSP::sample_count() const { return int(m.out - m.out_begin); } - -inline int SPC_DSP::read( int addr ) const -{ - assert( (unsigned) addr < register_count ); - return m.regs [addr]; -} - -inline void SPC_DSP::update_voice_vol( int addr ) -{ - int l = (int8_t) m.regs [addr + v_voll]; - int r = (int8_t) m.regs [addr + v_volr]; - - if ( l * r < m.surround_threshold ) - { - // signs differ, so negate those that are negative - l ^= l >> 7; - r ^= r >> 7; - } - - voice_t& v = m.voices [addr >> 4]; - int enabled = v.enabled; - v.volume [0] = l & enabled; - v.volume [1] = r & enabled; -} - -inline void SPC_DSP::write( int addr, int data ) -{ - assert( (unsigned) addr < register_count ); - - m.regs [addr] = (uint8_t) data; - int low = addr & 0x0F; - if ( low < 0x2 ) // voice volumes - { - update_voice_vol( low ^ addr ); - } - else if ( low == 0xC ) - { - if ( addr == r_kon ) - m.new_kon = (uint8_t) data; - - if ( addr == r_endx ) // always cleared, regardless of data written - m.regs [r_endx] = 0; - } -} - -inline void SPC_DSP::disable_surround( bool disable ) -{ - m.surround_threshold = disable ? 0 : -0x4000; -} - -#define SPC_NO_COPY_STATE_FUNCS 1 - -#define SPC_LESS_ACCURATE 1 - -#endif diff --git a/snes_spc/readme.txt b/snes_spc/readme.txt deleted file mode 100644 index 979913f0a..000000000 --- a/snes_spc/readme.txt +++ /dev/null @@ -1,86 +0,0 @@ -snes_spc 0.9.0: SNES SPC-700 APU Emulator ------------------------------------------ -This library includes a full SPC emulator and an S-DSP emulator that can -be used on its own. Two S-DSP emulators are available: a highly accurate -one for use in a SNES emulator, and a 3x faster one for use in an SPC -music player or a resource-limited SNES emulator. - -* Can be used from C and C++ code -* Full SPC-700 APU emulator with cycle accuracy in most cases -* Loads, plays, and saves SPC music files -* Can save and load exact full emulator state -* DSP voice muting, surround sound disable, and song tempo adjustment -* Uses 7% CPU average on 400 MHz Mac to play an SPC using fast DSP - -The accurate DSP emulator is based on past research by others and -hundreds of hours of recent research by me. It passes over a hundred -strenuous timing and behavior validation tests that were also run on the -SNES. As far as I know, it's the first DSP emulator with cycle accuracy, -properly emulating every DSP register and memory access at the exact SPC -cycle it occurs at, whereas previous DSP emulators emulated these only -to the nearest sample (which occurs every 32 clocks). - -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) - - -Getting Started ---------------- -Build a program consisting of demo/play_spc.c, demo/demo_util.c, -demo/wave_writer.c, and all source files in snes_spc/. Put an SPC music -file in the same directory and name it "test.spc". Running the program -should generate the recording "out.wav". - -Read snes_spc.txt for more information. Post to the discussion forum for -assistance. - - -Files ------ -snes_spc.txt Documentation -changes.txt Change log -license.txt GNU LGPL license - -demo/ - play_spc.c Records SPC file to wave sound file - benchmark.c Finds how fast emulator runs on your computer - trim_spc.c Trims silence off beginning of an SPC file - save_state.c Saves/loads exact emulator state to/from file - comm.c Communicates with SPC how SNES would - demo_util.h General utility functions used by demos - demo_util.c - wave_writer.h WAVE sound file writer used for demo output - wave_writer.c - -fast_dsp/ Optional standalone fast DSP emulator - SPC_DSP.h To use with full SPC emulator, move into - SPC_DSP.cpp snes_spc/ and replace original files - -snes_spc/ Library sources - blargg_config.h Configuration (modify as necessary) - - spc.h C interface to SPC emulator and sound filter - spc.cpp - - SPC_Filter.h Optional filter to make sound more authentic - SPC_Filter.cpp - - SNES_SPC.h Full SPC emulator - SNES_SPC.cpp - SNES_SPC_misc.cpp - SNES_SPC_state.cpp - SPC_CPU.h - - dsp.h C interface to DSP emulator - dsp.cpp - - SPC_DSP.h Standalone accurate DSP emulator - SPC_DSP.cpp - blargg_common.h - blargg_endian.h - blargg_source.h - --- -Shay Green diff --git a/snes_spc/slow_dsp/SPC_DSP.cpp b/snes_spc/slow_dsp/SPC_DSP.cpp deleted file mode 100644 index dd180506f..000000000 --- a/snes_spc/slow_dsp/SPC_DSP.cpp +++ /dev/null @@ -1,1018 +0,0 @@ -// snes_spc 0.9.0. http://www.slack.net/~ant/ - -#include "SPC_DSP.h" - -#include "blargg_endian.h" -#include - -/* Copyright (C) 2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "blargg_source.h" - -#ifdef BLARGG_ENABLE_OPTIMIZER - #include BLARGG_ENABLE_OPTIMIZER -#endif - -#if INT_MAX < 0x7FFFFFFF - #error "Requires that int type have at least 32 bits" -#endif - -// TODO: add to blargg_endian.h -#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) -#define GET_LE16A( addr ) GET_LE16( addr ) -#define SET_LE16A( addr, data ) SET_LE16( addr, data ) - -static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = -{ - 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, - 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, - 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, - 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, - 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, - 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, - 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, - 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF -}; - -// if ( io < -32768 ) io = -32768; -// if ( io > 32767 ) io = 32767; -#define CLAMP16( io )\ -{\ - if ( (int16_t) io != io )\ - io = (io >> 31) ^ 0x7FFF;\ -} - -// Access global DSP register -#define REG(n) m.regs [r_##n] - -// Access voice DSP register -#define VREG(r,n) r [v_##n] - -#define WRITE_SAMPLES( l, r, out ) \ -{\ - out [0] = l;\ - out [1] = r;\ - out += 2;\ - if ( out >= m.out_end )\ - {\ - check( out == m.out_end );\ - check( m.out_end != &m.extra [extra_size] || \ - (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ - out = m.extra;\ - m.out_end = &m.extra [extra_size];\ - }\ -}\ - -void SPC_DSP::set_output( sample_t* out, int size ) -{ - require( (size & 1) == 0 ); // must be even - if ( !out ) - { - out = m.extra; - size = extra_size; - } - m.out_begin = out; - m.out = out; - m.out_end = out + size; -} - -// Volume registers and efb are signed! Easy to forget int8_t cast. -// Prefixes are to avoid accidental use of locals with same names. - -// Gaussian interpolation - -static short const gauss [512] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, - 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, - 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, - 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, - 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, - 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, - 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, - 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, - 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, - 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, - 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, - 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, - 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, - 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, - 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, - 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, - 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, - 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, - 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, - 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, - 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, - 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, -1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, -1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, -1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, -1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, -1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, -1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, -1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, -}; - -inline int SPC_DSP::interpolate( voice_t const* v ) -{ - // Make pointers into gaussian based on fractional position between samples - int offset = v->interp_pos >> 4 & 0xFF; - short const* fwd = gauss + 255 - offset; - short const* rev = gauss + offset; // mirror left half of gaussian - - int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; - int out; - out = (fwd [ 0] * in [0]) >> 11; - out += (fwd [256] * in [1]) >> 11; - out += (rev [256] * in [2]) >> 11; - out = (int16_t) out; - out += (rev [ 0] * in [3]) >> 11; - - CLAMP16( out ); - out &= ~1; - return out; -} - - -//// Counters - -int const simple_counter_range = 2048 * 5 * 3; // 30720 - -static unsigned const counter_rates [32] = -{ - simple_counter_range + 1, // never fires - 2048, 1536, - 1280, 1024, 768, - 640, 512, 384, - 320, 256, 192, - 160, 128, 96, - 80, 64, 48, - 40, 32, 24, - 20, 16, 12, - 10, 8, 6, - 5, 4, 3, - 2, - 1 -}; - -static unsigned const counter_offsets [32] = -{ - 1, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 536, 0, 1040, - 0, - 0 -}; - -inline void SPC_DSP::init_counter() -{ - m.counter = 0; -} - -inline void SPC_DSP::run_counters() -{ - if ( --m.counter < 0 ) - m.counter = simple_counter_range - 1; -} - -inline unsigned SPC_DSP::read_counter( int rate ) -{ - return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; -} - - -//// Envelope - -inline void SPC_DSP::run_envelope( voice_t* const v ) -{ - int env = v->env; - if ( v->env_mode == env_release ) // 60% - { - if ( (env -= 0x8) < 0 ) - env = 0; - v->env = env; - } - else - { - int rate; - int env_data = VREG(v->regs,adsr1); - if ( m.t_adsr0 & 0x80 ) // 99% ADSR - { - if ( v->env_mode >= env_decay ) // 99% - { - env--; - env -= env >> 8; - rate = env_data & 0x1F; - if ( v->env_mode == env_decay ) // 1% - rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; - } - else // env_attack - { - rate = (m.t_adsr0 & 0x0F) * 2 + 1; - env += rate < 31 ? 0x20 : 0x400; - } - } - else // GAIN - { - int mode; - env_data = VREG(v->regs,gain); - mode = env_data >> 5; - if ( mode < 4 ) // direct - { - env = env_data * 0x10; - rate = 31; - } - else - { - rate = env_data & 0x1F; - if ( mode == 4 ) // 4: linear decrease - { - env -= 0x20; - } - else if ( mode < 6 ) // 5: exponential decrease - { - env--; - env -= env >> 8; - } - else // 6,7: linear increase - { - env += 0x20; - if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) - env += 0x8 - 0x20; // 7: two-slope linear increase - } - } - } - - // Sustain level - if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) - v->env_mode = env_sustain; - - v->hidden_env = env; - - // unsigned cast because linear decrease going negative also triggers this - if ( (unsigned) env > 0x7FF ) - { - env = (env < 0 ? 0 : 0x7FF); - if ( v->env_mode == env_attack ) - v->env_mode = env_decay; - } - - if ( !read_counter( rate ) ) - v->env = env; // nothing else is controlled by the counter - } -} - - -//// BRR Decoding - -inline void SPC_DSP::decode_brr( voice_t* v ) -{ - // Arrange the four input nybbles in 0xABCD order for easy decoding - int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; - - int const header = m.t_brr_header; - - // Write to next four samples in circular buffer - int* pos = &v->buf [v->buf_pos]; - int* end; - if ( (v->buf_pos += 4) >= brr_buf_size ) - v->buf_pos = 0; - - // Decode four samples - for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) - { - // Extract nybble and sign-extend - int s = (int16_t) nybbles >> 12; - - // Shift sample based on header - int const shift = header >> 4; - s = (s << shift) >> 1; - if ( shift >= 0xD ) // handle invalid range - s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) - - // Apply IIR filter (8 is the most commonly used) - int const filter = header & 0x0C; - int const p1 = pos [brr_buf_size - 1]; - int const p2 = pos [brr_buf_size - 2] >> 1; - if ( filter >= 8 ) - { - s += p1; - s -= p2; - if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 - { - s += p2 >> 4; - s += (p1 * -3) >> 6; - } - else // s += p1 * 0.8984375 - p2 * 0.40625 - { - s += (p1 * -13) >> 7; - s += (p2 * 3) >> 4; - } - } - else if ( filter ) // s += p1 * 0.46875 - { - s += p1 >> 1; - s += (-p1) >> 5; - } - - // Adjust and write sample - CLAMP16( s ); - s = (int16_t) (s * 2); - pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around - } -} - - -//// Misc - -#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() - -MISC_CLOCK( 27 ) -{ - m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON -} -MISC_CLOCK( 28 ) -{ - m.t_non = REG(non); - m.t_eon = REG(eon); - m.t_dir = REG(dir); -} -MISC_CLOCK( 29 ) -{ - if ( (m.every_other_sample ^= 1) != 0 ) - m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read -} -MISC_CLOCK( 30 ) -{ - if ( m.every_other_sample ) - { - m.kon = m.new_kon; - m.t_koff = REG(koff) | m.mute_mask; - } - - run_counters(); - - // Noise - if ( !read_counter( REG(flg) & 0x1F ) ) - { - int feedback = (m.noise << 13) ^ (m.noise << 14); - m.noise = (feedback & 0x4000) ^ (m.noise >> 1); - } -} - - -//// Voices - -#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) - -inline VOICE_CLOCK( V1 ) -{ - m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; - m.t_srcn = VREG(v->regs,srcn); -} -inline VOICE_CLOCK( V2 ) -{ - // Read sample pointer (ignored if not needed) - uint8_t const* entry = &m.ram [m.t_dir_addr]; - if ( !v->kon_delay ) - entry += 2; - m.t_brr_next_addr = GET_LE16A( entry ); - - m.t_adsr0 = VREG(v->regs,adsr0); - - // Read pitch, spread over two clocks - m.t_pitch = VREG(v->regs,pitchl); -} -inline VOICE_CLOCK( V3a ) -{ - m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; -} -inline VOICE_CLOCK( V3b ) -{ - // Read BRR header and byte - m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; - m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking -} -VOICE_CLOCK( V3c ) -{ - // Pitch modulation using previous voice's output - if ( m.t_pmon & v->vbit ) - m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; - - if ( v->kon_delay ) - { - // Get ready to start BRR decoding on next sample - if ( v->kon_delay == 5 ) - { - v->brr_addr = m.t_brr_next_addr; - v->brr_offset = 1; - v->buf_pos = 0; - m.t_brr_header = 0; // header is ignored on this sample - m.kon_check = true; - } - - // Envelope is never run during KON - v->env = 0; - v->hidden_env = 0; - - // Disable BRR decoding until last three samples - v->interp_pos = 0; - if ( --v->kon_delay & 3 ) - v->interp_pos = 0x4000; - - // Pitch is never added during KON - m.t_pitch = 0; - } - - // Gaussian interpolation - { - int output = interpolate( v ); - - // Noise - if ( m.t_non & v->vbit ) - output = (int16_t) (m.noise * 2); - - // Apply envelope - m.t_output = (output * v->env) >> 11 & ~1; - v->t_envx_out = (uint8_t) (v->env >> 4); - } - - // Immediate silence due to end of sample or soft reset - if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) - { - v->env_mode = env_release; - v->env = 0; - } - - if ( m.every_other_sample ) - { - // KOFF - if ( m.t_koff & v->vbit ) - v->env_mode = env_release; - - // KON - if ( m.kon & v->vbit ) - { - v->kon_delay = 5; - v->env_mode = env_attack; - } - } - - // Run envelope for next sample - if ( !v->kon_delay ) - run_envelope( v ); -} -inline void SPC_DSP::voice_output( voice_t const* v, int ch ) -{ - // Apply left/right volume - int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; - - // Add to output total - m.t_main_out [ch] += amp; - CLAMP16( m.t_main_out [ch] ); - - // Optionally add to echo total - if ( m.t_eon & v->vbit ) - { - m.t_echo_out [ch] += amp; - CLAMP16( m.t_echo_out [ch] ); - } -} -VOICE_CLOCK( V4 ) -{ - // Decode BRR - m.t_looped = 0; - if ( v->interp_pos >= 0x4000 ) - { - decode_brr( v ); - - if ( (v->brr_offset += 2) >= brr_block_size ) - { - // Start decoding next BRR block - assert( v->brr_offset == brr_block_size ); - v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; - if ( m.t_brr_header & 1 ) - { - v->brr_addr = m.t_brr_next_addr; - m.t_looped = v->vbit; - } - v->brr_offset = 1; - } - } - - // Apply pitch - v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; - - // Keep from getting too far ahead (when using pitch modulation) - if ( v->interp_pos > 0x7FFF ) - v->interp_pos = 0x7FFF; - - // Output left - voice_output( v, 0 ); -} -inline VOICE_CLOCK( V5 ) -{ - // Output right - voice_output( v, 1 ); - - // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier - int endx_buf = REG(endx) | m.t_looped; - - // Clear bit in ENDX if KON just began - if ( v->kon_delay == 5 ) - endx_buf &= ~v->vbit; - m.endx_buf = (uint8_t) endx_buf; -} -inline VOICE_CLOCK( V6 ) -{ - (void) v; // avoid compiler warning about unused v - m.outx_buf = (uint8_t) (m.t_output >> 8); -} -inline VOICE_CLOCK( V7 ) -{ - // Update ENDX - REG(endx) = m.endx_buf; - - m.envx_buf = v->t_envx_out; -} -inline VOICE_CLOCK( V8 ) -{ - // Update OUTX - VREG(v->regs,outx) = m.outx_buf; -} -inline VOICE_CLOCK( V9 ) -{ - // Update ENVX - VREG(v->regs,envx) = m.envx_buf; -} - -// Most voices do all these in one clock, so make a handy composite -inline VOICE_CLOCK( V3 ) -{ - voice_V3a( v ); - voice_V3b( v ); - voice_V3c( v ); -} - -// Common combinations of voice steps on different voices. This greatly reduces -// code size and allows everything to be inlined in these functions. -VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } -VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } -VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } - - -//// Echo - -// Current echo buffer pointer for left/right channel -#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) - -// Sample in echo history buffer, where 0 is the oldest -#define ECHO_FIR( i ) (m.echo_hist_pos [i]) - -// Calculate FIR point for left/right channel -#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) - -#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() - -inline void SPC_DSP::echo_read( int ch ) -{ - int s = GET_LE16SA( ECHO_PTR( ch ) ); - // second copy simplifies wrap-around handling - ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; -} - -ECHO_CLOCK( 22 ) -{ - // History - if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) - m.echo_hist_pos = m.echo_hist; - - m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; - echo_read( 0 ); - - // FIR (using l and r temporaries below helps compiler optimize) - int l = CALC_FIR( 0, 0 ); - int r = CALC_FIR( 0, 1 ); - - m.t_echo_in [0] = l; - m.t_echo_in [1] = r; -} -ECHO_CLOCK( 23 ) -{ - int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); - int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); - - m.t_echo_in [0] += l; - m.t_echo_in [1] += r; - - echo_read( 1 ); -} -ECHO_CLOCK( 24 ) -{ - int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); - int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); - - m.t_echo_in [0] += l; - m.t_echo_in [1] += r; -} -ECHO_CLOCK( 25 ) -{ - int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); - int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); - - l = (int16_t) l; - r = (int16_t) r; - - l += (int16_t) CALC_FIR( 7, 0 ); - r += (int16_t) CALC_FIR( 7, 1 ); - - CLAMP16( l ); - CLAMP16( r ); - - m.t_echo_in [0] = l & ~1; - m.t_echo_in [1] = r & ~1; -} -inline int SPC_DSP::echo_output( int ch ) -{ - int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + - (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); - CLAMP16( out ); - return out; -} -ECHO_CLOCK( 26 ) -{ - // Left output volumes - // (save sample for next clock so we can output both together) - m.t_main_out [0] = echo_output( 0 ); - - // Echo feedback - int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); - int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); - - CLAMP16( l ); - CLAMP16( r ); - - m.t_echo_out [0] = l & ~1; - m.t_echo_out [1] = r & ~1; -} -ECHO_CLOCK( 27 ) -{ - // Output - int l = m.t_main_out [0]; - int r = echo_output( 1 ); - m.t_main_out [0] = 0; - m.t_main_out [1] = 0; - - // TODO: global muting isn't this simple (turns DAC on and off - // or something, causing small ~37-sample pulse when first muted) - if ( REG(flg) & 0x40 ) - { - l = 0; - r = 0; - } - - // Output sample to DAC - #ifdef SPC_DSP_OUT_HOOK - SPC_DSP_OUT_HOOK( l, r ); - #else - sample_t* out = m.out; - WRITE_SAMPLES( l, r, out ); - m.out = out; - #endif -} -ECHO_CLOCK( 28 ) -{ - m.t_echo_enabled = REG(flg); -} -inline void SPC_DSP::echo_write( int ch ) -{ - if ( !(m.t_echo_enabled & 0x20) ) - SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); - m.t_echo_out [ch] = 0; -} -ECHO_CLOCK( 29 ) -{ - m.t_esa = REG(esa); - - if ( !m.echo_offset ) - m.echo_length = (REG(edl) & 0x0F) * 0x800; - - m.echo_offset += 4; - if ( m.echo_offset >= m.echo_length ) - m.echo_offset = 0; - - // Write left echo - echo_write( 0 ); - - m.t_echo_enabled = REG(flg); -} -ECHO_CLOCK( 30 ) -{ - // Write right echo - echo_write( 1 ); -} - - -//// Timing - -// Execute clock for a particular voice -#define V( clock, voice ) voice_##clock( &m.voices [voice] ); - -/* The most common sequence of clocks uses composite operations -for efficiency. For example, the following are equivalent to the -individual steps on the right: - -V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) -V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) -V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ - -// Voice 0 1 2 3 4 5 6 7 -#define GEN_DSP_TIMING \ -PHASE( 0) V(V5,0)V(V2,1)\ -PHASE( 1) V(V6,0)V(V3,1)\ -PHASE( 2) V(V7_V4_V1,0)\ -PHASE( 3) V(V8_V5_V2,0)\ -PHASE( 4) V(V9_V6_V3,0)\ -PHASE( 5) V(V7_V4_V1,1)\ -PHASE( 6) V(V8_V5_V2,1)\ -PHASE( 7) V(V9_V6_V3,1)\ -PHASE( 8) V(V7_V4_V1,2)\ -PHASE( 9) V(V8_V5_V2,2)\ -PHASE(10) V(V9_V6_V3,2)\ -PHASE(11) V(V7_V4_V1,3)\ -PHASE(12) V(V8_V5_V2,3)\ -PHASE(13) V(V9_V6_V3,3)\ -PHASE(14) V(V7_V4_V1,4)\ -PHASE(15) V(V8_V5_V2,4)\ -PHASE(16) V(V9_V6_V3,4)\ -PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ -PHASE(18) V(V8_V5_V2,5)\ -PHASE(19) V(V9_V6_V3,5)\ -PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ -PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ -PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ -PHASE(23) V(V7,7) echo_23();\ -PHASE(24) V(V8,7) echo_24();\ -PHASE(25) V(V3b,0) V(V9,7) echo_25();\ -PHASE(26) echo_26();\ -PHASE(27) misc_27(); echo_27();\ -PHASE(28) misc_28(); echo_28();\ -PHASE(29) misc_29(); echo_29();\ -PHASE(30) misc_30();V(V3c,0) echo_30();\ -PHASE(31) V(V4,0) V(V1,2)\ - -#if !SPC_DSP_CUSTOM_RUN - -void SPC_DSP::run( int clocks_remain ) -{ - require( clocks_remain > 0 ); - - int const phase = m.phase; - m.phase = (phase + clocks_remain) & 31; - switch ( phase ) - { - loop: - - #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: - GEN_DSP_TIMING - #undef PHASE - - if ( --clocks_remain ) - goto loop; - } -} - -#endif - - -//// Setup - -void SPC_DSP::init( void* ram_64k ) -{ - m.ram = (uint8_t*) ram_64k; - mute_voices( 0 ); - disable_surround( false ); - set_output( 0, 0 ); - reset(); - - #ifndef NDEBUG - // be sure this sign-extends - assert( (int16_t) 0x8000 == -0x8000 ); - - // be sure right shift preserves sign - assert( (-1 >> 1) == -1 ); - - // check clamp macro - int i; - i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); - i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); - - blargg_verify_byte_order(); - #endif -} - -void SPC_DSP::soft_reset_common() -{ - require( m.ram ); // init() must have been called already - - m.noise = 0x4000; - m.echo_hist_pos = m.echo_hist; - m.every_other_sample = 1; - m.echo_offset = 0; - m.phase = 0; - - init_counter(); -} - -void SPC_DSP::soft_reset() -{ - REG(flg) = 0xE0; - soft_reset_common(); -} - -void SPC_DSP::load( uint8_t const regs [register_count] ) -{ - memcpy( m.regs, regs, sizeof m.regs ); - memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); - - // Internal state - for ( int i = voice_count; --i >= 0; ) - { - voice_t* v = &m.voices [i]; - v->brr_offset = 1; - v->vbit = 1 << i; - v->regs = &m.regs [i * 0x10]; - } - m.new_kon = REG(kon); - m.t_dir = REG(dir); - m.t_esa = REG(esa); - - soft_reset_common(); -} - -void SPC_DSP::reset() { load( initial_regs ); } - - -//// State save/load - -#if !SPC_NO_COPY_STATE_FUNCS - -void SPC_State_Copier::copy( void* state, size_t size ) -{ - func( buf, state, size ); -} - -int SPC_State_Copier::copy_int( int state, int size ) -{ - BOOST::uint8_t s [2]; - SET_LE16( s, state ); - func( buf, &s, size ); - return GET_LE16( s ); -} - -void SPC_State_Copier::skip( int count ) -{ - if ( count > 0 ) - { - char temp [64]; - memset( temp, 0, sizeof temp ); - do - { - int n = sizeof temp; - if ( n > count ) - n = count; - count -= n; - func( buf, temp, n ); - } - while ( count ); - } -} - -void SPC_State_Copier::extra() -{ - int n = 0; - SPC_State_Copier& copier = *this; - SPC_COPY( uint8_t, n ); - skip( n ); -} - -void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) -{ - SPC_State_Copier copier( io, copy ); - - // DSP registers - copier.copy( m.regs, register_count ); - - // Internal state - - // Voices - int i; - for ( i = 0; i < voice_count; i++ ) - { - voice_t* v = &m.voices [i]; - - // BRR buffer - int i; - for ( i = 0; i < brr_buf_size; i++ ) - { - int s = v->buf [i]; - SPC_COPY( int16_t, s ); - v->buf [i] = v->buf [i + brr_buf_size] = s; - } - - SPC_COPY( uint16_t, v->interp_pos ); - SPC_COPY( uint16_t, v->brr_addr ); - SPC_COPY( uint16_t, v->env ); - SPC_COPY( int16_t, v->hidden_env ); - SPC_COPY( uint8_t, v->buf_pos ); - SPC_COPY( uint8_t, v->brr_offset ); - SPC_COPY( uint8_t, v->kon_delay ); - { - int m = v->env_mode; - SPC_COPY( uint8_t, m ); - v->env_mode = (enum env_mode_t) m; - } - SPC_COPY( uint8_t, v->t_envx_out ); - - copier.extra(); - } - - // Echo history - for ( i = 0; i < echo_hist_size; i++ ) - { - int j; - for ( j = 0; j < 2; j++ ) - { - int s = m.echo_hist_pos [i] [j]; - SPC_COPY( int16_t, s ); - m.echo_hist [i] [j] = s; // write back at offset 0 - } - } - m.echo_hist_pos = m.echo_hist; - memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); - - // Misc - SPC_COPY( uint8_t, m.every_other_sample ); - SPC_COPY( uint8_t, m.kon ); - - SPC_COPY( uint16_t, m.noise ); - SPC_COPY( uint16_t, m.counter ); - SPC_COPY( uint16_t, m.echo_offset ); - SPC_COPY( uint16_t, m.echo_length ); - SPC_COPY( uint8_t, m.phase ); - - SPC_COPY( uint8_t, m.new_kon ); - SPC_COPY( uint8_t, m.endx_buf ); - SPC_COPY( uint8_t, m.envx_buf ); - SPC_COPY( uint8_t, m.outx_buf ); - - SPC_COPY( uint8_t, m.t_pmon ); - SPC_COPY( uint8_t, m.t_non ); - SPC_COPY( uint8_t, m.t_eon ); - SPC_COPY( uint8_t, m.t_dir ); - SPC_COPY( uint8_t, m.t_koff ); - - SPC_COPY( uint16_t, m.t_brr_next_addr ); - SPC_COPY( uint8_t, m.t_adsr0 ); - SPC_COPY( uint8_t, m.t_brr_header ); - SPC_COPY( uint8_t, m.t_brr_byte ); - SPC_COPY( uint8_t, m.t_srcn ); - SPC_COPY( uint8_t, m.t_esa ); - SPC_COPY( uint8_t, m.t_echo_enabled ); - - SPC_COPY( int16_t, m.t_main_out [0] ); - SPC_COPY( int16_t, m.t_main_out [1] ); - SPC_COPY( int16_t, m.t_echo_out [0] ); - SPC_COPY( int16_t, m.t_echo_out [1] ); - SPC_COPY( int16_t, m.t_echo_in [0] ); - SPC_COPY( int16_t, m.t_echo_in [1] ); - - SPC_COPY( uint16_t, m.t_dir_addr ); - SPC_COPY( uint16_t, m.t_pitch ); - SPC_COPY( int16_t, m.t_output ); - SPC_COPY( uint16_t, m.t_echo_ptr ); - SPC_COPY( uint8_t, m.t_looped ); - - copier.extra(); -} -#endif diff --git a/snes_spc/slow_dsp/SPC_DSP.h b/snes_spc/slow_dsp/SPC_DSP.h deleted file mode 100644 index 0428d5fce..000000000 --- a/snes_spc/slow_dsp/SPC_DSP.h +++ /dev/null @@ -1,304 +0,0 @@ -// Highly accurate SNES SPC-700 DSP emulator - -// snes_spc 0.9.0 -#ifndef SPC_DSP_H -#define SPC_DSP_H - -#include "blargg_common.h" - -extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } - -struct SPC_DSP { -public: - typedef BOOST::uint8_t uint8_t; - -// Setup - - // Initializes DSP and has it use the 64K RAM provided - void init( void* ram_64k ); - - // Sets destination for output samples. If out is NULL or out_size is 0, - // doesn't generate any. - typedef short sample_t; - void set_output( sample_t* out, int out_size ); - - // Number of samples written to output since it was last set, always - // a multiple of 2. Undefined if more samples were generated than - // output buffer could hold. - int sample_count() const; - -// Emulation - - // Resets DSP to power-on state - void reset(); - - // Emulates pressing reset switch on SNES - void soft_reset(); - - // Reads/writes DSP registers. For accuracy, you must first call run() - // to catch the DSP up to present. - int read ( int addr ) const; - void write( int addr, int data ); - - // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks - // a pair of samples is be generated. - void run( int clock_count ); - -// Sound control - - // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). - // Reduces emulation accuracy. - enum { voice_count = 8 }; - void mute_voices( int mask ); - -// State - - // Resets DSP and uses supplied values to initialize registers - enum { register_count = 128 }; - void load( uint8_t const regs [register_count] ); - - // Saves/loads exact emulator state - enum { state_size = 640 }; // maximum space needed when saving - typedef dsp_copy_func_t copy_func_t; - void copy_state( unsigned char** io, copy_func_t ); - - // Returns non-zero if new key-on events occurred since last call - bool check_kon(); - -// DSP register addresses - - // Global registers - enum { - r_mvoll = 0x0C, r_mvolr = 0x1C, - r_evoll = 0x2C, r_evolr = 0x3C, - r_kon = 0x4C, r_koff = 0x5C, - r_flg = 0x6C, r_endx = 0x7C, - r_efb = 0x0D, r_pmon = 0x2D, - r_non = 0x3D, r_eon = 0x4D, - r_dir = 0x5D, r_esa = 0x6D, - r_edl = 0x7D, - r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F - }; - - // Voice registers - enum { - v_voll = 0x00, v_volr = 0x01, - v_pitchl = 0x02, v_pitchh = 0x03, - v_srcn = 0x04, v_adsr0 = 0x05, - v_adsr1 = 0x06, v_gain = 0x07, - v_envx = 0x08, v_outx = 0x09 - }; - -public: - enum { extra_size = 16 }; - sample_t* extra() { return m.extra; } - sample_t const* out_pos() const { return m.out; } - void disable_surround( bool ) { } // not supported -public: - BLARGG_DISABLE_NOTHROW - - typedef BOOST::int8_t int8_t; - typedef BOOST::int16_t int16_t; - - enum { echo_hist_size = 8 }; - - enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; - enum { brr_buf_size = 12 }; - struct voice_t - { - int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) - int buf_pos; // place in buffer where next samples will be decoded - int interp_pos; // relative fractional position in sample (0x1000 = 1.0) - int brr_addr; // address of current BRR block - int brr_offset; // current decoding offset in BRR block - uint8_t* regs; // pointer to voice's DSP registers - int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. - int kon_delay; // KON delay/current setup phase - env_mode_t env_mode; - int env; // current envelope level - int hidden_env; // used by GAIN mode 7, very obscure quirk - uint8_t t_envx_out; - }; -private: - enum { brr_block_size = 9 }; - - struct state_t - { - uint8_t regs [register_count]; - - // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) - int echo_hist [echo_hist_size * 2] [2]; - int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] - - int every_other_sample; // toggles every sample - int kon; // KON value when last checked - int noise; - int counter; - int echo_offset; // offset from ESA in echo buffer - int echo_length; // number of bytes that echo_offset will stop at - int phase; // next clock cycle to run (0-31) - bool kon_check; // set when a new KON occurs - - // Hidden registers also written to when main register is written to - int new_kon; - uint8_t endx_buf; - uint8_t envx_buf; - uint8_t outx_buf; - - // Temporary state between clocks - - // read once per sample - int t_pmon; - int t_non; - int t_eon; - int t_dir; - int t_koff; - - // read a few clocks ahead then used - int t_brr_next_addr; - int t_adsr0; - int t_brr_header; - int t_brr_byte; - int t_srcn; - int t_esa; - int t_echo_enabled; - - // internal state that is recalculated every sample - int t_dir_addr; - int t_pitch; - int t_output; - int t_looped; - int t_echo_ptr; - - // left/right sums - int t_main_out [2]; - int t_echo_out [2]; - int t_echo_in [2]; - - voice_t voices [voice_count]; - - // non-emulation state - uint8_t* ram; // 64K shared RAM between DSP and SMP - int mute_mask; - sample_t* out; - sample_t* out_end; - sample_t* out_begin; - sample_t extra [extra_size]; - }; - state_t m; - - void init_counter(); - void run_counters(); - unsigned read_counter( int rate ); - - int interpolate( voice_t const* v ); - void run_envelope( voice_t* const v ); - void decode_brr( voice_t* v ); - - void misc_27(); - void misc_28(); - void misc_29(); - void misc_30(); - - void voice_output( voice_t const* v, int ch ); - void voice_V1( voice_t* const ); - void voice_V2( voice_t* const ); - void voice_V3( voice_t* const ); - void voice_V3a( voice_t* const ); - void voice_V3b( voice_t* const ); - void voice_V3c( voice_t* const ); - void voice_V4( voice_t* const ); - void voice_V5( voice_t* const ); - void voice_V6( voice_t* const ); - void voice_V7( voice_t* const ); - void voice_V8( voice_t* const ); - void voice_V9( voice_t* const ); - void voice_V7_V4_V1( voice_t* const ); - void voice_V8_V5_V2( voice_t* const ); - void voice_V9_V6_V3( voice_t* const ); - - void echo_read( int ch ); - int echo_output( int ch ); - void echo_write( int ch ); - void echo_22(); - void echo_23(); - void echo_24(); - void echo_25(); - void echo_26(); - void echo_27(); - void echo_28(); - void echo_29(); - void echo_30(); - - void soft_reset_common(); -}; - -#include - -inline int SPC_DSP::sample_count() const { return int(m.out - m.out_begin); } - -inline int SPC_DSP::read( int addr ) const -{ - assert( (unsigned) addr < register_count ); - return m.regs [addr]; -} - -inline void SPC_DSP::write( int addr, int data ) -{ - assert( (unsigned) addr < register_count ); - - m.regs [addr] = (uint8_t) data; - switch ( addr & 0x0F ) - { - case v_envx: - m.envx_buf = (uint8_t) data; - break; - - case v_outx: - m.outx_buf = (uint8_t) data; - break; - - case 0x0C: - if ( addr == r_kon ) - m.new_kon = (uint8_t) data; - - if ( addr == r_endx ) // always cleared, regardless of data written - { - m.endx_buf = 0; - m.regs [r_endx] = 0; - } - break; - } -} - -inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } - -inline bool SPC_DSP::check_kon() -{ - bool old = m.kon_check; - m.kon_check = 0; - return old; -} - -#if !SPC_NO_COPY_STATE_FUNCS - -class SPC_State_Copier { - SPC_DSP::copy_func_t func; - unsigned char** buf; -public: - SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } - void copy( void* state, size_t size ); - int copy_int( int state, int size ); - void skip( int count ); - void extra(); -}; - -#define SPC_COPY( type, state )\ -{\ - state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ - assert( (BOOST::type) state == state );\ -} - -#endif - -#endif diff --git a/snes_spc/snes_spc.txt b/snes_spc/snes_spc.txt deleted file mode 100644 index d37b34347..000000000 --- a/snes_spc/snes_spc.txt +++ /dev/null @@ -1,318 +0,0 @@ -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 diff --git a/snes_spc/snes_spc/SNES_SPC_state.cpp b/snes_spc/snes_spc/SNES_SPC_state.cpp deleted file mode 100644 index a8052b658..000000000 --- a/snes_spc/snes_spc/SNES_SPC_state.cpp +++ /dev/null @@ -1,129 +0,0 @@ -// SPC emulation state save/load: copy_state(), save_spc() -// Separate file to avoid linking in unless needed - -// snes_spc 0.9.0. http://www.slack.net/~ant/ - -#include "SNES_SPC.h" - -#if !SPC_NO_COPY_STATE_FUNCS - -#include - -/* Copyright (C) 2004-2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "blargg_source.h" - -#define RAM (m.ram.ram) -#define REGS (m.smp_regs [0]) -#define REGS_IN (m.smp_regs [1]) - -void SNES_SPC::save_regs( uint8_t out [reg_count] ) -{ - // Use current timer counter values - for ( int i = 0; i < timer_count; i++ ) - out [r_t0out + i] = m.timers [i].counter; - - // Last written values - memcpy( out, REGS, r_t0out ); -} - -void SNES_SPC::init_header( void* spc_out ) -{ - spc_file_t* const spc = (spc_file_t*) spc_out; - - spc->has_id666 = 26; // has none - spc->version = 30; - memcpy( spc, signature, sizeof spc->signature ); - memset( spc->text, 0, sizeof spc->text ); -} - -void SNES_SPC::save_spc( void* spc_out ) -{ - spc_file_t* const spc = (spc_file_t*) spc_out; - - // CPU - spc->pcl = (uint8_t) (m.cpu_regs.pc >> 0); - spc->pch = (uint8_t) (m.cpu_regs.pc >> 8); - spc->a = m.cpu_regs.a; - spc->x = m.cpu_regs.x; - spc->y = m.cpu_regs.y; - spc->psw = m.cpu_regs.psw; - spc->sp = m.cpu_regs.sp; - - // RAM, ROM - memcpy( spc->ram, RAM, sizeof spc->ram ); - if ( m.rom_enabled ) - memcpy( spc->ram + rom_addr, m.hi_ram, sizeof m.hi_ram ); - memset( spc->unused, 0, sizeof spc->unused ); - memcpy( spc->ipl_rom, m.rom, sizeof spc->ipl_rom ); - - // SMP registers - save_regs( &spc->ram [0xF0] ); - int i; - for ( i = 0; i < port_count; i++ ) - spc->ram [0xF0 + r_cpuio0 + i] = REGS_IN [r_cpuio0 + i]; - - // DSP registers - for ( i = 0; i < SPC_DSP::register_count; i++ ) - spc->dsp [i] = dsp.read( i ); -} - -void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy ) -{ - SPC_State_Copier copier( io, copy ); - - // Make state data more readable by putting 64K RAM, 16 SMP registers, - // then DSP (with its 128 registers) first - - // RAM - enable_rom( 0 ); // will get re-enabled if necessary in regs_loaded() below - copier.copy( RAM, 0x10000 ); - - { - // SMP registers - uint8_t out_ports [port_count]; - uint8_t regs [reg_count]; - memcpy( out_ports, ®S [r_cpuio0], sizeof out_ports ); - save_regs( regs ); - copier.copy( regs, sizeof regs ); - copier.copy( out_ports, sizeof out_ports ); - load_regs( regs ); - regs_loaded(); - memcpy( ®S [r_cpuio0], out_ports, sizeof out_ports ); - } - - // CPU registers - SPC_COPY( uint16_t, m.cpu_regs.pc ); - SPC_COPY( uint8_t, m.cpu_regs.a ); - SPC_COPY( uint8_t, m.cpu_regs.x ); - SPC_COPY( uint8_t, m.cpu_regs.y ); - SPC_COPY( uint8_t, m.cpu_regs.psw ); - SPC_COPY( uint8_t, m.cpu_regs.sp ); - copier.extra(); - - SPC_COPY( int16_t, m.spc_time ); - SPC_COPY( int16_t, m.dsp_time ); - - // DSP - dsp.copy_state( io, copy ); - - // Timers - for ( int i = 0; i < timer_count; i++ ) - { - Timer* t = &m.timers [i]; - SPC_COPY( int16_t, t->next_time ); - SPC_COPY( uint8_t, t->divider ); - copier.extra(); - } - copier.extra(); -} -#endif diff --git a/snes_spc/snes_spc/SPC_DSP.cpp b/snes_spc/snes_spc/SPC_DSP.cpp deleted file mode 100644 index 1bb6907dd..000000000 --- a/snes_spc/snes_spc/SPC_DSP.cpp +++ /dev/null @@ -1,703 +0,0 @@ -// snes_spc 0.9.0. http://www.slack.net/~ant/ - -#include "SPC_DSP.h" - -#include "blargg_endian.h" -#include - -/* Copyright (C) 2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "blargg_source.h" - -#ifdef BLARGG_ENABLE_OPTIMIZER - #include BLARGG_ENABLE_OPTIMIZER -#endif - -#if INT_MAX < 0x7FFFFFFF - #error "Requires that int type have at least 32 bits" -#endif - - -// TODO: add to blargg_endian.h -#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) -#define GET_LE16A( addr ) GET_LE16( addr ) -#define SET_LE16A( addr, data ) SET_LE16( addr, data ) - -static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = -{ - 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, - 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, - 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, - 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, - 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, - 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, - 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, - 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF -}; - -// if ( io < -32768 ) io = -32768; -// if ( io > 32767 ) io = 32767; -#define CLAMP16( io )\ -{\ - if ( (int16_t) io != io )\ - io = (io >> 31) ^ 0x7FFF;\ -} - -// Access global DSP register -#define REG(n) m.regs [r_##n] - -// Access voice DSP register -#define VREG(r,n) r [v_##n] - -#define WRITE_SAMPLES( l, r, out ) \ -{\ - out [0] = l;\ - out [1] = r;\ - out += 2;\ - if ( out >= m.out_end )\ - {\ - check( out == m.out_end );\ - check( m.out_end != &m.extra [extra_size] || \ - (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ - out = m.extra;\ - m.out_end = &m.extra [extra_size];\ - }\ -}\ - -void SPC_DSP::set_output( sample_t* out, int size ) -{ - require( (size & 1) == 0 ); // must be even - if ( !out ) - { - out = m.extra; - size = extra_size; - } - m.out_begin = out; - m.out = out; - m.out_end = out + size; -} - -// Volume registers and efb are signed! Easy to forget int8_t cast. -// Prefixes are to avoid accidental use of locals with same names. - -// Interleved gauss table (to improve cache coherency) -// interleved_gauss [i] = gauss [(i & 1) * 256 + 255 - (i >> 1 & 0xFF)] -static short const interleved_gauss [512] = -{ - 370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303, - 339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299, - 311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292, - 283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282, - 257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269, - 233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253, - 210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234, - 188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213, - 168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190, - 150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164, - 132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136, - 117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106, - 102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074, - 89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040, - 77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005, - 66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969, - 56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932, - 48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894, - 40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855, - 33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816, - 27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777, - 22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737, - 17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698, - 14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659, - 10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620, - 8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582, - 5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545, - 4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508, - 2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473, - 1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439, - 0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405, - 0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374, -}; - - -//// Counters - -#define RATE( rate, div )\ - (rate >= div ? rate / div * 8 - 1 : rate - 1) - -static unsigned const counter_mask [32] = -{ - RATE( 2,2), RATE(2048,4), RATE(1536,3), - RATE(1280,5), RATE(1024,4), RATE( 768,3), - RATE( 640,5), RATE( 512,4), RATE( 384,3), - RATE( 320,5), RATE( 256,4), RATE( 192,3), - RATE( 160,5), RATE( 128,4), RATE( 96,3), - RATE( 80,5), RATE( 64,4), RATE( 48,3), - RATE( 40,5), RATE( 32,4), RATE( 24,3), - RATE( 20,5), RATE( 16,4), RATE( 12,3), - RATE( 10,5), RATE( 8,4), RATE( 6,3), - RATE( 5,5), RATE( 4,4), RATE( 3,3), - RATE( 2,4), - RATE( 1,4) -}; -#undef RATE - -inline void SPC_DSP::init_counter() -{ - // counters start out with this synchronization - m.counters [0] = 1; - m.counters [1] = 0; - m.counters [2] = unsigned(-0x20); - m.counters [3] = 0x0B; - - int n = 2; - for ( int i = 1; i < 32; i++ ) - { - m.counter_select [i] = &m.counters [n]; - if ( !--n ) - n = 3; - } - m.counter_select [ 0] = &m.counters [0]; - m.counter_select [30] = &m.counters [2]; -} - -inline void SPC_DSP::run_counter( int i ) -{ - int n = m.counters [i]; - if ( !(n-- & 7) ) - n -= 6 - i; - m.counters [i] = n; -} - -#define READ_COUNTER( rate )\ - (*m.counter_select [rate] & counter_mask [rate]) - - -//// Emulation - -void SPC_DSP::run( int clock_count ) -{ - int new_phase = m.phase + clock_count; - int count = new_phase >> 5; - m.phase = new_phase & 31; - if ( !count ) - return; - - uint8_t* const ram = m.ram; - uint8_t const* const dir = &ram [REG(dir) * 0x100]; - int const slow_gaussian = (REG(pmon) >> 1) | REG(non); - int const noise_rate = REG(flg) & 0x1F; - - // Global volume - int mvoll = (int8_t) REG(mvoll); - int mvolr = (int8_t) REG(mvolr); - if ( mvoll * mvolr < m.surround_threshold ) - mvoll = -mvoll; // eliminate surround - - do - { - // KON/KOFF reading - if ( (m.every_other_sample ^= 1) != 0 ) - { - m.new_kon &= ~m.kon; - m.kon = m.new_kon; - m.t_koff = REG(koff); - } - - run_counter( 1 ); - run_counter( 2 ); - run_counter( 3 ); - - // Noise - if ( !READ_COUNTER( noise_rate ) ) - { - int feedback = (m.noise << 13) ^ (m.noise << 14); - m.noise = (feedback & 0x4000) ^ (m.noise >> 1); - } - - // Voices - int pmon_input = 0; - int main_out_l = 0; - int main_out_r = 0; - int echo_out_l = 0; - int echo_out_r = 0; - voice_t* v = m.voices; - uint8_t* v_regs = m.regs; - int vbit = 1; - do - { - #define SAMPLE_PTR(i) GET_LE16A( &dir [VREG(v_regs,srcn) * 4 + i * 2] ) - - int brr_header = ram [v->brr_addr]; - int kon_delay = v->kon_delay; - - // Pitch - int pitch = GET_LE16A( &VREG(v_regs,pitchl) ) & 0x3FFF; - if ( REG(pmon) & vbit ) - pitch += ((pmon_input >> 5) * pitch) >> 10; - - // KON phases - if ( --kon_delay >= 0 ) - { - v->kon_delay = kon_delay; - - // Get ready to start BRR decoding on next sample - if ( kon_delay == 4 ) - { - v->brr_addr = SAMPLE_PTR( 0 ); - v->brr_offset = 1; - v->buf_pos = v->buf; - brr_header = 0; // header is ignored on this sample - } - - // Envelope is never run during KON - v->env = 0; - v->hidden_env = 0; - - // Disable BRR decoding until last three samples - v->interp_pos = (kon_delay & 3 ? 0x4000 : 0); - - // Pitch is never added during KON - pitch = 0; - } - - int env = v->env; - - // Gaussian interpolation - { - int output = 0; - VREG(v_regs,envx) = (uint8_t) (env >> 4); - if ( env ) - { - // Make pointers into gaussian based on fractional position between samples - int offset = (unsigned) v->interp_pos >> 3 & 0x1FE; - short const* fwd = interleved_gauss + offset; - short const* rev = interleved_gauss + 510 - offset; // mirror left half of gaussian - - int const* in = &v->buf_pos [(unsigned) v->interp_pos >> 12]; - - if ( !(slow_gaussian & vbit) ) // 99% - { - // Faster approximation when exact sample value isn't necessary for pitch mod - output = (fwd [0] * in [0] + - fwd [1] * in [1] + - rev [1] * in [2] + - rev [0] * in [3]) >> 11; - output = (output * env) >> 11; - } - else - { - output = (int16_t) (m.noise * 2); - if ( !(REG(non) & vbit) ) - { - output = (fwd [0] * in [0]) >> 11; - output += (fwd [1] * in [1]) >> 11; - output += (rev [1] * in [2]) >> 11; - output = (int16_t) output; - output += (rev [0] * in [3]) >> 11; - - CLAMP16( output ); - output &= ~1; - } - output = (output * env) >> 11 & ~1; - } - - // Output - int l = output * v->volume [0]; - int r = output * v->volume [1]; - - main_out_l += l; - main_out_r += r; - - if ( REG(eon) & vbit ) - { - echo_out_l += l; - echo_out_r += r; - } - } - - pmon_input = output; - VREG(v_regs,outx) = (uint8_t) (output >> 8); - } - - // Soft reset or end of sample - if ( REG(flg) & 0x80 || (brr_header & 3) == 1 ) - { - v->env_mode = env_release; - env = 0; - } - - if ( m.every_other_sample ) - { - // KOFF - if ( m.t_koff & vbit ) - v->env_mode = env_release; - - // KON - if ( m.kon & vbit ) - { - v->kon_delay = 5; - v->env_mode = env_attack; - REG(endx) &= ~vbit; - } - } - - // Envelope - if ( !v->kon_delay ) - { - if ( v->env_mode == env_release ) // 97% - { - env -= 0x8; - v->env = env; - if ( env <= 0 ) - { - v->env = 0; - goto skip_brr; // no BRR decoding for you! - } - } - else // 3% - { - int rate; - int const adsr0 = VREG(v_regs,adsr0); - int env_data = VREG(v_regs,adsr1); - if ( adsr0 >= 0x80 ) // 97% ADSR - { - if ( v->env_mode > env_decay ) // 89% - { - env--; - env -= env >> 8; - rate = env_data & 0x1F; - - // optimized handling - v->hidden_env = env; - if ( READ_COUNTER( rate ) ) - goto exit_env; - v->env = env; - goto exit_env; - } - else if ( v->env_mode == env_decay ) - { - env--; - env -= env >> 8; - rate = (adsr0 >> 3 & 0x0E) + 0x10; - } - else // env_attack - { - rate = (adsr0 & 0x0F) * 2 + 1; - env += rate < 31 ? 0x20 : 0x400; - } - } - else // GAIN - { - int mode; - env_data = VREG(v_regs,gain); - mode = env_data >> 5; - if ( mode < 4 ) // direct - { - env = env_data * 0x10; - rate = 31; - } - else - { - rate = env_data & 0x1F; - if ( mode == 4 ) // 4: linear decrease - { - env -= 0x20; - } - else if ( mode < 6 ) // 5: exponential decrease - { - env--; - env -= env >> 8; - } - else // 6,7: linear increase - { - env += 0x20; - if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) - env += 0x8 - 0x20; // 7: two-slope linear increase - } - } - } - - // Sustain level - if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) - v->env_mode = env_sustain; - - v->hidden_env = env; - - // unsigned cast because linear decrease going negative also triggers this - if ( (unsigned) env > 0x7FF ) - { - env = (env < 0 ? 0 : 0x7FF); - if ( v->env_mode == env_attack ) - v->env_mode = env_decay; - } - - if ( !READ_COUNTER( rate ) ) - v->env = env; // nothing else is controlled by the counter - } - } - exit_env: - - { - // Apply pitch - int old_pos = v->interp_pos; - int interp_pos = (old_pos & 0x3FFF) + pitch; - if ( interp_pos > 0x7FFF ) - interp_pos = 0x7FFF; - v->interp_pos = interp_pos; - - // BRR decode if necessary - if ( old_pos >= 0x4000 ) - { - // Arrange the four input nybbles in 0xABCD order for easy decoding - int nybbles = ram [(v->brr_addr + v->brr_offset) & 0xFFFF] * 0x100 + - ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; - - // Advance read position - int const brr_block_size = 9; - int brr_offset = v->brr_offset; - if ( (brr_offset += 2) >= brr_block_size ) - { - // Next BRR block - int brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; - assert( brr_offset == brr_block_size ); - if ( brr_header & 1 ) - { - brr_addr = SAMPLE_PTR( 1 ); - if ( !v->kon_delay ) - REG(endx) |= vbit; - } - v->brr_addr = brr_addr; - brr_offset = 1; - } - v->brr_offset = brr_offset; - - // Decode - - // 0: >>1 1: <<0 2: <<1 ... 12: <<11 13-15: >>4 <<11 - static unsigned char const shifts [16 * 2] = { - 13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11 - }; - int const scale = brr_header >> 4; - int const right_shift = shifts [scale]; - int const left_shift = shifts [scale + 16]; - - // Write to next four samples in circular buffer - int* pos = v->buf_pos; - int* end; - - // Decode four samples - for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) - { - // Extract upper nybble and scale appropriately - int s = ((int16_t) nybbles >> right_shift) << left_shift; - - // Apply IIR filter (8 is the most commonly used) - int const filter = brr_header & 0x0C; - int const p1 = pos [brr_buf_size - 1]; - int const p2 = pos [brr_buf_size - 2] >> 1; - if ( filter >= 8 ) - { - s += p1; - s -= p2; - if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 - { - s += p2 >> 4; - s += (p1 * -3) >> 6; - } - else // s += p1 * 0.8984375 - p2 * 0.40625 - { - s += (p1 * -13) >> 7; - s += (p2 * 3) >> 4; - } - } - else if ( filter ) // s += p1 * 0.46875 - { - s += p1 >> 1; - s += (-p1) >> 5; - } - - // Adjust and write sample - CLAMP16( s ); - s = (int16_t) (s * 2); - pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around - } - - if ( pos >= &v->buf [brr_buf_size] ) - pos = v->buf; - v->buf_pos = pos; - } - } -skip_brr: - // Next voice - vbit <<= 1; - v_regs += 0x10; - v++; - } - while ( vbit < 0x100 ); - - // Echo position - int echo_offset = m.echo_offset; - uint8_t* const echo_ptr = &ram [(REG(esa) * 0x100 + echo_offset) & 0xFFFF]; - if ( !echo_offset ) - m.echo_length = (REG(edl) & 0x0F) * 0x800; - echo_offset += 4; - if ( echo_offset >= m.echo_length ) - echo_offset = 0; - m.echo_offset = echo_offset; - - // FIR - int echo_in_l = GET_LE16SA( echo_ptr + 0 ); - int echo_in_r = GET_LE16SA( echo_ptr + 2 ); - - int (*echo_hist_pos) [2] = m.echo_hist_pos; - if ( ++echo_hist_pos >= &m.echo_hist [echo_hist_size] ) - echo_hist_pos = m.echo_hist; - m.echo_hist_pos = echo_hist_pos; - - echo_hist_pos [0] [0] = echo_hist_pos [8] [0] = echo_in_l; - echo_hist_pos [0] [1] = echo_hist_pos [8] [1] = echo_in_r; - - #define CALC_FIR_( i, in ) ((in) * (int8_t) REG(fir + i * 0x10)) - echo_in_l = CALC_FIR_( 7, echo_in_l ); - echo_in_r = CALC_FIR_( 7, echo_in_r ); - - #define CALC_FIR( i, ch ) CALC_FIR_( i, echo_hist_pos [i + 1] [ch] ) - #define DO_FIR( i )\ - echo_in_l += CALC_FIR( i, 0 );\ - echo_in_r += CALC_FIR( i, 1 ); - DO_FIR( 0 ); - DO_FIR( 1 ); - DO_FIR( 2 ); - #if defined (__MWERKS__) && __MWERKS__ < 0x3200 - __eieio(); // keeps compiler from stupidly "caching" things in memory - #endif - DO_FIR( 3 ); - DO_FIR( 4 ); - DO_FIR( 5 ); - DO_FIR( 6 ); - - // Echo out - if ( !(REG(flg) & 0x20) ) - { - int l = (echo_out_l >> 7) + ((echo_in_l * (int8_t) REG(efb)) >> 14); - int r = (echo_out_r >> 7) + ((echo_in_r * (int8_t) REG(efb)) >> 14); - - // just to help pass more validation tests - #if SPC_MORE_ACCURACY - l &= ~1; - r &= ~1; - #endif - - CLAMP16( l ); - CLAMP16( r ); - - SET_LE16A( echo_ptr + 0, l ); - SET_LE16A( echo_ptr + 2, r ); - } - - // Sound out - int l = (main_out_l * mvoll + echo_in_l * (int8_t) REG(evoll)) >> 14; - int r = (main_out_r * mvolr + echo_in_r * (int8_t) REG(evolr)) >> 14; - - CLAMP16( l ); - CLAMP16( r ); - - if ( (REG(flg) & 0x40) ) - { - l = 0; - r = 0; - } - - sample_t* out = m.out; - WRITE_SAMPLES( l, r, out ); - m.out = out; - } - while ( --count ); -} - - -//// Setup - -void SPC_DSP::mute_voices( int mask ) -{ - m.mute_mask = mask; - for ( int i = 0; i < voice_count; i++ ) - { - m.voices [i].enabled = (mask >> i & 1) - 1; - update_voice_vol( i * 0x10 ); - } -} - -void SPC_DSP::init( void* ram_64k ) -{ - m.ram = (uint8_t*) ram_64k; - mute_voices( 0 ); - disable_surround( false ); - set_output( 0, 0 ); - reset(); - - #ifndef NDEBUG - // be sure this sign-extends - assert( (int16_t) 0x8000 == -0x8000 ); - - // be sure right shift preserves sign - assert( (-1 >> 1) == -1 ); - - // check clamp macro - int i; - i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); - i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); - - blargg_verify_byte_order(); - #endif -} - -void SPC_DSP::soft_reset_common() -{ - require( m.ram ); // init() must have been called already - - m.noise = 0x4000; - m.echo_hist_pos = m.echo_hist; - m.every_other_sample = 1; - m.echo_offset = 0; - m.phase = 0; - - init_counter(); -} - -void SPC_DSP::soft_reset() -{ - REG(flg) = 0xE0; - soft_reset_common(); -} - -void SPC_DSP::load( uint8_t const regs [register_count] ) -{ - memcpy( m.regs, regs, sizeof m.regs ); - memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); - - // Internal state - int i; - for ( i = voice_count; --i >= 0; ) - { - voice_t& v = m.voices [i]; - v.brr_offset = 1; - v.buf_pos = v.buf; - } - m.new_kon = REG(kon); - - mute_voices( m.mute_mask ); - soft_reset_common(); -} - -void SPC_DSP::reset() { load( initial_regs ); } diff --git a/snes_spc/snes_spc/SPC_Filter.cpp b/snes_spc/snes_spc/SPC_Filter.cpp deleted file mode 100644 index b3d577082..000000000 --- a/snes_spc/snes_spc/SPC_Filter.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// snes_spc 0.9.0. http://www.slack.net/~ant/ - -#include "SPC_Filter.h" - -#include - -/* Copyright (C) 2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "blargg_source.h" - -void SPC_Filter::clear() { memset( ch, 0, sizeof ch ); } - -SPC_Filter::SPC_Filter() -{ - gain = gain_unit; - bass = bass_norm; - clear(); -} - -void SPC_Filter::run( short* io, int count ) -{ - require( (count & 1) == 0 ); // must be even - - int const gain = this->gain; - int const bass = this->bass; - chan_t* c = &ch [2]; - do - { - // cache in registers - int sum = (--c)->sum; - int pp1 = c->pp1; - int p1 = c->p1; - - for ( int i = 0; i < count; i += 2 ) - { - // Low-pass filter (two point FIR with coeffs 0.25, 0.75) - int f = io [i] + p1; - p1 = io [i] * 3; - - // High-pass filter ("leaky integrator") - int delta = f - pp1; - pp1 = f; - int s = sum >> (gain_bits + 2); - sum += (delta * gain) - (sum >> bass); - - // Clamp to 16 bits - if ( (short) s != s ) - s = (s >> 31) ^ 0x7FFF; - - io [i] = (short) s; - } - - c->p1 = p1; - c->pp1 = pp1; - c->sum = sum; - ++io; - } - while ( c != ch ); -} diff --git a/snes_spc/snes_spc/SPC_Filter.h b/snes_spc/snes_spc/SPC_Filter.h deleted file mode 100644 index d5c83cb8f..000000000 --- a/snes_spc/snes_spc/SPC_Filter.h +++ /dev/null @@ -1,47 +0,0 @@ -// Simple low-pass and high-pass filter to better match sound output of a SNES - -// snes_spc 0.9.0 -#ifndef SPC_FILTER_H -#define SPC_FILTER_H - -#include "blargg_common.h" - -struct SPC_Filter { -public: - - // Filters count samples of stereo sound in place. Count must be a multiple of 2. - typedef short sample_t; - void run( sample_t* io, int count ); - -// Optional features - - // Clears filter to silence - void clear(); - - // Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit - // are fine, since output is clamped to 16-bit sample range. - enum { gain_unit = 0x100 }; - void set_gain( int gain ); - - // Sets amount of bass (logarithmic scale) - enum { bass_none = 0 }; - enum { bass_norm = 8 }; // normal amount - enum { bass_max = 31 }; - void set_bass( int bass ); - -public: - SPC_Filter(); - BLARGG_DISABLE_NOTHROW -private: - enum { gain_bits = 8 }; - int gain; - int bass; - struct chan_t { int p1, pp1, sum; }; - chan_t ch [2]; -}; - -inline void SPC_Filter::set_gain( int g ) { gain = g; } - -inline void SPC_Filter::set_bass( int b ) { bass = b; } - -#endif diff --git a/snes_spc/snes_spc/blargg_config.h b/snes_spc/snes_spc/blargg_config.h deleted file mode 100644 index 9dc69db83..000000000 --- a/snes_spc/snes_spc/blargg_config.h +++ /dev/null @@ -1,24 +0,0 @@ -// snes_spc 0.9.0 user configuration file. Don't replace when updating library. - -// snes_spc 0.9.0 -#ifndef BLARGG_CONFIG_H -#define BLARGG_CONFIG_H - -// Uncomment to disable debugging checks -//#define NDEBUG 1 - -// Uncomment to enable platform-specific (and possibly non-portable) optimizations -//#define BLARGG_NONPORTABLE 1 - -// Uncomment if automatic byte-order determination doesn't work -//#define BLARGG_BIG_ENDIAN 1 - -// Uncomment if you get errors in the bool section of blargg_common.h -//#define BLARGG_COMPILER_HAS_BOOL 1 - -// Use standard config.h if present -#ifdef HAVE_CONFIG_H - #include "config.h" -#endif - -#endif diff --git a/snes_spc/snes_spc/dsp.cpp b/snes_spc/snes_spc/dsp.cpp deleted file mode 100644 index 58919e908..000000000 --- a/snes_spc/snes_spc/dsp.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// snes_spc 0.9.0. http://www.slack.net/~ant/ - -#include "dsp.h" - -#include "SPC_DSP.h" - -/* Copyright (C) 2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "blargg_source.h" - -SPC_DSP* spc_dsp_new( void ) -{ - // be sure constants match - assert( spc_dsp_voice_count == (int) SPC_DSP::voice_count ); - assert( spc_dsp_register_count == (int) SPC_DSP::register_count ); - #if !SPC_NO_COPY_STATE_FUNCS - assert( spc_dsp_state_size == (int) SPC_DSP::state_size ); - #endif - - return new SPC_DSP; -} - -void spc_dsp_delete ( SPC_DSP* s ) { delete s; } -void spc_dsp_init ( SPC_DSP* s, void* ram_64k ) { s->init( ram_64k ); } -void spc_dsp_set_output ( SPC_DSP* s, spc_dsp_sample_t* p, int n ) { s->set_output( p, n ); } -int spc_dsp_sample_count( SPC_DSP const* s ) { return s->sample_count(); } -void spc_dsp_reset ( SPC_DSP* s ) { s->reset(); } -void spc_dsp_soft_reset ( SPC_DSP* s ) { s->soft_reset(); } -int spc_dsp_read ( SPC_DSP const* s, int addr ) { return s->read( addr ); } -void spc_dsp_write ( SPC_DSP* s, int addr, int data ) { s->write( addr, data ); } -void spc_dsp_run ( SPC_DSP* s, int clock_count ) { s->run( clock_count ); } -void spc_dsp_mute_voices ( SPC_DSP* s, int mask ) { s->mute_voices( mask ); } -void spc_dsp_disable_surround( SPC_DSP* s, int disable ) { s->disable_surround( !!disable ); } -void spc_dsp_load ( SPC_DSP* s, unsigned char const regs [spc_dsp_register_count] ) { s->load( regs ); } - -#if !SPC_NO_COPY_STATE_FUNCS -void spc_dsp_copy_state ( SPC_DSP* s, unsigned char** p, spc_dsp_copy_func_t f ) { s->copy_state( p, f ); } -int spc_dsp_check_kon ( SPC_DSP* s ) { return s->check_kon(); } -#endif diff --git a/snes_spc/snes_spc/dsp.h b/snes_spc/snes_spc/dsp.h deleted file mode 100644 index 59867d92f..000000000 --- a/snes_spc/snes_spc/dsp.h +++ /dev/null @@ -1,83 +0,0 @@ -/* SNES SPC-700 DSP emulator C interface (also usable from C++) */ - -/* snes_spc 0.9.0 */ -#ifndef DSP_H -#define DSP_H - -#include - -#ifdef __cplusplus - extern "C" { -#endif - -typedef struct SPC_DSP SPC_DSP; - -/* Creates new DSP emulator. NULL if out of memory. */ -SPC_DSP* spc_dsp_new( void ); - -/* Frees DSP emulator */ -void spc_dsp_delete( SPC_DSP* ); - -/* Initializes DSP and has it use the 64K RAM provided */ -void spc_dsp_init( SPC_DSP*, void* ram_64k ); - -/* Sets destination for output samples. If out is NULL or out_size is 0, -doesn't generate any. */ -typedef short spc_dsp_sample_t; -void spc_dsp_set_output( SPC_DSP*, spc_dsp_sample_t* out, int out_size ); - -/* Number of samples written to output since it was last set, always -a multiple of 2. Undefined if more samples were generated than -output buffer could hold. */ -int spc_dsp_sample_count( SPC_DSP const* ); - - -/**** Emulation *****/ - -/* Resets DSP to power-on state */ -void spc_dsp_reset( SPC_DSP* ); - -/* Emulates pressing reset switch on SNES */ -void spc_dsp_soft_reset( SPC_DSP* ); - -/* Reads/writes DSP registers. For accuracy, you must first call spc_dsp_run() */ -/* to catch the DSP up to present. */ -int spc_dsp_read ( SPC_DSP const*, int addr ); -void spc_dsp_write( SPC_DSP*, int addr, int data ); - -/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks */ -/* a pair of samples is be generated. */ -void spc_dsp_run( SPC_DSP*, int clock_count ); - - -/**** Sound control *****/ - -/* Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */ -enum { spc_dsp_voice_count = 8 }; -void spc_dsp_mute_voices( SPC_DSP*, int mask ); - -/* If true, prevents channels and global volumes from being phase-negated. -Only supported by fast DSP; has no effect on accurate DSP. */ -void spc_dsp_disable_surround( SPC_DSP*, int disable ); - - -/**** State save/load *****/ - -/* Resets DSP and uses supplied values to initialize registers */ -enum { spc_dsp_register_count = 128 }; -void spc_dsp_load( SPC_DSP*, unsigned char const regs [spc_dsp_register_count] ); - -/* Saves/loads exact emulator state (accurate DSP only) */ -enum { spc_dsp_state_size = 640 }; /* maximum space needed when saving */ -typedef void (*spc_dsp_copy_func_t)( unsigned char** io, void* state, size_t ); -void spc_dsp_copy_state( SPC_DSP*, unsigned char** io, spc_dsp_copy_func_t ); - -/* Returns non-zero if new key-on events occurred since last call (accurate DSP only) */ -int spc_dsp_check_kon( SPC_DSP* ); - - -#ifdef __cplusplus - } -#endif - -#endif diff --git a/snes_spc/snes_spc/spc.cpp b/snes_spc/snes_spc/spc.cpp deleted file mode 100644 index 0aeb26f2f..000000000 --- a/snes_spc/snes_spc/spc.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// snes_spc 0.9.0. http://www.slack.net/~ant/ - -#include "spc.h" - -#include "SNES_SPC.h" -#include "SPC_Filter.h" - -/* Copyright (C) 2004-2007 Shay Green. This module is free software; you -can redistribute it and/or modify it under the terms of the GNU Lesser -General Public License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. This -module 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 Lesser General Public License for more -details. You should have received a copy of the GNU Lesser General Public -License along with this module; if not, write to the Free Software Foundation, -Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "blargg_source.h" - -SNES_SPC* spc_new( void ) -{ - // be sure constants match - assert( spc_sample_rate == (int) SNES_SPC::sample_rate ); - assert( spc_rom_size == (int) SNES_SPC::rom_size ); - assert( spc_clock_rate == (int) SNES_SPC::clock_rate ); - assert( spc_clocks_per_sample == (int) SNES_SPC::clocks_per_sample ); - assert( spc_port_count == (int) SNES_SPC::port_count ); - assert( spc_voice_count == (int) SNES_SPC::voice_count ); - assert( spc_tempo_unit == (int) SNES_SPC::tempo_unit ); - assert( spc_file_size == (int) SNES_SPC::spc_file_size ); - #if !SPC_NO_COPY_STATE_FUNCS - assert( spc_state_size == (int) SNES_SPC::state_size ); - #endif - - SNES_SPC* s = new SNES_SPC; - if ( s && s->init() ) - { - delete s; - s = 0; - } - return s; -} - -void spc_delete ( SNES_SPC* s ) { delete s; } -void spc_init_rom ( SNES_SPC* s, unsigned char const r [64] ) { s->init_rom( r ); } -void spc_set_output ( SNES_SPC* s, spc_sample_t* p, int n ) { s->set_output( p, n ); } -int spc_sample_count ( SNES_SPC const* s ) { return s->sample_count(); } -void spc_reset ( SNES_SPC* s ) { s->reset(); } -void spc_soft_reset ( SNES_SPC* s ) { s->soft_reset(); } -int spc_read_port ( SNES_SPC* s, spc_time_t t, int p ) { return s->read_port( t, p ); } -void spc_write_port ( SNES_SPC* s, spc_time_t t, int p, int d ) { s->write_port( t, p, d ); } -void spc_end_frame ( SNES_SPC* s, spc_time_t t ) { s->end_frame( t ); } -void spc_mute_voices ( SNES_SPC* s, int mask ) { s->mute_voices( mask ); } -void spc_disable_surround( SNES_SPC* s, int disable ) { s->disable_surround( !!disable ); } -void spc_set_tempo ( SNES_SPC* s, int tempo ) { s->set_tempo( tempo ); } -spc_err_t spc_load_spc ( SNES_SPC* s, void const* p, long n ) { return s->load_spc( p, n ); } -void spc_clear_echo ( SNES_SPC* s ) { s->clear_echo(); } -spc_err_t spc_play ( SNES_SPC* s, int count, short* out ) { return s->play( count, out ); } -spc_err_t spc_skip ( SNES_SPC* s, int count ) { return s->skip( count ); } -#if !SPC_NO_COPY_STATE_FUNCS -void spc_copy_state ( SNES_SPC* s, unsigned char** p, spc_copy_func_t f ) { s->copy_state( p, f ); } -void spc_init_header ( void* spc_out ) { SNES_SPC::init_header( spc_out ); } -void spc_save_spc ( SNES_SPC* s, void* spc_out ) { s->save_spc( spc_out ); } -int spc_check_kon ( SNES_SPC* s ) { return s->check_kon(); } -#endif - -SPC_Filter* spc_filter_new( void ) { return new SPC_Filter; } -void spc_filter_delete( SPC_Filter* f ) { delete f; } -void spc_filter_run( SPC_Filter* f, spc_sample_t* p, int s ) { f->run( p, s ); } -void spc_filter_clear( SPC_Filter* f ) { f->clear(); } -void spc_filter_set_gain( SPC_Filter* f, int gain ) { f->set_gain( gain ); } -void spc_filter_set_bass( SPC_Filter* f, int bass ) { f->set_bass( bass ); } diff --git a/snes_spc/snes_spc/spc.h b/snes_spc/snes_spc/spc.h deleted file mode 100644 index 9ffdedce1..000000000 --- a/snes_spc/snes_spc/spc.h +++ /dev/null @@ -1,147 +0,0 @@ -/* SNES SPC-700 APU emulator C interface (also usable from C++) */ - -/* snes_spc 0.9.0 */ -#ifndef SPC_H -#define SPC_H - -#include - -#ifdef __cplusplus - extern "C" { -#endif - -/* Error string return. NULL if success, otherwise error message. */ -typedef const char* spc_err_t; - -typedef struct SNES_SPC SNES_SPC; - -/* Creates new SPC emulator. NULL if out of memory. */ -SNES_SPC* spc_new( void ); - -/* Frees SPC emulator */ -void spc_delete( SNES_SPC* ); - -/* Sample pairs generated per second */ -enum { spc_sample_rate = 32000 }; - - -/**** Emulator use ****/ - -/* Sets IPL ROM data. Library does not include ROM data. Most SPC music files -don't need ROM, but a full emulator must provide this. */ -enum { spc_rom_size = 0x40 }; -void spc_init_rom( SNES_SPC*, unsigned char const rom [spc_rom_size] ); - -/* Sets destination for output samples */ -typedef short spc_sample_t; -void spc_set_output( SNES_SPC*, spc_sample_t* out, int out_size ); - -/* Number of samples written to output since last set */ -int spc_sample_count( SNES_SPC const* ); - -/* Resets SPC to power-on state. This resets your output buffer, so you must -call spc_set_output() after this. */ -void spc_reset( SNES_SPC* ); - -/* Emulates pressing reset switch on SNES. This resets your output buffer, so -you must call spc_set_output() after this. */ -void spc_soft_reset( SNES_SPC* ); - -/* 1024000 SPC clocks per second, sample pair every 32 clocks */ -typedef int spc_time_t; -enum { spc_clock_rate = 1024000 }; -enum { spc_clocks_per_sample = 32 }; - -/* Reads/writes port at specified time */ -enum { spc_port_count = 4 }; -int spc_read_port ( SNES_SPC*, spc_time_t, int port ); -void spc_write_port( SNES_SPC*, spc_time_t, int port, int data ); - -/* Runs SPC to end_time and starts a new time frame at 0 */ -void spc_end_frame( SNES_SPC*, spc_time_t end_time ); - - -/**** Sound control ****/ - -/*Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */ -enum { spc_voice_count = 8 }; -void spc_mute_voices( SNES_SPC*, int mask ); - -/* If true, prevents channels and global volumes from being phase-negated. -Only supported by fast DSP; has no effect on accurate DSP. */ -void spc_disable_surround( SNES_SPC*, int disable ); - -/* Sets tempo, where spc_tempo_unit = normal, spc_tempo_unit / 2 = half speed, etc. */ -enum { spc_tempo_unit = 0x100 }; -void spc_set_tempo( SNES_SPC*, int ); - - -/**** SPC music playback *****/ - -/* Loads SPC data into emulator. Returns NULL on success, otherwise error string. */ -spc_err_t spc_load_spc( SNES_SPC*, void const* spc_in, long size ); - -/* Clears echo region. Useful after loading an SPC as many have garbage in echo. */ -void spc_clear_echo( SNES_SPC* ); - -/* Plays for count samples and write samples to out. Discards samples if out -is NULL. Count must be a multiple of 2 since output is stereo. */ -spc_err_t spc_play( SNES_SPC*, int count, short* out ); - -/* Skips count samples. Several times faster than spc_play(). */ -spc_err_t spc_skip( SNES_SPC*, int count ); - - -/**** State save/load (only available with accurate DSP) ****/ - -/* Saves/loads exact emulator state */ -enum { spc_state_size = 67 * 1024L }; /* maximum space needed when saving */ -typedef void (*spc_copy_func_t)( unsigned char** io, void* state, size_t ); -void spc_copy_state( SNES_SPC*, unsigned char** io, spc_copy_func_t ); - -/* Writes minimal SPC file header to spc_out */ -void spc_init_header( void* spc_out ); - -/* Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. -Does not set up SPC header; use spc_init_header() for that. */ -enum { spc_file_size = 0x10200 }; /* spc_out must have this many bytes allocated */ -void spc_save_spc( SNES_SPC*, void* spc_out ); - -/* Returns non-zero if new key-on events occurred since last check. Useful for -trimming silence while saving an SPC. */ -int spc_check_kon( SNES_SPC* ); - - -/**** SPC_Filter ****/ - -typedef struct SPC_Filter SPC_Filter; - -/* Creates new filter. NULL if out of memory. */ -SPC_Filter* spc_filter_new( void ); - -/* Frees filter */ -void spc_filter_delete( SPC_Filter* ); - -/* Filters count samples of stereo sound in place. Count must be a multiple of 2. */ -void spc_filter_run( SPC_Filter*, spc_sample_t* io, int count ); - -/* Clears filter to silence */ -void spc_filter_clear( SPC_Filter* ); - -/* Sets gain (volume), where spc_filter_gain_unit is normal. Gains greater than -spc_filter_gain_unit are fine, since output is clamped to 16-bit sample range. */ -enum { spc_filter_gain_unit = 0x100 }; -void spc_filter_set_gain( SPC_Filter*, int gain ); - -/* Sets amount of bass (logarithmic scale) */ -enum { spc_filter_bass_none = 0 }; -enum { spc_filter_bass_norm = 8 }; /* normal amount */ -enum { spc_filter_bass_max = 31 }; -void spc_filter_set_bass( SPC_Filter*, int bass ); - - -#ifdef __cplusplus - } -#endif - -#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 544fc2af0..f66c300f3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -674,13 +674,13 @@ add_executable( zdoom WIN32 sound/i_sound.cpp sound/music_cd.cpp sound/music_dumb.cpp + sound/music_gme.cpp sound/music_midistream.cpp sound/music_midi_base.cpp sound/music_midi_midiout.cpp sound/music_midi_timidity.cpp sound/music_mus_midiout.cpp sound/music_mus_opl.cpp - sound/music_spc.cpp sound/music_stream.cpp sound/music_timidity_mididevice.cpp sound/music_win_mididevice.cpp @@ -726,7 +726,7 @@ add_executable( zdoom WIN32 set_source_files_properties( xlat/parse_xlat.cpp PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c" ) set_source_files_properties( sc_man.cpp PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h" ) -target_link_libraries( zdoom ${ZDOOM_LIBS} snes_spc gdtoa dumb lzma ) +target_link_libraries( zdoom ${ZDOOM_LIBS} gme gdtoa dumb lzma ) include_directories( . g_doom g_heretic @@ -740,7 +740,7 @@ include_directories( . thingdef timidity xlat - ../snes_spc/snes_spc + ../game-music-emu/gme ../gdtoa ../dumb/include ${CMAKE_BINARY_DIR}/gdtoa diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 564b20435..1dcde8ccc 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -189,7 +189,7 @@ static EIWADType ScanIWAD (const char *iwad) Check_e2m1 }; int lumpsfound[NUM_CHECKLUMPS]; - int i; + size_t i; memset (lumpsfound, 0, sizeof(lumpsfound)); FResourceFile *iwadfile = FResourceFile::OpenResourceFile(iwad, NULL, true); diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index 05b070667..befbaf535 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -52,7 +52,7 @@ void OPLmusicBlock::Restart() LastOffset = 0; } -OPLmusicFile::OPLmusicFile (FILE *file, char *musiccache, int len) +OPLmusicFile::OPLmusicFile (FILE *file, BYTE *musiccache, int len) : ScoreLen (len) { if (io == NULL) diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index cd2b9c414..58cadc8b6 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -28,7 +28,7 @@ protected: class OPLmusicFile : public OPLmusicBlock { public: - OPLmusicFile(FILE *file, char *musiccache, int len); + OPLmusicFile(FILE *file, BYTE *musiccache, int len); OPLmusicFile(const OPLmusicFile *source, const char *filename); virtual ~OPLmusicFile(); diff --git a/src/s_sound.cpp b/src/s_sound.cpp index ecb0e9673..c0955cb5c 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -2231,7 +2231,7 @@ bool S_StartMusic (const char *m_id) // specified, it will only be played if the specified CD is in a drive. //========================================================================== -TArray musiccache; +TArray musiccache; bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force) { diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index 6fa967582..f347f9051 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -78,8 +78,6 @@ struct FEnumList // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- -FMOD_RESULT SPC_CreateCodec(FMOD::System *sys); - // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- @@ -1083,12 +1081,6 @@ bool FMODSoundRenderer::Init() } } - result = SPC_CreateCodec(Sys); - if (result != FMOD_OK) - { - Printf(TEXTCOLOR_BLUE" Could not register SPC codec. (Error %d)\n", result); - } - if (FMOD_OK != Sys->getSoftwareFormat(&OutputRate, NULL, NULL, NULL, NULL, NULL)) { OutputRate = 48000; // Guess, but this should never happen. diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index d2915b920..d2e2efee0 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -73,11 +73,24 @@ extern void ChildSigHandler (int signum); #include "stats.h" #include "timidity/timidity.h" +#define GZIP_ID1 31 +#define GZIP_ID2 139 +#define GZIP_CM 8 +#define GZIP_ID MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0) + +#define GZIP_FTEXT 1 +#define GZIP_FHCRC 2 +#define GZIP_FEXTRA 4 +#define GZIP_FNAME 8 +#define GZIP_FCOMMENT 16 + EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Int, snd_mididevice) static bool MusicDown = true; +static BYTE *ungzip(BYTE *data, int *size); + MusInfo *currSong; int nomusic = 0; float relative_volume = 1.f; @@ -275,11 +288,13 @@ MusInfo *I_RegisterURLSong (const char *url) return NULL; } -MusInfo *I_RegisterSong (const char *filename, char *musiccache, int offset, int len, int device) +MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int len, int device) { FILE *file; MusInfo *info = NULL; DWORD id; + const char *fmt; + BYTE *ungzipped; if (nomusic) { @@ -315,14 +330,45 @@ MusInfo *I_RegisterSong (const char *filename, char *musiccache, int offset, int else { file = NULL; - memcpy(&id, &musiccache[0], 4); + id = *(DWORD *)musiccache; } #ifndef _WIN32 // non-windows platforms don't support MDEV_MIDI so map to MDEV_FMOD - if (device == MDEV_MMAPI) device = MDEV_FMOD; + if (device == MDEV_MMAPI) + device = MDEV_FMOD; #endif + // Check for gzip compression. Some formats are expected to have players + // that can handle it, so it simplifies things if we make all songs + // gzippable. + ungzipped = NULL; + if ((id & MAKE_ID(255,255,255,0)) == GZIP_ID) + { + if (offset != -1) + { + BYTE *gzipped = new BYTE[len]; + if (fread(gzipped, 1, len, file) != (size_t)len) + { + delete[] gzipped; + fclose(file); + return NULL; + } + ungzipped = ungzip(gzipped, &len); + delete[] gzipped; + } + else + { + ungzipped = ungzip(musiccache, &len); + } + if (ungzipped == NULL) + { + fclose(file); + return NULL; + } + musiccache = ungzipped; + id = *(DWORD *)ungzipped; + } // Check for MUS format if (id == MAKE_ID('M','U','S',0x1a)) @@ -417,7 +463,7 @@ MusInfo *I_RegisterSong (const char *filename, char *musiccache, int offset, int - if explicitly selected by $mididevice - when snd_mididevice is -2 and no midi device is set for the song - FMod: + FMOD: - if explicitly selected by $mididevice - when snd_mididevice is -1 and no midi device is set for the song - as fallback when Timidity failed unless snd_mididevice is >= 0 @@ -481,6 +527,11 @@ MusInfo *I_RegisterSong (const char *filename, char *musiccache, int offset, int info = new OPLMUSSong (file, musiccache, len); } } + // Check for game music + else if ((fmt = GME_CheckFormat(id)) != NULL) + { + info = GME_OpenSong(file, musiccache, len, fmt); + } // Check for module formats else { @@ -526,21 +577,23 @@ MusInfo *I_RegisterSong (const char *filename, char *musiccache, int offset, int fclose (file); file = NULL; } - info = new StreamSong (offset >=0 ? filename : musiccache, offset, len); + info = new StreamSong (offset >= 0 ? filename : (const char *)musiccache, offset, len); } } - if (info && !info->IsValid ()) { delete info; info = NULL; } - if (file != NULL) { fclose (file); } + if (ungzipped != NULL) + { + delete[] ungzipped; + } return info; } @@ -558,6 +611,88 @@ MusInfo *I_RegisterCDSong (int track, int id) return info; } +//========================================================================== +// +// ungzip +// +// VGZ files are compressed with gzip, so we need to uncompress them before +// handing them to GME. +// +//========================================================================== + +BYTE *ungzip(BYTE *data, int *complen) +{ + const BYTE *max = data + *complen - 8; + const BYTE *compstart = data + 10; + BYTE flags = data[3]; + unsigned isize; + BYTE *newdata; + z_stream stream; + int err; + + // Find start of compressed data stream + if (flags & GZIP_FEXTRA) + { + compstart += 2 + LittleShort(*(WORD *)(data + 10)); + } + if (flags & GZIP_FNAME) + { + while (compstart < max && *compstart != 0) + { + compstart++; + } + } + if (flags & GZIP_FCOMMENT) + { + while (compstart < max && *compstart != 0) + { + compstart++; + } + } + if (flags & GZIP_FHCRC) + { + compstart += 2; + } + if (compstart >= max - 1) + { + return NULL; + } + + // Decompress + isize = LittleLong(*(DWORD *)(data + *complen - 4)); + newdata = new BYTE[isize]; + + stream.next_in = (Bytef *)compstart; + stream.avail_in = (uInt)(max - compstart); + stream.next_out = newdata; + stream.avail_out = isize; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) + { + delete[] newdata; + return NULL; + } + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) + { + inflateEnd(&stream); + delete[] newdata; + return NULL; + } + err = inflateEnd(&stream); + if (err != Z_OK) + { + delete[] newdata; + return NULL; + } + *complen = isize; + return newdata; +} + void I_UpdateMusic() { if (currSong != NULL) diff --git a/src/sound/i_music.h b/src/sound/i_music.h index 12a26e71d..4bde02df1 100644 --- a/src/sound/i_music.h +++ b/src/sound/i_music.h @@ -55,7 +55,7 @@ void I_ResumeSong (void *handle); // Registers a song handle to song data. class MusInfo; -MusInfo *I_RegisterSong (const char *file, char * musiccache, int offset, int length, int device); +MusInfo *I_RegisterSong (const char *file, BYTE *musiccache, int offset, int length, int device); MusInfo *I_RegisterCDSong (int track, int cdid = 0); MusInfo *I_RegisterURLSong (const char *url); diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 02cfe8747..9d7739ee4 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -343,7 +343,7 @@ protected: class MUSSong2 : public MIDIStreamer { public: - MUSSong2(FILE *file, char *musiccache, int length, EMIDIDevice type); + MUSSong2(FILE *file, BYTE *musiccache, int length, EMIDIDevice type); ~MUSSong2(); MusInfo *GetOPLDumper(const char *filename); @@ -369,7 +369,7 @@ protected: class MIDISong2 : public MIDIStreamer { public: - MIDISong2(FILE *file, char *musiccache, int length, EMIDIDevice type); + MIDISong2(FILE *file, BYTE *musiccache, int length, EMIDIDevice type); ~MIDISong2(); MusInfo *GetOPLDumper(const char *filename); @@ -431,7 +431,7 @@ protected: class TimiditySong : public StreamSong { public: - TimiditySong (FILE *file, char * musiccache, int length); + TimiditySong (FILE *file, BYTE *musiccache, int length); ~TimiditySong (); void Play (bool looping, int subsong); void Stop (); @@ -469,7 +469,7 @@ protected: class OPLMUSSong : public StreamSong { public: - OPLMUSSong (FILE *file, char *musiccache, int length); + OPLMUSSong (FILE *file, BYTE *musiccache, int length); ~OPLMUSSong (); void Play (bool looping, int subsong); bool IsPlaying (); @@ -524,7 +524,12 @@ public: // Module played via foo_dumb ----------------------------------------------- -MusInfo *MOD_OpenSong(FILE *file, char *musiccache, int len); +MusInfo *MOD_OpenSong(FILE *file, BYTE *musiccache, int len); + +// Music played via Game Music Emu ------------------------------------------ + +const char *GME_CheckFormat(uint32 header); +MusInfo *GME_OpenSong(FILE *file, BYTE *musiccache, int len, const char *fmt); // -------------------------------------------------------------------------- diff --git a/src/sound/music_dumb.cpp b/src/sound/music_dumb.cpp index e2002ba9a..3ba117fd1 100644 --- a/src/sound/music_dumb.cpp +++ b/src/sound/music_dumb.cpp @@ -513,7 +513,7 @@ static DUMBFILE_SYSTEM mem_dfs = { // //========================================================================== -DUMBFILE *dumb_read_allfile(dumbfile_mem_status *filestate, char *start, FILE *file, char *musiccache, int lenhave, int lenfull) +DUMBFILE *dumb_read_allfile(dumbfile_mem_status *filestate, BYTE *start, FILE *file, BYTE *musiccache, int lenhave, int lenfull) { filestate->size = lenfull; filestate->offset = 0; @@ -857,13 +857,13 @@ static void MOD_SetAutoChip(DUH *duh) // //========================================================================== -MusInfo *MOD_OpenSong(FILE *file, char *musiccache, int size) +MusInfo *MOD_OpenSong(FILE *file, BYTE *musiccache, int size) { DUH *duh = 0; int headsize; union { - char start[64]; + BYTE start[64]; DWORD dstart[64/4]; }; dumbfile_mem_status filestate; @@ -881,7 +881,7 @@ MusInfo *MOD_OpenSong(FILE *file, char *musiccache, int size) atterm(dumb_exit); - filestate.ptr = (BYTE *)start; + filestate.ptr = start; filestate.offset = 0; headsize = MIN((int)sizeof(start), size); if (musiccache != NULL) @@ -921,9 +921,9 @@ MusInfo *MOD_OpenSong(FILE *file, char *musiccache, int size) } else if (size >= 1168 && /*start[28] == 0x1A &&*/ start[29] == 2 && - ( ! strnicmp( &start[20], "!Scream!", 8 ) || - ! strnicmp( &start[20], "BMOD2STM", 8 ) || - ! strnicmp( &start[20], "WUZAMOD!", 8 ) ) ) + ( !memcmp( &start[20], "!Scream!", 8 ) || + !memcmp( &start[20], "BMOD2STM", 8 ) || + !memcmp( &start[20], "WUZAMOD!", 8 ) ) ) { if ((f = dumb_read_allfile(&filestate, start, file, musiccache, headsize, size))) { @@ -1050,7 +1050,7 @@ MusInfo *MOD_OpenSong(FILE *file, char *musiccache, int size) fseek(file, fpos, SEEK_SET); } } - if (filestate.ptr != (BYTE *)start && filestate.ptr != (BYTE *)musiccache) + if (filestate.ptr != (BYTE *)start && filestate.ptr != musiccache) { delete[] const_cast(filestate.ptr); } diff --git a/src/sound/music_gme.cpp b/src/sound/music_gme.cpp new file mode 100644 index 000000000..07e217cfb --- /dev/null +++ b/src/sound/music_gme.cpp @@ -0,0 +1,359 @@ +/* +** music_gme.cpp +** General game music player, using Game Music Emu for decoding. +** +**--------------------------------------------------------------------------- +** Copyright 2009 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +// Uncomment if you are using the DLL version of GME. +//#define GME_DLL + +#define USE_WINDOWS_DWORD +#include "i_musicinterns.h" +#include "c_cvars.h" +#include "critsec.h" +#include "gme.h" +#include "v_text.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class GMESong : public StreamSong +{ +public: + GMESong(Music_Emu *emu, int sample_rate); + ~GMESong(); + bool SetSubsong(int subsong); + void Play(bool looping, int subsong); + FString GetStats(); + +protected: + FCriticalSection CritSec; + Music_Emu *Emu; + gme_info_t *TrackInfo; + int SampleRate; + int CurrTrack; + + bool StartTrack(int track, bool getcritsec=true); + bool GetTrackInfo(); + int CalcSongLength(); + + static bool Read(SoundStream *stream, void *buff, int len, void *userdata); +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// Currently not used. +CVAR (Float, spc_amp, 1.875f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// GME_CheckFormat +// +//========================================================================== + +const char *GME_CheckFormat(uint32 id) +{ + return gme_identify_header(&id); +} + +//========================================================================== +// +// GME_OpenSong +// +//========================================================================== + +MusInfo *GME_OpenSong(FILE *file, BYTE *musiccache, int len, const char *fmt) +{ + gme_type_t type; + gme_err_t err; + BYTE *song; + Music_Emu *emu; + int sample_rate; + + type = gme_identify_extension(fmt); + if (type == NULL) + { + return NULL; + } + sample_rate = (int)GSnd->GetOutputRate(); + emu = gme_new_emu(type, sample_rate); + if (emu == NULL) + { + return NULL; + } + if (musiccache != NULL) + { + song = musiccache; + } + else + { + song = new BYTE[len]; + if (fread(song, 1, len, file) != (size_t)len) + { + delete[] song; + gme_delete(emu); + return NULL; + } + } + err = gme_load_data(emu, song, len); + if (song != musiccache) + { + delete[] song; + } + if (err != NULL) + { + Printf("Failed loading song: %s\n", err); + gme_delete(emu); + return NULL; + } + return new GMESong(emu, sample_rate); +} + +//========================================================================== +// +// GMESong - Constructor +// +//========================================================================== + +GMESong::GMESong(Music_Emu *emu, int sample_rate) +{ + Emu = emu; + SampleRate = sample_rate; + CurrTrack = 0; + TrackInfo = NULL; + m_Stream = GSnd->CreateStream(Read, 32*1024, 0, sample_rate, this); +} + +//========================================================================== +// +// GMESong - Destructor +// +//========================================================================== + +GMESong::~GMESong() +{ + Stop(); + if (m_Stream != NULL) + { + delete m_Stream; + m_Stream = NULL; + } + if (TrackInfo != NULL) + { + gme_free_info(TrackInfo); + } + if (Emu != NULL) + { + gme_delete(Emu); + } +} + + +//========================================================================== +// +// GMESong :: Play +// +//========================================================================== + +void GMESong::Play(bool looping, int track) +{ + m_Status = STATE_Stopped; + m_Looping = looping; + if (StartTrack(0) && m_Stream->Play(looping, 1)) + { + m_Status = STATE_Playing; + } +} + +//========================================================================== +// +// GMESong :: SetSubsong +// +//========================================================================== + +bool GMESong::SetSubsong(int track) +{ + if (CurrTrack == track) + { + return false; + } + return StartTrack(track); +} + +//========================================================================== +// +// GMESong :: StartTrack +// +//========================================================================== + +bool GMESong::StartTrack(int track, bool getcritsec) +{ + gme_err_t err; + + if (getcritsec) + { + CritSec.Enter(); + } + err = gme_start_track(Emu, track); + if (getcritsec) + { + CritSec.Leave(); + } + if (err != NULL) + { + Printf("Could not start track %d: %s\n", track, err); + return false; + } + CurrTrack = track; + GetTrackInfo(); + if (!m_Looping) + { + gme_set_fade(Emu, CalcSongLength()); + } + return true; +} + +//========================================================================== +// +// GMESong :: GetStats +// +//========================================================================== + +FString GMESong::GetStats() +{ + FString out; + + if (TrackInfo != NULL) + { + int time = gme_tell(Emu); + out.Format( + "Track: " TEXTCOLOR_YELLOW "%d" TEXTCOLOR_NORMAL + " Time:" TEXTCOLOR_YELLOW "%3d:%02d:%03d" TEXTCOLOR_NORMAL + " System: " TEXTCOLOR_YELLOW "%s" TEXTCOLOR_NORMAL, + CurrTrack, + time/60000, + (time/1000) % 60, + time % 1000, + TrackInfo->system); + } + return out; +} + +//========================================================================== +// +// GMESong :: GetTrackInfo +// +//========================================================================== + +bool GMESong::GetTrackInfo() +{ + gme_err_t err; + + if (TrackInfo != NULL) + { + gme_free_info(TrackInfo); + TrackInfo = NULL; + } + err = gme_track_info(Emu, &TrackInfo, CurrTrack); + if (err != NULL) + { + Printf("Could not get track %d info: %s\n", CurrTrack, err); + return false; + } + return true; +} + +//========================================================================== +// +// GMESong :: CalcSongLength +// +//========================================================================== + +int GMESong::CalcSongLength() +{ + if (TrackInfo == NULL) + { + return 150000; + } + if (TrackInfo->length > 0) + { + return TrackInfo->length; + } + if (TrackInfo->loop_length > 0) + { + return TrackInfo->intro_length + TrackInfo->loop_length * 2; + } + return 150000; +} + +//========================================================================== +// +// GMESong :: Read STATIC +// +//========================================================================== + +bool GMESong::Read(SoundStream *stream, void *buff, int len, void *userdata) +{ + gme_err_t err; + GMESong *song = (GMESong *)userdata; + + song->CritSec.Enter(); + if (gme_track_ended(song->Emu)) + { + if (song->m_Looping) + { + song->StartTrack(song->CurrTrack, false); + } + else + { + memset(buff, 0, len); + return false; + } + } + err = gme_play(song->Emu, len >> 1, (short *)buff); + song->CritSec.Leave(); + return (err == NULL); +} diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp index d91a18ae0..00d816c66 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_midi_midiout.cpp @@ -101,7 +101,7 @@ static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // //========================================================================== -MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, EMIDIDevice type) +MIDISong2::MIDISong2 (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) : MIDIStreamer(type), MusHeader(0), Tracks(0) { int p; diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index 4765407ad..15e69c8a6 100644 --- a/src/sound/music_midi_timidity.cpp +++ b/src/sound/music_midi_timidity.cpp @@ -174,7 +174,7 @@ TimiditySong::~TimiditySong () #endif } -TimiditySong::TimiditySong (FILE *file, char * musiccache, int len) +TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) : DiskName ("zmid"), #ifdef _WIN32 ReadWavePipe (INVALID_HANDLE_VALUE), WriteWavePipe (INVALID_HANDLE_VALUE), @@ -212,7 +212,10 @@ TimiditySong::TimiditySong (FILE *file, char * musiccache, int len) buf = new BYTE[len]; fread (buf, 1, len, file); } - else buf = (BYTE*)musiccache; + else + { + buf = musiccache; + } // The file type has already been checked before this class instance was @@ -227,7 +230,7 @@ TimiditySong::TimiditySong (FILE *file, char * musiccache, int len) success = ProduceMIDI (buf, f); } fclose (f); - if (file!=NULL) + if (file != NULL) { delete[] buf; } diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index c89292e72..b46cbbaa7 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -89,7 +89,7 @@ static const BYTE CtrlTranslate[15] = // //========================================================================== -MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len, EMIDIDevice type) +MUSSong2::MUSSong2 (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) : MIDIStreamer(type), MusHeader(0), MusBuffer(0) { #ifdef _WIN32 diff --git a/src/sound/music_mus_opl.cpp b/src/sound/music_mus_opl.cpp index 006b1a069..8f5d430ba 100644 --- a/src/sound/music_mus_opl.cpp +++ b/src/sound/music_mus_opl.cpp @@ -10,7 +10,7 @@ CUSTOM_CVAR (Bool, opl_onechip, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) } } -OPLMUSSong::OPLMUSSong (FILE *file, char *musiccache, int len) +OPLMUSSong::OPLMUSSong (FILE *file, BYTE *musiccache, int len) { int samples = int(OPL_SAMPLE_RATE / 14); diff --git a/src/sound/music_spc.cpp b/src/sound/music_spc.cpp deleted file mode 100644 index fc5d2dc30..000000000 --- a/src/sound/music_spc.cpp +++ /dev/null @@ -1,340 +0,0 @@ -/* -** music_spc.cpp -** SPC codec for FMOD Ex, using snes_spc for decoding. -** -**--------------------------------------------------------------------------- -** Copyright 2008 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -// HEADER FILES ------------------------------------------------------------ - -#include "templates.h" -#include "c_cvars.h" -#include "SNES_SPC.h" -#include "SPC_Filter.h" -#include "fmod_wrap.h" -#include "doomdef.h" -#include "m_swap.h" -#include "m_fixed.h" - -// MACROS ------------------------------------------------------------------ - -// TYPES ------------------------------------------------------------------- - -struct XID6Tag -{ - BYTE ID; - BYTE Type; - WORD Value; -}; - -struct SPCCodecData -{ - SNES_SPC *SPC; - SPC_Filter *Filter; - FMOD_CODEC_WAVEFORMAT WaveFormat; - bool bPlayed; -}; - -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -FMOD_RESULT SPC_CreateCodec(FMOD::System *sys); - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -static FMOD_RESULT SPC_DoOpen(FMOD_CODEC_STATE *codec, SPCCodecData *userdata); -static FMOD_RESULT F_CALLBACK SPC_Open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo); -static FMOD_RESULT F_CALLBACK SPC_Close(FMOD_CODEC_STATE *codec); -static FMOD_RESULT F_CALLBACK SPC_Read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read); -static FMOD_RESULT F_CALLBACK SPC_SetPosition(FMOD_CODEC_STATE *codec_state, int subsound, unsigned int position, FMOD_TIMEUNIT postype); - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -EXTERN_CVAR (Int, snd_samplerate) - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -CVAR (Float, spc_amp, 1.875f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static FMOD_CODEC_DESCRIPTION SPCCodecDescription = { - "Super Nintendo SPC File", - 0x00000001, - 1, - FMOD_TIMEUNIT_MS | FMOD_TIMEUNIT_PCM, - SPC_Open, - SPC_Close, - SPC_Read, - NULL, - SPC_SetPosition, - NULL, - NULL, - NULL -}; - -// CODE -------------------------------------------------------------------- - -//========================================================================== -// -// SPC_CreateCodec -// -//========================================================================== - -FMOD_RESULT SPC_CreateCodec(FMOD::System *sys) -{ - return sys->createCodec(&SPCCodecDescription); -} - -//========================================================================== -// -// SPC_Open -// -//========================================================================== - -static FMOD_RESULT SPC_DoOpen(FMOD_CODEC_STATE *codec, SPCCodecData *userdata) -{ - FMOD_RESULT result; - char spcfile[SNES_SPC::spc_file_size]; - unsigned int bytesread; - SNES_SPC *spc; - SPC_Filter *filter; - - if (codec->filesize < 66048) - { // Anything smaller than this is definitely not a (valid) SPC. - return FMOD_ERR_FORMAT; - } - result = codec->fileseek(codec->filehandle, 0, 0); - if (result != FMOD_OK) - { - return result; - } - // Check the signature. - result = codec->fileread(codec->filehandle, spcfile, SNES_SPC::signature_size, &bytesread, NULL); - if (result != FMOD_OK || bytesread != SNES_SPC::signature_size || - memcmp(spcfile, SNES_SPC::signature, SNES_SPC::signature_size) != 0) - { - result = FMOD_ERR_FORMAT; - // If the length is right and this is just a different version, try - // to pretend it's the current one. - if (bytesread == SNES_SPC::signature_size) - { - memcpy(spcfile + 28, SNES_SPC::signature + 28, 7); - if (memcmp(spcfile, SNES_SPC::signature, SNES_SPC::signature_size) == 0) - { - result = FMOD_OK; - } - } - } - if (result != FMOD_OK) - { - return result; - } - // Load the rest of the file. - result = codec->fileread(codec->filehandle, spcfile + SNES_SPC::signature_size, - SNES_SPC::spc_file_size - SNES_SPC::signature_size, &bytesread, NULL); - if (result != FMOD_OK || bytesread < SNES_SPC::spc_min_file_size - SNES_SPC::signature_size) - { - return FMOD_ERR_FILE_BAD; - } - bytesread += SNES_SPC::signature_size; - - // Create the SPC. - spc = (userdata != NULL) ? userdata->SPC : new SNES_SPC; - spc->init(); - if (spc->load_spc(spcfile, bytesread) != NULL) - { - if (userdata != NULL) - { - delete spc; - } - return FMOD_ERR_FILE_BAD; - } - spc->clear_echo(); - - // Create the filter. - filter = (userdata != NULL) ? userdata->Filter : new SPC_Filter; - filter->set_gain(int(SPC_Filter::gain_unit * spc_amp)); - - // Search for amplification tag in extended ID666 info. - if (codec->filesize > SNES_SPC::spc_file_size + 8 && bytesread == SNES_SPC::spc_file_size) - { - DWORD id; - - result = codec->fileread(codec->filehandle, &id, 4, &bytesread, NULL); - if (result == FMOD_OK && bytesread == 4 && id == MAKE_ID('x','i','d','6')) - { - DWORD size; - - result = codec->fileread(codec->filehandle, &size, 4, &bytesread, NULL); - if (result == FMOD_OK && bytesread == 4) - { - size = LittleLong(size); - DWORD pos = SNES_SPC::spc_file_size + 8; - - while (pos < size) - { - XID6Tag tag; - - result = codec->fileread(codec->filehandle, &tag, 4, &bytesread, NULL); - if (result != FMOD_OK || bytesread != 4) - { - break; - } - if (tag.Type == 0) - { - // Don't care about these - } - else - { - if ((pos += LittleShort(tag.Value)) <= size) - { - if (tag.Type == 4 && tag.ID == 0x36) - { - DWORD amp; - result = codec->fileread(codec->filehandle, &, 4, &bytesread, NULL); - if (result == FMOD_OK && bytesread == 4) - { - filter->set_gain(LittleLong(amp) >> 8); - } - break; - } - } - if (FMOD_OK != codec->fileseek(codec->filehandle, pos, NULL)) - { - break; - } - } - } - } - } - } - if (userdata == NULL) - { - userdata = new SPCCodecData; - userdata->SPC = spc; - userdata->Filter = filter; - memset(&userdata->WaveFormat, 0, sizeof(userdata->WaveFormat)); - userdata->WaveFormat.format = FMOD_SOUND_FORMAT_PCM16; - userdata->WaveFormat.channels = 2; - userdata->WaveFormat.frequency = SNES_SPC::sample_rate; - userdata->WaveFormat.lengthbytes = SNES_SPC::spc_file_size; - userdata->WaveFormat.lengthpcm = ~0u; - userdata->WaveFormat.blockalign = 4; - codec->numsubsounds = 0; - codec->waveformat = &userdata->WaveFormat; - codec->plugindata = userdata; - } - userdata->bPlayed = false; - return FMOD_OK; -} - -//========================================================================== -// -// SPC_Open -// -//========================================================================== - -static FMOD_RESULT F_CALLBACK SPC_Open(FMOD_CODEC_STATE *codec, FMOD_MODE usermode, FMOD_CREATESOUNDEXINFO *userexinfo) -{ - return SPC_DoOpen(codec, NULL); -} - -//========================================================================== -// -// SPC_Close -// -//========================================================================== - -static FMOD_RESULT F_CALLBACK SPC_Close(FMOD_CODEC_STATE *codec) -{ - SPCCodecData *data = (SPCCodecData *)codec->plugindata; - if (data != NULL) - { - delete data->Filter; - delete data->SPC; - delete data; - } - return FMOD_OK; -} - -//========================================================================== -// -// SPC_Read -// -//========================================================================== - -static FMOD_RESULT F_CALLBACK SPC_Read(FMOD_CODEC_STATE *codec, void *buffer, unsigned int size, unsigned int *read) -{ - SPCCodecData *data = (SPCCodecData *)codec->plugindata; - if (read != NULL) - { - *read = size; - } - data->bPlayed = true; - if (data->SPC->play(size >> 1, (short *)buffer) != NULL) - { - return FMOD_ERR_PLUGIN; - } - data->Filter->run((short *)buffer, size >> 1); - return FMOD_OK; -} - -static FMOD_RESULT F_CALLBACK SPC_SetPosition(FMOD_CODEC_STATE *codec, int subsound, unsigned int position, FMOD_TIMEUNIT postype) -{ - SPCCodecData *data = (SPCCodecData *)codec->plugindata; - FMOD_RESULT result; - - if (data->bPlayed) - { // Must reload the file after it's already played something. - result = SPC_DoOpen(codec, data); - if (result != FMOD_OK) - { - return result; - } - } - if (position != 0) - { - if (postype == FMOD_TIMEUNIT_PCM) - { - data->SPC->skip(position * 4); - } - else if (postype == FMOD_TIMEUNIT_MS) - { - data->SPC->skip(Scale(position, SNES_SPC::sample_rate, 1000) * 4); - } - else - { - return FMOD_ERR_UNSUPPORTED; - } - } - return FMOD_OK; -} diff --git a/src/win32/i_keyboard.cpp b/src/win32/i_keyboard.cpp index fa449548e..4669ad359 100644 --- a/src/win32/i_keyboard.cpp +++ b/src/win32/i_keyboard.cpp @@ -21,6 +21,11 @@ #define DINPUT_BUFFERSIZE 32 +// Hi, w32api! +#ifndef GET_RAWINPUT_CODE_WPARAM +#define GET_RAWINPUT_CODE_WPARAM(wParam) ((wParam) & 0xff) +#endif + // TYPES ------------------------------------------------------------------- class FDInputKeyboard : public FKeyboard diff --git a/zdoom.sln b/zdoom.sln index 0e0321eb5..0f4d541a6 100644 --- a/zdoom.sln +++ b/zdoom.sln @@ -1,19 +1,19 @@ Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual C++ Express 2005 +# Visual Studio 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zdoom", "zdoom.vcproj", "{8049475B-5C87-46F9-9358-635218A4EF18}" ProjectSection(ProjectDependencies) = postProject - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88} = {E83FD370-2E72-4D4C-9427-FF9D9DED1E88} - {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} - {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} = {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} - {AC3F5340-40CB-4C3A-8AA7-CB7158DB4466} = {AC3F5340-40CB-4C3A-8AA7-CB7158DB4466} - {8997289F-10BF-4678-8BAA-3BB509C84953} = {8997289F-10BF-4678-8BAA-3BB509C84953} - {B68E0ABF-B627-48A3-A92F-D8F827A75054} = {B68E0ABF-B627-48A3-A92F-D8F827A75054} - {1D179D4B-F008-431B-8C72-111F8372584F} = {1D179D4B-F008-431B-8C72-111F8372584F} - {DA47396F-60C1-4BDE-A977-7F7DE461CF77} = {DA47396F-60C1-4BDE-A977-7F7DE461CF77} - {0F80ACBF-460E-44F0-B28E-B3272D1774A7} = {0F80ACBF-460E-44F0-B28E-B3272D1774A7} - {667D2EE7-C357-49E2-9BAB-0A4A45F0F76E} = {667D2EE7-C357-49E2-9BAB-0A4A45F0F76E} - {6077B7D6-349F-4077-B552-3BC302EF5859} = {6077B7D6-349F-4077-B552-3BC302EF5859} + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96} = {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96} {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} = {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} + {6077B7D6-349F-4077-B552-3BC302EF5859} = {6077B7D6-349F-4077-B552-3BC302EF5859} + {667D2EE7-C357-49E2-9BAB-0A4A45F0F76E} = {667D2EE7-C357-49E2-9BAB-0A4A45F0F76E} + {0F80ACBF-460E-44F0-B28E-B3272D1774A7} = {0F80ACBF-460E-44F0-B28E-B3272D1774A7} + {DA47396F-60C1-4BDE-A977-7F7DE461CF77} = {DA47396F-60C1-4BDE-A977-7F7DE461CF77} + {1D179D4B-F008-431B-8C72-111F8372584F} = {1D179D4B-F008-431B-8C72-111F8372584F} + {B68E0ABF-B627-48A3-A92F-D8F827A75054} = {B68E0ABF-B627-48A3-A92F-D8F827A75054} + {8997289F-10BF-4678-8BAA-3BB509C84953} = {8997289F-10BF-4678-8BAA-3BB509C84953} + {AC3F5340-40CB-4C3A-8AA7-CB7158DB4466} = {AC3F5340-40CB-4C3A-8AA7-CB7158DB4466} + {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} = {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} + {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib\zlib.vcproj", "{F9D9E7D4-E1A2-4866-9E85-B1B14137EE63}" @@ -33,23 +33,23 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpeg-6b", "jpeg-6b\jpeg-6b. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fixrtext", "tools\fixrtext\fixrtext.vcproj", "{DA47396F-60C1-4BDE-A977-7F7DE461CF77}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "snes_spc", "snes_spc\snes_spc.vcproj", "{E83FD370-2E72-4D4C-9427-FF9D9DED1E88}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dumb_static", "dumb\vc6\dumb_static\dumb_static.vcproj", "{8997289F-10BF-4678-8BAA-3BB509C84953}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gdtoa", "gdtoa\gdtoa.vcproj", "{B68E0ABF-B627-48A3-A92F-D8F827A75054}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zipdir", "tools\zipdir\zipdir.vcproj", "{24A19C02-F041-4AB0-A1A1-02E1E88EDBD3}" ProjectSection(ProjectDependencies) = postProject - {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} - {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} = {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} = {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4} + {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} = {F9D9E7D4-E1A2-4866-9E85-B1B14137EE63} + {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} = {6EB27E78-7C7A-4F08-8E19-957E8EB3A20F} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lzmalib", "lzma\lzmalib.vcproj", "{6EB27E78-7C7A-4F08-8E19-957E8EB3A20F}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bzip2", "bzip2\bzip2.vcproj", "{A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "game-music-emu", "game-music-emu\game-music-emu.vcproj", "{9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -122,14 +122,6 @@ Global {DA47396F-60C1-4BDE-A977-7F7DE461CF77}.Release|Win32.Build.0 = Release|Win32 {DA47396F-60C1-4BDE-A977-7F7DE461CF77}.Release|x64.ActiveCfg = Release|Win32 {DA47396F-60C1-4BDE-A977-7F7DE461CF77}.Release|x64.Build.0 = Release|Win32 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Debug|Win32.ActiveCfg = Release|Win32 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Debug|Win32.Build.0 = Release|Win32 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Debug|x64.ActiveCfg = Debug|x64 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Debug|x64.Build.0 = Debug|x64 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Release|Win32.ActiveCfg = Release|Win32 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Release|Win32.Build.0 = Release|Win32 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Release|x64.ActiveCfg = Release|x64 - {E83FD370-2E72-4D4C-9427-FF9D9DED1E88}.Release|x64.Build.0 = Release|x64 {8997289F-10BF-4678-8BAA-3BB509C84953}.Debug|Win32.ActiveCfg = Debug|Win32 {8997289F-10BF-4678-8BAA-3BB509C84953}.Debug|Win32.Build.0 = Debug|Win32 {8997289F-10BF-4678-8BAA-3BB509C84953}.Debug|x64.ActiveCfg = Debug|x64 @@ -170,6 +162,12 @@ Global {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}.Release|Win32.Build.0 = Release|Win32 {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}.Release|x64.ActiveCfg = Release|x64 {A7DE5C73-D623-4118-A48A-BDFD1FAE97D4}.Release|x64.Build.0 = Release|x64 + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}.Debug|Win32.ActiveCfg = Release|Win32 + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}.Debug|Win32.Build.0 = Release|Win32 + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}.Debug|x64.ActiveCfg = Release|x64 + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}.Release|Win32.ActiveCfg = Release|Win32 + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}.Release|Win32.Build.0 = Release|Win32 + {9B465A9E-E5C7-4577-B559-3CA2F7AE7D96}.Release|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/zdoom.vcproj b/zdoom.vcproj index 9b4d9db96..0edfb8349 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -1,7 +1,7 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - @@ -1864,6 +1856,14 @@ Outputs="$(IntDir)/$(InputName).obj" /> + + + @@ -2041,6 +2041,14 @@ Outputs="$(IntDir)\$(InputName).obj" /> + + + @@ -2051,14 +2059,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - + + + - - - + + + @@ -5363,14 +5371,6 @@ AdditionalIncludeDirectories="src\win32;$(NoInherit)" /> - - - @@ -5429,6 +5429,10 @@ RelativePath=".\src\sound\music_dumb.cpp" > + + @@ -5461,10 +5465,6 @@ RelativePath=".\src\oplsynth\music_opldumper_mididevice.cpp" > - - @@ -5649,7 +5649,7 @@ />