Mercurial > audlegacy-plugins
comparison src/console/Sap_Emu.cxx @ 316:fb513e10174e trunk
[svn] - merge libconsole-blargg into mainline libconsole:
+ obsoletes plugins-ugly:sapplug
author | nenolod |
---|---|
date | Thu, 30 Nov 2006 19:54:33 -0800 |
parents | |
children | 230decbfe9be |
comparison
equal
deleted
inserted
replaced
315:2294f3a6f136 | 316:fb513e10174e |
---|---|
1 // Game_Music_Emu 0.5.1. http://www.slack.net/~ant/ | |
2 | |
3 #include "Sap_Emu.h" | |
4 | |
5 #include "blargg_endian.h" | |
6 #include <string.h> | |
7 | |
8 /* Copyright (C) 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 long const base_scanline_period = 114; | |
22 | |
23 Sap_Emu::Sap_Emu() | |
24 { | |
25 set_type( gme_sap_type ); | |
26 | |
27 static const char* const names [Sap_Apu::osc_count * 2] = { | |
28 "Wave 1", "Wave 2", "Wave 3", "Wave 4", | |
29 "Wave 5", "Wave 6", "Wave 7", "Wave 8", | |
30 }; | |
31 set_voice_names( names ); | |
32 | |
33 static int const types [Sap_Apu::osc_count * 2] = { | |
34 wave_type | 1, wave_type | 2, wave_type | 3, wave_type | 0, | |
35 wave_type | 5, wave_type | 6, wave_type | 7, wave_type | 4, | |
36 }; | |
37 set_voice_types( types ); | |
38 set_silence_lookahead( 6 ); | |
39 } | |
40 | |
41 Sap_Emu::~Sap_Emu() { } | |
42 | |
43 // Track info | |
44 | |
45 // Returns 16 or greater if not hex | |
46 inline int from_hex_char( int h ) | |
47 { | |
48 h -= 0x30; | |
49 if ( (unsigned) h > 9 ) | |
50 h = ((h - 0x11) & 0xDF) + 10; | |
51 return h; | |
52 } | |
53 | |
54 static long from_hex( byte const* in ) | |
55 { | |
56 unsigned result = 0; | |
57 for ( int n = 4; n--; ) | |
58 { | |
59 int h = from_hex_char( *in++ ); | |
60 if ( h > 15 ) | |
61 return -1; | |
62 result = result * 0x10 + h; | |
63 } | |
64 return result; | |
65 } | |
66 | |
67 static int from_dec( byte const* in, byte const* end ) | |
68 { | |
69 if ( in >= end ) | |
70 return -1; | |
71 | |
72 int n = 0; | |
73 while ( in < end ) | |
74 { | |
75 int dig = *in++ - '0'; | |
76 if ( (unsigned) dig > 9 ) | |
77 return -1; | |
78 n = n * 10 + dig; | |
79 } | |
80 return n; | |
81 } | |
82 | |
83 static void parse_string( byte const* in, byte const* end, int len, char* out ) | |
84 { | |
85 byte const* start = in; | |
86 if ( *in++ == '\"' ) | |
87 { | |
88 start++; | |
89 while ( in < end && *in != '\"' ) | |
90 in++; | |
91 } | |
92 else | |
93 { | |
94 in = end; | |
95 } | |
96 len = min( len - 1, int (in - start) ); | |
97 out [len] = 0; | |
98 memcpy( out, start, len ); | |
99 } | |
100 | |
101 static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out ) | |
102 { | |
103 out->track_count = 1; | |
104 out->author [0] = 0; | |
105 out->name [0] = 0; | |
106 out->copyright [0] = 0; | |
107 | |
108 if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) ) | |
109 return gme_wrong_file_type; | |
110 | |
111 byte const* file_end = in + size - 5; | |
112 in += 5; | |
113 while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) ) | |
114 { | |
115 byte const* line_end = in; | |
116 while ( line_end < file_end && *line_end != 0x0D ) | |
117 line_end++; | |
118 | |
119 char const* tag = (char const*) in; | |
120 while ( in < line_end && *in > ' ' ) | |
121 in++; | |
122 int tag_len = (char const*) in - tag; | |
123 | |
124 while ( in < line_end && *in <= ' ' ) in++; | |
125 | |
126 if ( tag_len <= 0 ) | |
127 { | |
128 // skip line | |
129 } | |
130 else if ( !strncmp( "INIT", tag, tag_len ) ) | |
131 { | |
132 out->init_addr = from_hex( in ); | |
133 if ( (unsigned long) out->init_addr > 0xFFFF ) | |
134 return "Invalid init address"; | |
135 } | |
136 else if ( !strncmp( "PLAYER", tag, tag_len ) ) | |
137 { | |
138 out->play_addr = from_hex( in ); | |
139 if ( (unsigned long) out->play_addr > 0xFFFF ) | |
140 return "Invalid play address"; | |
141 } | |
142 else if ( !strncmp( "MUSIC", tag, tag_len ) ) | |
143 { | |
144 out->music_addr = from_hex( in ); | |
145 if ( (unsigned long) out->music_addr > 0xFFFF ) | |
146 return "Invalid music address"; | |
147 } | |
148 else if ( !strncmp( "SONGS", tag, tag_len ) ) | |
149 { | |
150 out->track_count = from_dec( in, line_end ); | |
151 if ( out->track_count <= 0 ) | |
152 return "Invalid track count"; | |
153 } | |
154 else if ( !strncmp( "TYPE", tag, tag_len ) ) | |
155 { | |
156 switch ( out->type = *in ) | |
157 { | |
158 case 'C': | |
159 case 'B': | |
160 break; | |
161 | |
162 case 'D': | |
163 return "Digimusic not supported"; | |
164 | |
165 default: | |
166 return "Unsupported player type"; | |
167 } | |
168 } | |
169 else if ( !strncmp( "STEREO", tag, tag_len ) ) | |
170 { | |
171 out->stereo = true; | |
172 } | |
173 else if ( !strncmp( "FASTPLAY", tag, tag_len ) ) | |
174 { | |
175 out->fastplay = from_dec( in, line_end ); | |
176 if ( out->fastplay <= 0 ) | |
177 return "Invalid fastplay value"; | |
178 } | |
179 else if ( !strncmp( "AUTHOR", tag, tag_len ) ) | |
180 { | |
181 parse_string( in, line_end, sizeof out->author, out->author ); | |
182 } | |
183 else if ( !strncmp( "NAME", tag, tag_len ) ) | |
184 { | |
185 parse_string( in, line_end, sizeof out->name, out->name ); | |
186 } | |
187 else if ( !strncmp( "DATE", tag, tag_len ) ) | |
188 { | |
189 parse_string( in, line_end, sizeof out->copyright, out->copyright ); | |
190 } | |
191 | |
192 in = line_end + 2; | |
193 } | |
194 | |
195 if ( in [0] != 0xFF || in [1] != 0xFF ) | |
196 return "ROM data missing"; | |
197 out->rom_data = in + 2; | |
198 | |
199 return 0; | |
200 } | |
201 | |
202 static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out ) | |
203 { | |
204 Gme_File::copy_field_( out->game, in.name ); | |
205 Gme_File::copy_field_( out->author, in.author ); | |
206 Gme_File::copy_field_( out->copyright, in.copyright ); | |
207 } | |
208 | |
209 blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const | |
210 { | |
211 copy_sap_fields( info, out ); | |
212 return 0; | |
213 } | |
214 | |
215 struct Sap_File : Gme_Info_ | |
216 { | |
217 Sap_Emu::info_t info; | |
218 | |
219 Sap_File() { set_type( gme_sap_type ); } | |
220 | |
221 blargg_err_t load_mem_( byte const* begin, long size ) | |
222 { | |
223 RETURN_ERR( parse_info( begin, size, &info ) ); | |
224 set_track_count( info.track_count ); | |
225 return 0; | |
226 } | |
227 | |
228 blargg_err_t track_info_( track_info_t* out, int ) const | |
229 { | |
230 copy_sap_fields( info, out ); | |
231 return 0; | |
232 } | |
233 }; | |
234 | |
235 static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; } | |
236 static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; } | |
237 | |
238 gme_type_t_ const gme_sap_type [1] = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 }; | |
239 | |
240 // Setup | |
241 | |
242 blargg_err_t Sap_Emu::load_mem_( byte const* in, long size ) | |
243 { | |
244 file_end = in + size; | |
245 | |
246 info.warning = 0; | |
247 info.type = 'B'; | |
248 info.stereo = false; | |
249 info.init_addr = -1; | |
250 info.play_addr = -1; | |
251 info.music_addr = -1; | |
252 info.fastplay = 312; | |
253 RETURN_ERR( parse_info( in, size, &info ) ); | |
254 | |
255 set_warning( info.warning ); | |
256 set_track_count( info.track_count ); | |
257 set_voice_count( Sap_Apu::osc_count << info.stereo ); | |
258 apu_impl.volume( gain() ); | |
259 | |
260 return setup_buffer( 1773447 ); | |
261 } | |
262 | |
263 void Sap_Emu::update_eq( blip_eq_t const& eq ) | |
264 { | |
265 apu_impl.synth.treble_eq( eq ); | |
266 } | |
267 | |
268 void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) | |
269 { | |
270 int i2 = i - Sap_Apu::osc_count; | |
271 if ( i2 >= 0 ) | |
272 apu2.osc_output( i2, right ); | |
273 else | |
274 apu.osc_output( i, (info.stereo ? left : center) ); | |
275 } | |
276 | |
277 // Emulation | |
278 | |
279 void Sap_Emu::set_tempo_( double t ) | |
280 { | |
281 scanline_period = sap_time_t (base_scanline_period / t); | |
282 } | |
283 | |
284 inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; } | |
285 | |
286 void Sap_Emu::cpu_jsr( sap_addr_t addr ) | |
287 { | |
288 check( r.sp >= 0xFE ); // catch anything trying to leave data on stack | |
289 r.pc = addr; | |
290 int high_byte = (idle_addr - 1) >> 8; | |
291 if ( r.sp == 0xFE && mem [0x1FF] == high_byte ) | |
292 r.sp = 0xFF; // pop extra byte off | |
293 mem [0x100 + r.sp--] = high_byte; // some routines use RTI to return | |
294 mem [0x100 + r.sp--] = high_byte; | |
295 mem [0x100 + r.sp--] = idle_addr - 1; | |
296 } | |
297 | |
298 void Sap_Emu::run_routine( sap_addr_t addr ) | |
299 { | |
300 cpu_jsr( addr ); | |
301 cpu::run( 312 * base_scanline_period * 60 ); | |
302 check( r.pc == idle_addr ); | |
303 } | |
304 | |
305 inline void Sap_Emu::call_init( int track ) | |
306 { | |
307 switch ( info.type ) | |
308 { | |
309 case 'B': | |
310 r.a = track; | |
311 run_routine( info.init_addr ); | |
312 break; | |
313 | |
314 case 'C': | |
315 r.a = 0x70; | |
316 r.x = info.music_addr&0xFF; | |
317 r.y = info.music_addr >> 8; | |
318 run_routine( info.play_addr + 3 ); | |
319 r.a = 0; | |
320 r.x = track; | |
321 run_routine( info.play_addr + 3 ); | |
322 break; | |
323 } | |
324 } | |
325 | |
326 blargg_err_t Sap_Emu::start_track_( int track ) | |
327 { | |
328 RETURN_ERR( Classic_Emu::start_track_( track ) ); | |
329 | |
330 memset( mem, 0, sizeof mem ); | |
331 byte const* in = info.rom_data; | |
332 while ( file_end - in >= 5 ) | |
333 { | |
334 unsigned start = get_le16( in ); | |
335 unsigned end = get_le16( in + 2 ); | |
336 //dprintf( "Block $%04X-$%04X\n", start, end ); | |
337 in += 4; | |
338 if ( end < start ) | |
339 { | |
340 set_warning( "Invalid file data block" ); | |
341 break; | |
342 } | |
343 long len = end - start + 1; | |
344 if ( len > file_end - in ) | |
345 { | |
346 set_warning( "Invalid file data block" ); | |
347 break; | |
348 } | |
349 | |
350 memcpy( mem + start, in, len ); | |
351 in += len; | |
352 if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF ) | |
353 in += 2; | |
354 } | |
355 | |
356 apu.reset( &apu_impl ); | |
357 apu2.reset( &apu_impl ); | |
358 cpu::reset( mem ); | |
359 time_mask = 0; // disables sound during init | |
360 call_init( track ); | |
361 time_mask = -1; | |
362 | |
363 next_play = play_period(); | |
364 | |
365 return 0; | |
366 } | |
367 | |
368 // Emulation | |
369 | |
370 // see sap_cpu_io.h for read/write functions | |
371 | |
372 void Sap_Emu::cpu_write_( sap_addr_t addr, int data ) | |
373 { | |
374 if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) ) | |
375 { | |
376 apu.write_data( time() & time_mask, addr, data ); | |
377 return; | |
378 } | |
379 | |
380 if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) && | |
381 info.stereo ) | |
382 { | |
383 apu2.write_data( time() & time_mask, addr ^ 0x10, data ); | |
384 return; | |
385 } | |
386 | |
387 if ( (addr & ~0x0010) != 0xD20F || data != 0x03 ) | |
388 dprintf( "Unmapped write $%04X <- $%02X\n", addr, data ); | |
389 } | |
390 | |
391 inline void Sap_Emu::call_play() | |
392 { | |
393 switch ( info.type ) | |
394 { | |
395 case 'B': | |
396 cpu_jsr( info.play_addr ); | |
397 break; | |
398 | |
399 case 'C': | |
400 cpu_jsr( info.play_addr + 6 ); | |
401 break; | |
402 } | |
403 } | |
404 | |
405 blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int ) | |
406 { | |
407 set_time( 0 ); | |
408 while ( time() < duration ) | |
409 { | |
410 if ( cpu::run( duration ) || r.pc > idle_addr ) | |
411 return "Emulation error (illegal instruction)"; | |
412 | |
413 if ( r.pc == idle_addr ) | |
414 { | |
415 if ( next_play <= duration ) | |
416 { | |
417 set_time( next_play ); | |
418 next_play += play_period(); | |
419 call_play(); | |
420 } | |
421 else | |
422 { | |
423 set_time( duration ); | |
424 } | |
425 } | |
426 } | |
427 | |
428 duration = time(); | |
429 next_play -= duration; | |
430 check( next_play >= 0 ); | |
431 if ( next_play < 0 ) | |
432 next_play = 0; | |
433 apu.end_frame( duration ); | |
434 if ( info.stereo ) | |
435 apu2.end_frame( duration ); | |
436 | |
437 return 0; | |
438 } |