diff options
Diffstat (limited to 'plugins/gme/game-music-emu-0.6pre/gme/Kss_Emu.cpp')
-rw-r--r-- | plugins/gme/game-music-emu-0.6pre/gme/Kss_Emu.cpp | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/plugins/gme/game-music-emu-0.6pre/gme/Kss_Emu.cpp b/plugins/gme/game-music-emu-0.6pre/gme/Kss_Emu.cpp new file mode 100644 index 00000000..9eac0756 --- /dev/null +++ b/plugins/gme/game-music-emu-0.6pre/gme/Kss_Emu.cpp @@ -0,0 +1,470 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "Kss_Emu.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006-2009 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 IF_PTR( ptr ) if ( ptr ) (ptr) + +int const clock_rate = 3579545; + +#define FOR_EACH_APU( macro )\ +{\ + macro( sms.psg );\ + macro( sms.fm );\ + macro( msx.psg );\ + macro( msx.scc );\ + macro( msx.music );\ + macro( msx.audio );\ +} + +Kss_Emu::Kss_Emu() : + core( this ) +{ + #define ACTION( apu ) { core.apu = NULL; } + FOR_EACH_APU( ACTION ); + #undef ACTION + + set_type( gme_kss_type ); +} + +Kss_Emu::~Kss_Emu() +{ + unload(); +} + +inline void Kss_Emu::Core::unload() +{ + #define ACTION( ptr ) { delete (ptr); (ptr) = 0; } + FOR_EACH_APU( ACTION ); + #undef ACTION +} + +void Kss_Emu::unload() +{ + core.unload(); + Classic_Emu::unload(); +} + +// Track info + +static void copy_kss_fields( Kss_Core::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"; + + if ( h.device_flags & 0x01 ) + system = "Sega Mark III"; + } + else + { + if ( h.device_flags & 0x09 ) + system = "MSX + FM Sound"; + } + 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 ); +// TODO: remove +//if ( msx.music ) strcpy( out->system, "msxmusic" ); +//if ( msx.audio ) strcpy( out->system, "msxaudio" ); +//if ( sms.fm ) strcpy( out->system, "fmunit" ); + return blargg_ok; +} + +static blargg_err_t check_kss_header( void const* header ) +{ + if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) + return blargg_err_file_type; + + return blargg_ok; +} + +struct Kss_File : Gme_Info_ +{ + Kss_Emu::header_t header_; + + Kss_File() { set_type( gme_kss_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + memset( &header_, 0, sizeof header_ ); + blargg_err_t err = in.read( &header_, header_.size ); + if ( err ) + return (err == blargg_err_file_eof ? blargg_err_file_type : err); + + if ( header_.tag [3] == 'X' && header_.extra_header == 0x10 ) + set_track_count( get_le16( header_.last_track ) + 1 ); + + return check_kss_header( &header_ ); + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_kss_fields( header_, out ); + return blargg_ok; + } +}; + +static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; } +static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; } + +gme_type_t_ const gme_kss_type [1] = {{ + "MSX", + 256, + &new_kss_emu, + &new_kss_file, + "KSS", + 0x03 +}}; + +// Setup + +void Kss_Emu::Core::update_gain_() +{ + double g = emu.gain(); + if ( msx.music || msx.audio || sms.fm ) + { + g *= 0.3; + } + else + { + g *= 1.2; + if ( scc_accessed ) + g *= 1.4; + } + + #define ACTION( apu ) IF_PTR( apu )->volume( g ) + FOR_EACH_APU( ACTION ); + #undef ACTION +} + +static blargg_err_t new_opl_apu( Opl_Apu::type_t type, Opl_Apu** out ) +{ + check( !*out ); + CHECK_ALLOC( *out = BLARGG_NEW( Opl_Apu ) ); + blip_time_t const period = 72; + int const rate = clock_rate / period; + return (*out)->init( rate * period, rate, period, type ); +} + +blargg_err_t Kss_Emu::load_( Data_Reader& in ) +{ + RETURN_ERR( core.load( in ) ); + set_warning( core.warning() ); + + set_track_count( get_le16( header().last_track ) + 1 ); + + core.scc_enabled = false; + if ( header().device_flags & 0x02 ) // Sega Master System + { + int const osc_count = Sms_Apu::osc_count + Opl_Apu::osc_count; + static const char* const names [osc_count] = { + "Square 1", "Square 2", "Square 3", "Noise", "FM" + }; + set_voice_names( names ); + + static int const types [osc_count] = { + wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0 + }; + set_voice_types( types ); + + // sms.psg + set_voice_count( Sms_Apu::osc_count ); + check( !core.sms.psg ); + CHECK_ALLOC( core.sms.psg = BLARGG_NEW Sms_Apu ); + + // sms.fm + if ( header().device_flags & 0x01 ) + { + set_voice_count( osc_count ); + RETURN_ERR( new_opl_apu( Opl_Apu::type_smsfmunit, &core.sms.fm ) ); + } + + } + else // MSX + { + int const osc_count = Ay_Apu::osc_count + Opl_Apu::osc_count; + static const char* const names [osc_count] = { + "Square 1", "Square 2", "Square 3", "FM" + }; + set_voice_names( names ); + + static int const types [osc_count] = { + wave_type+1, wave_type+3, wave_type+2, wave_type+0 + }; + set_voice_types( types ); + + // msx.psg + set_voice_count( Ay_Apu::osc_count ); + check( !core.msx.psg ); + CHECK_ALLOC( core.msx.psg = BLARGG_NEW Ay_Apu ); + + if ( header().device_flags & 0x10 ) + set_warning( "MSX stereo not supported" ); + + // msx.music + if ( header().device_flags & 0x01 ) + { + set_voice_count( osc_count ); + RETURN_ERR( new_opl_apu( Opl_Apu::type_msxmusic, &core.msx.music ) ); + } + + // msx.audio + if ( header().device_flags & 0x08 ) + { + set_voice_count( osc_count ); + RETURN_ERR( new_opl_apu( Opl_Apu::type_msxaudio, &core.msx.audio ) ); + } + + if ( !(header().device_flags & 0x80) ) + { + if ( !(header().device_flags & 0x84) ) + core.scc_enabled = core.scc_enabled_true; + + // msx.scc + check( !core.msx.scc ); + CHECK_ALLOC( core.msx.scc = BLARGG_NEW Scc_Apu ); + + int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count; + 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+1, wave_type+3, wave_type+2, + wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7, + }; + set_voice_types( types ); + + set_voice_count( osc_count ); + } + } + + set_silence_lookahead( 6 ); + if ( core.sms.fm || core.msx.music || core.msx.audio ) + { + if ( !Opl_Apu::supported() ) + set_warning( "FM sound not supported" ); + else + set_silence_lookahead( 3 ); // Opl_Apu is really slow + } + + return setup_buffer( ::clock_rate ); +} + +void Kss_Emu::update_eq( blip_eq_t const& eq ) +{ + #define ACTION( apu ) IF_PTR( core.apu )->treble_eq( eq ) + FOR_EACH_APU( ACTION ); + #undef ACTION +} + +void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + if ( core.sms.psg ) // Sega Master System + { + i -= core.sms.psg->osc_count; + if ( i < 0 ) + { + core.sms.psg->set_output( i + core.sms.psg->osc_count, center, left, right ); + return; + } + + if ( core.sms.fm && i < core.sms.fm->osc_count ) + core.sms.fm->set_output( i, center, NULL, NULL ); + } + else if ( core.msx.psg ) // MSX + { + i -= core.msx.psg->osc_count; + if ( i < 0 ) + { + core.msx.psg->set_output( i + core.msx.psg->osc_count, center ); + return; + } + + if ( core.msx.scc && i < core.msx.scc->osc_count ) core.msx.scc ->set_output( i, center ); + if ( core.msx.music && i < core.msx.music->osc_count ) core.msx.music->set_output( i, center, NULL, NULL ); + if ( core.msx.audio && i < core.msx.audio->osc_count ) core.msx.audio->set_output( i, center, NULL, NULL ); + } +} + +void Kss_Emu::set_tempo_( double t ) +{ + int period = (header().device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60); + core.set_play_period( (Kss_Core::time_t) (period / t) ); +} + +blargg_err_t Kss_Emu::start_track_( int track ) +{ + RETURN_ERR( Classic_Emu::start_track_( track ) ); + + #define ACTION( apu ) IF_PTR( core.apu )->reset() + FOR_EACH_APU( ACTION ); + #undef ACTION + + core.scc_accessed = false; + core.update_gain_(); + + return core.start_track( track ); +} + +void Kss_Emu::Core::cpu_write_( addr_t addr, int data ) +{ + // TODO: SCC+ support + + data &= 0xFF; + switch ( addr ) + { + case 0x9000: + set_bank( 0, data ); + return; + + case 0xB000: + set_bank( 1, data ); + return; + + case 0xBFFE: // selects between mapping areas (we just always enable both) + if ( data == 0 || data == 0x20 ) + return; + } + + int scc_addr = (addr & 0xDFFF) - 0x9800; + if ( (unsigned) scc_addr < 0xB0 && msx.scc ) + { + scc_accessed = true; + //if ( (unsigned) (scc_addr - 0x90) < 0x10 ) + // scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F + if ( scc_addr < Scc_Apu::reg_count ) + msx.scc->write( cpu.time(), addr, data ); + return; + } + + dprintf( "LD ($%04X),$%02X\n", addr, data ); +} + +void Kss_Emu::Core::cpu_write( addr_t addr, int data ) +{ + *cpu.write( addr ) = data; + if ( (addr & scc_enabled) == 0x8000 ) + cpu_write_( addr, data ); +} + +void Kss_Emu::Core::cpu_out( time_t time, addr_t addr, int data ) +{ + data &= 0xFF; + switch ( addr & 0xFF ) + { + case 0xA0: + if ( msx.psg ) + msx.psg->write_addr( data ); + return; + + case 0xA1: + if ( msx.psg ) + msx.psg->write_data( time, data ); + return; + + case 0x06: + if ( sms.psg && (header().device_flags & 0x04) ) + { + sms.psg->write_ggstereo( time, data ); + return; + } + break; + + case 0x7E: + case 0x7F: + if ( sms.psg ) + { + sms.psg->write_data( time, data ); + return; + } + break; + + #define OPL_WRITE_HANDLER( base, opl )\ + case base : if ( opl ) { opl->write_addr( data ); return; } break;\ + case base+1: if ( opl ) { opl->write_data( time, data ); return; } break; + + OPL_WRITE_HANDLER( 0x7C, msx.music ) + OPL_WRITE_HANDLER( 0xC0, msx.audio ) + OPL_WRITE_HANDLER( 0xF0, sms.fm ) + + case 0xFE: + set_bank( 0, data ); + return; + + #ifndef NDEBUG + case 0xA8: // PPI + return; + #endif + } + + Kss_Core::cpu_out( time, addr, data ); +} + +int Kss_Emu::Core::cpu_in( time_t time, addr_t addr ) +{ + switch ( addr & 0xFF ) + { + case 0xC0: + case 0xC1: + if ( msx.audio ) + return msx.audio->read( time, addr & 1 ); + break; + + case 0xA2: + if ( msx.psg ) + return msx.psg->read(); + break; + + #ifndef NDEBUG + case 0xA8: // PPI + return 0; + #endif + } + + return Kss_Core::cpu_in( time, addr ); +} + +void Kss_Emu::Core::update_gain() +{ + if ( scc_accessed ) + { + dprintf( "SCC accessed\n" ); + update_gain_(); + } +} + +blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int ) +{ + RETURN_ERR( core.end_frame( duration ) ); + + #define ACTION( apu ) IF_PTR( core.apu )->end_frame( duration ) + FOR_EACH_APU( ACTION ); + #undef ACTION + + return blargg_ok; +} |