summaryrefslogtreecommitdiff
path: root/plugins/gme/game-music-emu-0.6pre/gme/Kss_Emu.cpp
diff options
context:
space:
mode:
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.cpp470
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;
+}