PageRenderTime 60ms CodeModel.GetById 28ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 0ms

/gme/Gym_Emu.cpp

http://game-music-emu.googlecode.com/
C++ | 380 lines | 288 code | 68 blank | 24 comment | 64 complexity | 72903e598c85ac23481635147fcc32bd MD5 | raw file
  1// Game_Music_Emu 0.5.5. http://www.slack.net/~ant/
  2
  3#include "Gym_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
 21double const min_tempo = 0.25;
 22double const oversample_factor = 5 / 3.0;
 23double const fm_gain = 3.0;
 24
 25const long base_clock = 53700300;
 26const long clock_rate = base_clock / 15;
 27
 28Gym_Emu::Gym_Emu()
 29{
 30	data = 0;
 31	pos  = 0;
 32	set_type( gme_gym_type );
 33	
 34	static const char* const names [] = {
 35		"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
 36	};
 37	set_voice_names( names );
 38	set_silence_lookahead( 1 ); // tracks should already be trimmed
 39}
 40
 41Gym_Emu::~Gym_Emu() { }
 42
 43// Track info
 44
 45static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out )
 46{
 47	if ( !memcmp( h.tag, "GYMX", 4 ) )
 48	{
 49		length = length * 50 / 3; // 1000 / 60
 50		long loop = get_le32( h.loop_start );
 51		if ( loop )
 52		{
 53			out->intro_length = loop * 50 / 3;
 54			out->loop_length  = length - out->intro_length;
 55		}
 56		else
 57		{
 58			out->length = length;
 59			out->intro_length = length; // make it clear that track is no longer than length
 60			out->loop_length = 0;
 61		}
 62		
 63		// more stupidity where the field should have been left
 64		if ( strcmp( h.song, "Unknown Song" ) )
 65			GME_COPY_FIELD( h, out, song );
 66		
 67		if ( strcmp( h.game, "Unknown Game" ) )
 68			GME_COPY_FIELD( h, out, game );
 69		
 70		if ( strcmp( h.copyright, "Unknown Publisher" ) )
 71			GME_COPY_FIELD( h, out, copyright );
 72		
 73		if ( strcmp( h.dumper, "Unknown Person" ) )
 74			GME_COPY_FIELD( h, out, dumper );
 75		
 76		if ( strcmp( h.comment, "Header added by YMAMP" ) )
 77			GME_COPY_FIELD( h, out, comment );
 78	}
 79}
 80
 81blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const
 82{
 83	get_gym_info( header_, track_length(), out );
 84	return 0;
 85}
 86
 87static long gym_track_length( byte const* p, byte const* end )
 88{
 89	long time = 0;
 90	while ( p < end )
 91	{
 92		switch ( *p++ )
 93		{
 94			case 0:
 95				time++;
 96				break;
 97			
 98			case 1:
 99			case 2:
100				p += 2;
101				break;
102			
103			case 3:
104				p += 1;
105				break;
106		}
107	}
108	return time;
109}
110
111long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); }
112
113static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 )
114{
115	if ( size < 4 )
116		return gme_wrong_file_type;
117	
118	if ( memcmp( in, "GYMX", 4 ) == 0 )
119	{
120		if ( size < Gym_Emu::header_size + 1 )
121			return gme_wrong_file_type;
122		
123		if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 )
124			return "Packed GYM file not supported";
125		
126		if ( data_offset )
127			*data_offset = Gym_Emu::header_size;
128	}
129	else if ( *in > 3 )
130	{
131		return gme_wrong_file_type;
132	}
133	
134	return 0;
135}
136
137struct Gym_File : Gme_Info_
138{
139	byte const* file_begin;
140	byte const* file_end;
141	int data_offset;
142	
143	Gym_File() { set_type( gme_gym_type ); }
144	
145	blargg_err_t load_mem_( byte const* in, long size )
146	{
147		file_begin = in;
148		file_end   = in + size;
149		data_offset = 0;
150		return check_header( in, size, &data_offset );
151	}
152	
153	blargg_err_t track_info_( track_info_t* out, int ) const
154	{
155		long length = gym_track_length( &file_begin [data_offset], file_end );
156		get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out );
157		return 0;
158	}
159};
160
161static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
162static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; }
163
164static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 };
165gme_type_t const gme_gym_type = &gme_gym_type_;
166
167// Setup
168
169blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate )
170{
171	blip_eq_t eq( -32, 8000, sample_rate );
172	apu.treble_eq( eq );
173	dac_synth.treble_eq( eq );
174	apu.volume( 0.135 * fm_gain * gain() );
175	dac_synth.volume( 0.125 / 256 * fm_gain * gain() );
176	double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() );
177	fm_sample_rate = sample_rate * factor;
178	
179	RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) );
180	blip_buf.clock_rate( clock_rate );
181	
182	RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) );
183	RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) );
184	
185	return 0;
186}
187
188void Gym_Emu::set_tempo_( double t )
189{
190	if ( t < min_tempo )
191	{
192		set_tempo( min_tempo );
193		return;
194	}
195	
196	if ( blip_buf.sample_rate() )
197	{
198		clocks_per_frame = long (clock_rate / 60 / tempo());
199		Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) );
200	}
201}
202
203void Gym_Emu::mute_voices_( int mask )
204{
205	Music_Emu::mute_voices_( mask );
206	fm.mute_voices( mask );
207	dac_muted = (mask & 0x40) != 0;
208	apu.output( (mask & 0x80) ? 0 : &blip_buf );
209}
210
211blargg_err_t Gym_Emu::load_mem_( byte const* in, long size )
212{
213	assert( offsetof (header_t,packed [4]) == header_size );
214	int offset = 0;
215	RETURN_ERR( check_header( in, size, &offset ) );
216	set_voice_count( 8 );
217	
218	data     = in + offset;
219	data_end = in + size;
220	loop_begin = 0;
221	
222	if ( offset )
223		header_ = *(header_t const*) in;
224	else
225		memset( &header_, 0, sizeof header_ );
226	
227	return 0;
228}
229
230// Emulation
231
232blargg_err_t Gym_Emu::start_track_( int track )
233{
234	RETURN_ERR( Music_Emu::start_track_( track ) );
235	
236	pos         = data;
237	loop_remain = get_le32( header_.loop_start );
238	
239	prev_dac_count = 0;
240	dac_enabled    = false;
241	dac_amp        = -1;
242	
243	fm.reset();
244	apu.reset();
245	blip_buf.clear();
246	Dual_Resampler::clear();
247	return 0;
248}
249
250void Gym_Emu::run_dac( int dac_count )
251{
252	// Guess beginning and end of sample and adjust rate and buffer position accordingly.
253	
254	// count dac samples in next frame
255	int next_dac_count = 0;
256	const byte* p = this->pos;
257	int cmd;
258	while ( (cmd = *p++) != 0 )
259	{
260		int data = *p++;
261		if ( cmd <= 2 )
262			++p;
263		if ( cmd == 1 && data == 0x2A )
264			next_dac_count++;
265	}
266	
267	// detect beginning and end of sample
268	int rate_count = dac_count;
269	int start = 0;
270	if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count )
271	{
272		rate_count = next_dac_count;
273		start = next_dac_count - dac_count;
274	}
275	else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count )
276	{
277		rate_count = prev_dac_count;
278	}
279	
280	// Evenly space samples within buffer section being used
281	blip_resampled_time_t period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count;
282	
283	blip_resampled_time_t time = blip_buf.resampled_time( 0 ) +
284			period * start + (period >> 1);
285	
286	int dac_amp = this->dac_amp;
287	if ( dac_amp < 0 )
288		dac_amp = dac_buf [0];
289	
290	for ( int i = 0; i < dac_count; i++ )
291	{
292		int delta = dac_buf [i] - dac_amp;
293		dac_amp += delta;
294		dac_synth.offset_resampled( time, delta, &blip_buf );
295		time += period;
296	}
297	this->dac_amp = dac_amp;
298}
299
300void Gym_Emu::parse_frame()
301{
302	int dac_count = 0;
303	const byte* pos = this->pos;
304	
305	if ( loop_remain && !--loop_remain )
306		loop_begin = pos; // find loop on first time through sequence
307	
308	int cmd;
309	while ( (cmd = *pos++) != 0 )
310	{
311		int data = *pos++;
312		if ( cmd == 1 )
313		{
314			int data2 = *pos++;
315			if ( data != 0x2A )
316			{
317				if ( data == 0x2B )
318					dac_enabled = (data2 & 0x80) != 0;
319				
320				fm.write0( data, data2 );
321			}
322			else if ( dac_count < (int) sizeof dac_buf )
323			{
324				dac_buf [dac_count] = data2;
325				dac_count += dac_enabled;
326			}
327		}
328		else if ( cmd == 2 )
329		{
330			fm.write1( data, *pos++ );
331		}
332		else if ( cmd == 3 )
333		{
334			apu.write_data( 0, data );
335		}
336		else
337		{
338			// to do: many GYM streams are full of errors, and error count should
339			// reflect cases where music is really having problems
340			//log_error(); 
341			--pos; // put data back
342		}
343	}
344	
345	// loop
346	if ( pos >= data_end )
347	{
348		check( pos == data_end );
349		
350		if ( loop_begin )
351			pos = loop_begin;
352		else
353			set_track_ended();
354	}
355	this->pos = pos;
356	
357	// dac
358	if ( dac_count && !dac_muted )
359		run_dac( dac_count );
360	prev_dac_count = dac_count;
361}
362
363int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf )
364{
365	if ( !track_ended() )
366		parse_frame();
367	
368	apu.end_frame( blip_time );
369	
370	memset( buf, 0, sample_count * sizeof *buf );
371	fm.run( sample_count >> 1, buf );
372	
373	return sample_count;
374}
375
376blargg_err_t Gym_Emu::play_( long count, sample_t* out )
377{
378	Dual_Resampler::dual_play( count, out, blip_buf );
379	return 0;
380}