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
9 can redistribute it and/or modify it under the terms of the GNU Lesser
10 General Public License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version. This
12 module is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 details. You should have received a copy of the GNU Lesser General Public
16 License along with this module; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
18
19 #include "blargg_source.h"
20
21 double const min_tempo = 0.25;
22 double const oversample_factor = 5 / 3.0;
23 double const fm_gain = 3.0;
24
25 const long base_clock = 53700300;
26 const long clock_rate = base_clock / 15;
27
Gym_Emu()28 Gym_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
~Gym_Emu()41 Gym_Emu::~Gym_Emu() { }
42
43 // Track info
44
get_gym_info(Gym_Emu::header_t const & h,long length,track_info_t * out)45 static 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
track_info_(track_info_t * out,int) const81 blargg_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
gym_track_length(byte const * p,byte const * end)87 static 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
track_length() const111 long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); }
112
check_header(byte const * in,long size,int * data_offset=0)113 static 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
137 struct Gym_File : Gme_Info_
138 {
139 byte const* file_begin;
140 byte const* file_end;
141 int data_offset;
142
Gym_FileGym_File143 Gym_File() { set_type( gme_gym_type ); }
144
load_mem_Gym_File145 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
track_info_Gym_File153 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
new_gym_emu()161 static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
new_gym_file()162 static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; }
163
164 static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 };
165 gme_type_t const gme_gym_type = &gme_gym_type_;
166
167 // Setup
168
set_sample_rate_(long sample_rate)169 blargg_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
set_tempo_(double t)188 void 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
mute_voices_(int mask)203 void 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
load_mem_(byte const * in,long size)211 blargg_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
start_track_(int track)232 blargg_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
run_dac(int dac_count)250 void 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
parse_frame()300 void 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
play_frame(blip_time_t blip_time,int sample_count,sample_t * buf)363 int 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
play_(long count,sample_t * out)376 blargg_err_t Gym_Emu::play_( long count, sample_t* out )
377 {
378 Dual_Resampler::dual_play( count, out, blip_buf );
379 return 0;
380 }
381