Mercurial > audlegacy-plugins
comparison src/console/Gbs_Emu.cxx @ 12:3da1b8942b8b trunk
[svn] - remove src/Input src/Output src/Effect src/General src/Visualization src/Container
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 03:14:20 -0700 |
parents | src/Input/console/Gbs_Emu.cxx@13389e613d67 |
children | fb513e10174e |
comparison
equal
deleted
inserted
replaced
11:cff1d04026ae | 12:3da1b8942b8b |
---|---|
1 | |
2 // Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ | |
3 | |
4 #include "Gbs_Emu.h" | |
5 | |
6 #include <string.h> | |
7 | |
8 #include "blargg_endian.h" | |
9 | |
10 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you | |
11 can redistribute it and/or modify it under the terms of the GNU Lesser | |
12 General Public License as published by the Free Software Foundation; either | |
13 version 2.1 of the License, or (at your option) any later version. This | |
14 module is distributed in the hope that it will be useful, but WITHOUT ANY | |
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
16 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for | |
17 more details. You should have received a copy of the GNU Lesser General | |
18 Public License along with this module; if not, write to the Free Software | |
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | |
20 | |
21 #include BLARGG_SOURCE_BEGIN | |
22 | |
23 #ifndef RUN_GB_CPU | |
24 #define RUN_GB_CPU( cpu, n ) cpu.run( n ) | |
25 #endif | |
26 | |
27 const long bank_size = 0x4000; | |
28 const gb_addr_t ram_addr = 0xa000; | |
29 const gb_addr_t halt_addr = 0x9EFE; | |
30 static BOOST::uint8_t unmapped_code [Gb_Cpu::page_size]; | |
31 | |
32 Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000 }; | |
33 Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 300 }; | |
34 | |
35 // RAM | |
36 | |
37 int Gbs_Emu::read_ram( Gbs_Emu* emu, gb_addr_t addr ) | |
38 { | |
39 return emu->ram [addr - ram_addr]; | |
40 } | |
41 | |
42 void Gbs_Emu::write_ram( Gbs_Emu* emu, gb_addr_t addr, int data ) | |
43 { | |
44 emu->ram [addr - ram_addr] = data; | |
45 } | |
46 | |
47 // Unmapped | |
48 | |
49 int Gbs_Emu::read_unmapped( Gbs_Emu*, gb_addr_t addr ) | |
50 { | |
51 dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr ); | |
52 return 0xFF; // open bus value | |
53 } | |
54 | |
55 void Gbs_Emu::write_unmapped( Gbs_Emu*, gb_addr_t addr, int ) | |
56 { | |
57 dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr ); | |
58 } | |
59 | |
60 // ROM | |
61 | |
62 int Gbs_Emu::read_rom( Gbs_Emu* emu, gb_addr_t addr ) | |
63 { | |
64 return emu->rom [addr]; | |
65 } | |
66 | |
67 int Gbs_Emu::read_bank( Gbs_Emu* emu, gb_addr_t addr ) | |
68 { | |
69 return emu->rom_bank [addr & (bank_size - 1)]; | |
70 } | |
71 | |
72 void Gbs_Emu::set_bank( int n ) | |
73 { | |
74 if ( n >= bank_count ) | |
75 { | |
76 n = 0; | |
77 dprintf( "Set to non-existent bank %d\n", (int) n ); | |
78 } | |
79 if ( n == 0 && bank_count > 1 ) | |
80 { | |
81 // to do: what is the correct behavior? Current Wario Land 3 and | |
82 // Tetris DX GBS rips require that this have no effect or set to bank 1. | |
83 //return; | |
84 //dprintf( "Selected ROM bank 0\n" ); | |
85 } | |
86 rom_bank = &rom [n * bank_size]; | |
87 cpu.map_code( bank_size, bank_size, rom_bank ); | |
88 } | |
89 | |
90 void Gbs_Emu::write_rom( Gbs_Emu* emu, gb_addr_t addr, int data ) | |
91 { | |
92 if ( unsigned (addr - 0x2000) < 0x2000 ) | |
93 emu->set_bank( data & 0x1F ); | |
94 } | |
95 | |
96 // I/O: Timer, APU | |
97 | |
98 void Gbs_Emu::set_timer( int modulo, int rate ) | |
99 { | |
100 if ( timer_mode ) | |
101 { | |
102 static byte const rates [4] = { 10, 4, 6, 8 }; | |
103 play_period = (gb_time_t) (256 - modulo) << (rates [rate & 3] - double_speed); | |
104 } | |
105 } | |
106 | |
107 inline gb_time_t Gbs_Emu::clock() const | |
108 { | |
109 return cpu_time - cpu.remain(); | |
110 } | |
111 | |
112 int Gbs_Emu::read_io( Gbs_Emu* emu, gb_addr_t addr ) | |
113 { | |
114 // hi_page is accessed most | |
115 if ( addr >= 0xFF80 ) | |
116 return emu->hi_page [addr & 0xFF]; | |
117 | |
118 if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count ) | |
119 return emu->apu.read_register( emu->clock(), addr ); | |
120 | |
121 if ( addr == 0xFF00 ) | |
122 return 0; // joypad | |
123 | |
124 dprintf( "Unhandled I/O read 0x%4X\n", (unsigned) addr ); | |
125 | |
126 return 0xFF; | |
127 } | |
128 | |
129 void Gbs_Emu::write_io( Gbs_Emu* emu, gb_addr_t addr, int data ) | |
130 { | |
131 // apu is accessed most | |
132 if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) | |
133 { | |
134 emu->apu.write_register( emu->clock(), addr, data ); | |
135 } | |
136 else | |
137 { | |
138 emu->hi_page [addr & 0xFF] = data; | |
139 | |
140 if ( addr == 0xFF06 || addr == 0xFF07 ) | |
141 emu->set_timer( emu->hi_page [6], emu->hi_page [7] ); | |
142 | |
143 //if ( addr == 0xFFFF ) | |
144 // dprintf( "Wrote interrupt mask\n" ); | |
145 } | |
146 } | |
147 | |
148 Gbs_Emu::Gbs_Emu( double gain ) : cpu( this ) | |
149 { | |
150 apu.volume( gain ); | |
151 | |
152 static equalizer_t const eq = { -1.0, 120 }; | |
153 set_equalizer( eq ); | |
154 | |
155 // unmapped code is all HALT instructions | |
156 memset( unmapped_code, 0x76, sizeof unmapped_code ); | |
157 | |
158 // cpu | |
159 cpu.reset( unmapped_code, read_unmapped, write_unmapped ); | |
160 cpu.map_memory( 0x0000, 0x4000, read_rom, write_rom ); | |
161 cpu.map_memory( 0x4000, 0x4000, read_bank, write_rom ); | |
162 cpu.map_memory( ram_addr, 0x4000, read_ram, write_ram ); | |
163 cpu.map_code( ram_addr, 0x4000, ram ); | |
164 cpu.map_code( 0xFF00, 0x0100, hi_page ); | |
165 cpu.map_memory( 0xFF00, 0x0100, read_io, write_io ); | |
166 } | |
167 | |
168 Gbs_Emu::~Gbs_Emu() | |
169 { | |
170 } | |
171 | |
172 void Gbs_Emu::unload() | |
173 { | |
174 cpu.r.pc = halt_addr; | |
175 rom.clear(); | |
176 } | |
177 | |
178 void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) | |
179 { | |
180 apu.osc_output( i, c, l, r ); | |
181 } | |
182 | |
183 void Gbs_Emu::update_eq( blip_eq_t const& eq ) | |
184 { | |
185 apu.treble_eq( eq ); | |
186 } | |
187 | |
188 blargg_err_t Gbs_Emu::load( Data_Reader& in ) | |
189 { | |
190 header_t h; | |
191 BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); | |
192 return load( h, in ); | |
193 } | |
194 | |
195 blargg_err_t Gbs_Emu::load( const header_t& h, Data_Reader& in ) | |
196 { | |
197 header_ = h; | |
198 unload(); | |
199 | |
200 // check compatibility | |
201 if ( 0 != memcmp( header_.tag, "GBS", 3 ) ) | |
202 return "Not a GBS file"; | |
203 if ( header_.vers != 1 ) | |
204 return "Unsupported GBS format"; | |
205 | |
206 // gather relevant fields | |
207 load_addr = get_le16( header_.load_addr ); | |
208 init_addr = get_le16( header_.init_addr ); | |
209 play_addr = get_le16( header_.play_addr ); | |
210 stack_ptr = get_le16( header_.stack_ptr ); | |
211 double_speed = (header_.timer_mode & 0x80) != 0; | |
212 timer_modulo_init = header_.timer_modulo; | |
213 timer_mode = header_.timer_mode; | |
214 if ( !(timer_mode & 0x04) ) | |
215 timer_mode = 0; // using vbl | |
216 | |
217 #ifndef NDEBUG | |
218 { | |
219 if ( header_.timer_mode & 0x78 ) | |
220 dprintf( "TAC field has extra bits set: 0x%02x\n", (unsigned) header_.timer_mode ); | |
221 | |
222 if ( load_addr < 0x400 || load_addr >= 0x8000 || | |
223 init_addr < 0x400 || init_addr >= 0x8000 || | |
224 play_addr < 0x400 || play_addr >= 0x8000 ) | |
225 dprintf( "Load/init/play address violates GBS spec.\n" ); | |
226 } | |
227 #endif | |
228 | |
229 // rom | |
230 bank_count = (load_addr + in.remain() + bank_size - 1) / bank_size; | |
231 BLARGG_RETURN_ERR( rom.resize( bank_count * bank_size ) ); | |
232 memset( rom.begin(), 0, rom.size() ); | |
233 blargg_err_t err = in.read( &rom [load_addr], in.remain() ); | |
234 if ( err ) | |
235 { | |
236 unload(); | |
237 return err; | |
238 } | |
239 | |
240 // cpu | |
241 cpu.rst_base = load_addr; | |
242 cpu.map_code( 0x0000, 0x4000, rom.begin() ); | |
243 | |
244 set_voice_count( Gb_Apu::osc_count ); | |
245 set_track_count( header_.track_count ); | |
246 | |
247 return setup_buffer( 4194304 ); | |
248 } | |
249 | |
250 const char** Gbs_Emu::voice_names() const | |
251 { | |
252 static const char* names [] = { "Square 1", "Square 2", "Wave", "Noise" }; | |
253 return names; | |
254 } | |
255 | |
256 // Emulation | |
257 | |
258 static const BOOST::uint8_t sound_data [Gb_Apu::register_count] = { | |
259 0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1 | |
260 0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2 | |
261 0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave | |
262 0x00, 0xFF, 0x00, 0x00, 0xBF, // noise | |
263 0x77, 0xF3, 0xF1, // vin/volume, status, power mode | |
264 0, 0, 0, 0, 0, 0, 0, 0, 0, // unused | |
265 0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, // waveform data | |
266 0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48 | |
267 }; | |
268 | |
269 void Gbs_Emu::cpu_jsr( gb_addr_t addr ) | |
270 { | |
271 cpu.write( --cpu.r.sp, cpu.r.pc >> 8 ); | |
272 cpu.write( --cpu.r.sp, cpu.r.pc&0xFF ); | |
273 cpu.r.pc = addr; | |
274 } | |
275 | |
276 void Gbs_Emu::start_track( int track_index ) | |
277 { | |
278 require( rom.size() ); // file must be loaded | |
279 | |
280 Classic_Emu::start_track( track_index ); | |
281 | |
282 apu.reset(); | |
283 | |
284 memset( ram, 0, sizeof ram ); | |
285 memset( hi_page, 0, sizeof hi_page ); | |
286 | |
287 // configure hardware | |
288 set_bank( bank_count > 1 ); | |
289 for ( int i = 0; i < (int) sizeof sound_data; i++ ) | |
290 apu.write_register( 0, i + apu.start_addr, sound_data [i] ); | |
291 play_period = 70224; // 59.73 Hz | |
292 set_timer( timer_modulo_init, timer_mode ); // ignored if using vbl | |
293 next_play = play_period; | |
294 | |
295 // set up init call | |
296 cpu.r.a = track_index; | |
297 cpu.r.b = 0; | |
298 cpu.r.c = 0; | |
299 cpu.r.d = 0; | |
300 cpu.r.e = 0; | |
301 cpu.r.h = 0; | |
302 cpu.r.l = 0; | |
303 cpu.r.flags = 0; | |
304 cpu.r.pc = halt_addr; | |
305 cpu.r.sp = stack_ptr; | |
306 cpu_jsr( init_addr ); | |
307 } | |
308 | |
309 blip_time_t Gbs_Emu::run_clocks( blip_time_t duration, bool* added_stereo ) | |
310 { | |
311 require( rom.size() ); // file must be loaded | |
312 | |
313 cpu_time = 0; | |
314 while ( cpu_time < duration ) | |
315 { | |
316 // check for idle cpu | |
317 if ( cpu.r.pc == halt_addr ) | |
318 { | |
319 if ( next_play > duration ) | |
320 { | |
321 cpu_time = duration; | |
322 break; | |
323 } | |
324 | |
325 if ( cpu_time < next_play ) | |
326 cpu_time = next_play; | |
327 next_play += play_period; | |
328 cpu_jsr( play_addr ); | |
329 } | |
330 | |
331 long count = duration - cpu_time; | |
332 cpu_time = duration; | |
333 Gb_Cpu::result_t result = RUN_GB_CPU( cpu, count ); | |
334 cpu_time -= cpu.remain(); | |
335 | |
336 if ( (result == Gb_Cpu::result_halt && cpu.r.pc != halt_addr) || | |
337 result == Gb_Cpu::result_badop ) | |
338 { | |
339 if ( cpu.r.pc > 0xFFFF ) | |
340 { | |
341 dprintf( "PC wrapped around\n" ); | |
342 cpu.r.pc &= 0xFFFF; | |
343 } | |
344 else | |
345 { | |
346 log_error(); | |
347 dprintf( "Bad opcode $%.2x at $%.4x\n", | |
348 (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); | |
349 cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF; | |
350 cpu_time += 6; | |
351 } | |
352 } | |
353 } | |
354 | |
355 // end time frame | |
356 | |
357 next_play -= cpu_time; | |
358 if ( next_play < 0 ) // could go negative if routine is taking too long to return | |
359 next_play = 0; | |
360 | |
361 if ( apu.end_frame( cpu_time ) && added_stereo ) | |
362 *added_stereo = true; | |
363 | |
364 return cpu_time; | |
365 } | |
366 |