PageRenderTime 34ms CodeModel.GetById 11ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/gme/Gbs_Emu.cpp

http://game-music-emu.googlecode.com/
C++ | 289 lines | 214 code | 55 blank | 20 comment | 25 complexity | 2c087170647d68494540888b215230d1 MD5 | raw file
  1// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/
  2
  3#include "Gbs_Emu.h"
  4
  5#include "blargg_endian.h"
  6#include <string.h>
  7
  8/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
  9can redistribute it and/or modify it under the terms of the GNU Lesser
 10General Public License as published by the Free Software Foundation; either
 11version 2.1 of the License, or (at your option) any later version. This
 12module is distributed in the hope that it will be useful, but WITHOUT ANY
 13WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 14FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 15details. You should have received a copy of the GNU Lesser General Public
 16License along with this module; if not, write to the Free Software Foundation,
 17Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
 18
 19#include "blargg_source.h"
 20
 21Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq   = { -47.0, 2000 };
 22Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = {   0.0,  300 };
 23
 24Gbs_Emu::Gbs_Emu()
 25{
 26	set_type( gme_gbs_type );
 27	
 28	static const char* const names [Gb_Apu::osc_count] = {
 29		"Square 1", "Square 2", "Wave", "Noise"
 30	};
 31	set_voice_names( names );
 32	
 33	static int const types [Gb_Apu::osc_count] = {
 34		wave_type | 1, wave_type | 2, wave_type | 0, mixed_type | 0
 35	};
 36	set_voice_types( types );
 37	
 38	set_silence_lookahead( 6 );
 39	set_max_initial_silence( 21 );
 40	set_gain( 1.2 );
 41	
 42	static equalizer_t const eq = { -1.0, 120 };
 43	set_equalizer( eq );
 44}
 45
 46Gbs_Emu::~Gbs_Emu() { }
 47
 48void Gbs_Emu::unload()
 49{
 50	rom.clear();
 51	Music_Emu::unload();
 52}
 53
 54// Track info
 55
 56static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
 57{
 58	GME_COPY_FIELD( h, out, game );
 59	GME_COPY_FIELD( h, out, author );
 60	GME_COPY_FIELD( h, out, copyright );
 61}
 62
 63blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
 64{
 65	copy_gbs_fields( header_, out );
 66	return 0;
 67}
 68
 69static blargg_err_t check_gbs_header( void const* header )
 70{
 71	if ( memcmp( header, "GBS", 3 ) )
 72		return gme_wrong_file_type;
 73	return 0;
 74}
 75
 76struct Gbs_File : Gme_Info_
 77{
 78	Gbs_Emu::header_t h;
 79	
 80	Gbs_File() { set_type( gme_gbs_type ); }
 81	
 82	blargg_err_t load_( Data_Reader& in )
 83	{
 84		blargg_err_t err = in.read( &h, Gbs_Emu::header_size );
 85		if ( err )
 86			return (err == in.eof_error ? gme_wrong_file_type : err);
 87		
 88		set_track_count( h.track_count );
 89		return check_gbs_header( &h );
 90	}
 91	
 92	blargg_err_t track_info_( track_info_t* out, int ) const
 93	{
 94		copy_gbs_fields( h, out );
 95		return 0;
 96	}
 97};
 98
 99static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
100static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
101
102static gme_type_t_ const gme_gbs_type_ = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 };
103gme_type_t const gme_gbs_type = &gme_gbs_type_;
104
105// Setup
106
107blargg_err_t Gbs_Emu::load_( Data_Reader& in )
108{
109	assert( offsetof (header_t,copyright [32]) == header_size );
110	RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
111	
112	set_track_count( header_.track_count );
113	RETURN_ERR( check_gbs_header( &header_ ) );
114	
115	if ( header_.vers != 1 )
116		set_warning( "Unknown file version" );
117	
118	if ( header_.timer_mode & 0x78 )
119		set_warning( "Invalid timer mode" );
120	
121	unsigned load_addr = get_le16( header_.load_addr );
122	if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
123			load_addr < 0x400 )
124		set_warning( "Invalid load/init/play address" );
125	
126	set_voice_count( Gb_Apu::osc_count );
127	
128	apu.volume( gain() );
129	
130	return setup_buffer( 4194304 );
131}
132
133void Gbs_Emu::update_eq( blip_eq_t const& eq )
134{
135	apu.treble_eq( eq );
136}
137
138void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
139{
140	apu.osc_output( i, c, l, r );
141}
142
143// Emulation
144
145// see gb_cpu_io.h for read/write functions
146
147void Gbs_Emu::set_bank( int n )
148{
149	blargg_long addr = rom.mask_addr( n * (blargg_long) bank_size );
150	if ( addr == 0 && rom.size() > bank_size )
151	{
152		// TODO: what is the correct behavior? Current Game & Watch Gallery
153		// rip requires that this have no effect or set to bank 1.
154		//debug_printf( "Selected ROM bank 0\n" );
155		return;
156		//n = 1;
157	}
158	cpu::map_code( bank_size, bank_size, rom.at_addr( addr ) );
159}
160
161void Gbs_Emu::update_timer()
162{
163	if ( header_.timer_mode & 0x04 )
164	{
165		static byte const rates [4] = { 10, 4, 6, 8 };
166		int shift = rates [ram [hi_page + 7] & 3] - (header_.timer_mode >> 7);
167		play_period = (256L - ram [hi_page + 6]) << shift;
168	}
169	else
170	{
171		play_period = 70224; // 59.73 Hz
172	}
173	if ( tempo() != 1.0 )
174		play_period = blip_time_t (play_period / tempo());
175}
176
177static BOOST::uint8_t const sound_data [Gb_Apu::register_count] = {
178	0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1
179	0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2
180	0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave
181	0x00, 0xFF, 0x00, 0x00, 0xBF, // noise
182	0x77, 0xF3, 0xF1, // vin/volume, status, power mode
183	0, 0, 0, 0, 0, 0, 0, 0, 0, // unused
184	0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, // waveform data
185	0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48
186};
187
188void Gbs_Emu::cpu_jsr( gb_addr_t addr )
189{
190	check( cpu::r.sp == get_le16( header_.stack_ptr ) );
191	cpu::r.pc = addr;
192	cpu_write( --cpu::r.sp, idle_addr >> 8 );
193	cpu_write( --cpu::r.sp, idle_addr&0xFF );
194}
195
196void Gbs_Emu::set_tempo_( double t )
197{
198	apu.set_tempo( t );
199	update_timer();
200}
201
202blargg_err_t Gbs_Emu::start_track_( int track )
203{
204	RETURN_ERR( Classic_Emu::start_track_( track ) );
205	
206	memset( ram, 0, 0x4000 );
207	memset( ram + 0x4000, 0xFF, 0x1F80 );
208	memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
209	ram [hi_page] = 0; // joypad reads back as 0
210	
211	apu.reset();
212	for ( int i = 0; i < (int) sizeof sound_data; i++ )
213		apu.write_register( 0, i + apu.start_addr, sound_data [i] );
214	
215	unsigned load_addr = get_le16( header_.load_addr );
216	rom.set_addr( load_addr );
217	cpu::rst_base = load_addr;
218	
219	cpu::reset( rom.unmapped() );
220	
221	cpu::map_code( ram_addr, 0x10000 - ram_addr, ram );
222	cpu::map_code( 0, bank_size, rom.at_addr( 0 ) );
223	set_bank( rom.size() > bank_size );
224	
225	ram [hi_page + 6] = header_.timer_modulo;
226	ram [hi_page + 7] = header_.timer_mode;
227	update_timer();
228	next_play = play_period;
229	
230	cpu::r.a  = track;
231	cpu::r.pc = idle_addr;
232	cpu::r.sp = get_le16( header_.stack_ptr );
233	cpu_time  = 0;
234	cpu_jsr( get_le16( header_.init_addr ) );
235	
236	return 0;
237}
238
239blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
240{
241	cpu_time = 0;
242	while ( cpu_time < duration )
243	{
244		long count = duration - cpu_time;
245		cpu_time = duration;
246		bool result = cpu::run( count );
247		cpu_time -= cpu::remain();
248		
249		if ( result )
250		{
251			if ( cpu::r.pc == idle_addr )
252			{
253				if ( next_play > duration )
254				{
255					cpu_time = duration;
256					break;
257				}
258				
259				if ( cpu_time < next_play )
260					cpu_time = next_play;
261				next_play += play_period;
262				cpu_jsr( get_le16( header_.play_addr ) );
263				GME_FRAME_HOOK( this );
264				// TODO: handle timer rates different than 60 Hz
265			}
266			else if ( cpu::r.pc > 0xFFFF )
267			{
268				debug_printf( "PC wrapped around\n" );
269				cpu::r.pc &= 0xFFFF;
270			}
271			else
272			{
273				set_warning( "Emulation error (illegal/unsupported instruction)" );
274				debug_printf( "Bad opcode $%.2x at $%.4x\n",
275						(int) *cpu::get_code( cpu::r.pc ), (int) cpu::r.pc );
276				cpu::r.pc = (cpu::r.pc + 1) & 0xFFFF;
277				cpu_time += 6;
278			}
279		}
280	}
281	
282	duration = cpu_time;
283	next_play -= cpu_time;
284	if ( next_play < 0 ) // could go negative if routine is taking too long to return
285		next_play = 0;
286	apu.end_frame( cpu_time );
287	
288	return 0;
289}