Updated Game_Music_Emu to latest VGMPlay branch

This commit is contained in:
Chris Moeller 2015-11-27 02:02:41 -08:00
parent 9020667054
commit 09e546591a
342 changed files with 90075 additions and 39784 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,412 +1,412 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Ay_Apu.h" #include "Ay_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
// Emulation inaccuracies: // Emulation inaccuracies:
// * Noise isn't run when not in use // * Noise isn't run when not in use
// * Changes to envelope and noise periods are delayed until next reload // * Changes to envelope and noise periods are delayed until next reload
// * Super-sonic tone should attenuate output to about 60%, not 50% // * Super-sonic tone should attenuate output to about 60%, not 50%
// Tones above this frequency are treated as disabled tone at half volume. // Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division). // Power of two is more efficient (avoids division).
int const inaudible_freq = 16384; int const inaudible_freq = 16384;
int const period_factor = 16; int const period_factor = 16;
static byte const amp_table [16] = static byte const amp_table [16] =
{ {
#define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5) #define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5)
// With channels tied together and 1K resistor to ground (as datasheet recommends), // With channels tied together and 1K resistor to ground (as datasheet recommends),
// output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step. // output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step.
ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625), ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625),
ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500), ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500),
ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000), ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000),
ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000), ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000),
/* /*
// Measured from an AY-3-8910A chip with date code 8611. // Measured from an AY-3-8910A chip with date code 8611.
// Direct voltages without any load (very linear) // Direct voltages without any load (very linear)
ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785), ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785),
ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032), ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032),
ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043), ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043),
ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000), ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000),
// With only some load // With only some load
ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876), ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876),
ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388), ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388),
ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945), ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945),
ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000), ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000),
*/ */
#undef ENTRY #undef ENTRY
}; };
static byte const modes [8] = static byte const modes [8] =
{ {
#define MODE( a0,a1, b0,b1, c0,c1 ) \ #define MODE( a0,a1, b0,b1, c0,c1 ) \
(a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5) (a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5)
MODE( 1,0, 1,0, 1,0 ), MODE( 1,0, 1,0, 1,0 ),
MODE( 1,0, 0,0, 0,0 ), MODE( 1,0, 0,0, 0,0 ),
MODE( 1,0, 0,1, 1,0 ), MODE( 1,0, 0,1, 1,0 ),
MODE( 1,0, 1,1, 1,1 ), MODE( 1,0, 1,1, 1,1 ),
MODE( 0,1, 0,1, 0,1 ), MODE( 0,1, 0,1, 0,1 ),
MODE( 0,1, 1,1, 1,1 ), MODE( 0,1, 1,1, 1,1 ),
MODE( 0,1, 1,0, 0,1 ), MODE( 0,1, 1,0, 0,1 ),
MODE( 0,1, 0,0, 0,0 ), MODE( 0,1, 0,0, 0,0 ),
}; };
void Ay_Apu::set_output( Blip_Buffer* b ) void Ay_Apu::set_output( Blip_Buffer* b )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
set_output( i, b ); set_output( i, b );
} }
Ay_Apu::Ay_Apu() Ay_Apu::Ay_Apu()
{ {
// build full table of the upper 8 envelope waveforms // build full table of the upper 8 envelope waveforms
for ( int m = 8; m--; ) for ( int m = 8; m--; )
{ {
byte* out = env_modes [m]; byte* out = env_modes [m];
int flags = modes [m]; int flags = modes [m];
for ( int x = 3; --x >= 0; ) for ( int x = 3; --x >= 0; )
{ {
int amp = flags & 1; int amp = flags & 1;
int end = flags >> 1 & 1; int end = flags >> 1 & 1;
int step = end - amp; int step = end - amp;
amp *= 15; amp *= 15;
for ( int y = 16; --y >= 0; ) for ( int y = 16; --y >= 0; )
{ {
*out++ = amp_table [amp]; *out++ = amp_table [amp];
amp += step; amp += step;
} }
flags >>= 2; flags >>= 2;
} }
} }
type_ = Ay8910; type_ = Ay8910;
set_output( NULL ); set_output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
void Ay_Apu::reset() void Ay_Apu::reset()
{ {
addr_ = 0; addr_ = 0;
last_time = 0; last_time = 0;
noise_delay = 0; noise_delay = 0;
noise_lfsr = 1; noise_lfsr = 1;
for ( osc_t* osc = &oscs [osc_count]; osc != oscs; ) for ( osc_t* osc = &oscs [osc_count]; osc != oscs; )
{ {
osc--; osc--;
osc->period = period_factor; osc->period = period_factor;
osc->delay = 0; osc->delay = 0;
osc->last_amp = 0; osc->last_amp = 0;
osc->phase = 0; osc->phase = 0;
} }
for ( int i = sizeof regs; --i >= 0; ) for ( int i = sizeof regs; --i >= 0; )
regs [i] = 0; regs [i] = 0;
regs [7] = 0xFF; regs [7] = 0xFF;
write_data_( 13, 0 ); write_data_( 13, 0 );
} }
int Ay_Apu::read() int Ay_Apu::read()
{ {
static byte const masks [reg_count] = { static byte const masks [reg_count] = {
0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F, 0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F,
0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00 0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00
}; };
if (!(type_ & 0x10)) return regs [addr_] & masks [addr_]; if (!(type_ & 0x10)) return regs [addr_] & masks [addr_];
else return regs [addr_]; else return regs [addr_];
} }
void Ay_Apu::write_data_( int addr, int data ) void Ay_Apu::write_data_( int addr, int data )
{ {
assert( (unsigned) addr < reg_count ); assert( (unsigned) addr < reg_count );
if ( (unsigned) addr >= 14 ) if ( (unsigned) addr >= 14 )
dprintf( "Wrote to I/O port %02X\n", (int) addr ); dprintf( "Wrote to I/O port %02X\n", (int) addr );
// envelope mode // envelope mode
if ( addr == 13 ) if ( addr == 13 )
{ {
if ( !(data & 8) ) // convert modes 0-7 to proper equivalents if ( !(data & 8) ) // convert modes 0-7 to proper equivalents
data = (data & 4) ? 15 : 9; data = (data & 4) ? 15 : 9;
env_wave = env_modes [data - 7]; env_wave = env_modes [data - 7];
env_pos = -48; env_pos = -48;
env_delay = 0; // will get set to envelope period in run_until() env_delay = 0; // will get set to envelope period in run_until()
} }
regs [addr] = data; regs [addr] = data;
// handle period changes accurately // handle period changes accurately
int i = addr >> 1; int i = addr >> 1;
if ( i < osc_count ) if ( i < osc_count )
{ {
blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) + blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) +
regs [i * 2] * period_factor; regs [i * 2] * period_factor;
if ( !period ) if ( !period )
period = period_factor; period = period_factor;
// adjust time of next timer expiration based on change in period // adjust time of next timer expiration based on change in period
osc_t& osc = oscs [i]; osc_t& osc = oscs [i];
if ( (osc.delay += period - osc.period) < 0 ) if ( (osc.delay += period - osc.period) < 0 )
osc.delay = 0; osc.delay = 0;
osc.period = period; osc.period = period;
} }
// TODO: same as above for envelope timer, and it also has a divide by two after it // TODO: same as above for envelope timer, and it also has a divide by two after it
} }
int const noise_off = 0x08; int const noise_off = 0x08;
int const tone_off = 0x01; int const tone_off = 0x01;
void Ay_Apu::run_until( blip_time_t final_end_time ) void Ay_Apu::run_until( blip_time_t final_end_time )
{ {
require( final_end_time >= last_time ); require( final_end_time >= last_time );
// noise period and initial values // noise period and initial values
blip_time_t const noise_period_factor = period_factor * 2; // verified blip_time_t const noise_period_factor = period_factor * 2; // verified
blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor; blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor;
if ( !noise_period ) if ( !noise_period )
noise_period = noise_period_factor; noise_period = noise_period_factor;
blip_time_t const old_noise_delay = noise_delay; blip_time_t const old_noise_delay = noise_delay;
unsigned const old_noise_lfsr = noise_lfsr; unsigned const old_noise_lfsr = noise_lfsr;
// envelope period // envelope period
int env_step_scale = ((type_ & 0xF0) == 0x00) ? 1 : 0; int env_step_scale = ((type_ & 0xF0) == 0x00) ? 1 : 0;
blip_time_t const env_period_factor = period_factor << env_step_scale; // verified blip_time_t const env_period_factor = period_factor << env_step_scale; // verified
blip_time_t env_period = (regs [12] * 0x100 + regs [11]) * env_period_factor; blip_time_t env_period = (regs [12] * 0x100 + regs [11]) * env_period_factor;
if ( !env_period ) if ( !env_period )
env_period = env_period_factor; // same as period 1 on my AY chip env_period = env_period_factor; // same as period 1 on my AY chip
if ( !env_delay ) if ( !env_delay )
env_delay = env_period; env_delay = env_period;
// run each osc separately // run each osc separately
for ( int index = 0; index < osc_count; index++ ) for ( int index = 0; index < osc_count; index++ )
{ {
osc_t* const osc = &oscs [index]; osc_t* const osc = &oscs [index];
int osc_mode = regs [7] >> index; int osc_mode = regs [7] >> index;
// output // output
Blip_Buffer* const osc_output = osc->output; Blip_Buffer* const osc_output = osc->output;
if ( !osc_output ) if ( !osc_output )
continue; continue;
osc_output->set_modified(); osc_output->set_modified();
// period // period
int half_vol = 0; int half_vol = 0;
blip_time_t inaudible_period = (unsigned) (osc_output->clock_rate() + blip_time_t inaudible_period = (unsigned) (osc_output->clock_rate() +
inaudible_freq) / (unsigned) (inaudible_freq * 2); inaudible_freq) / (unsigned) (inaudible_freq * 2);
if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) if ( osc->period <= inaudible_period && !(osc_mode & tone_off) )
{ {
half_vol = 1; // Actually around 60%, but 50% is close enough half_vol = 1; // Actually around 60%, but 50% is close enough
osc_mode |= tone_off; osc_mode |= tone_off;
} }
// envelope // envelope
blip_time_t start_time = last_time; blip_time_t start_time = last_time;
blip_time_t end_time = final_end_time; blip_time_t end_time = final_end_time;
int const vol_mode = regs [0x08 + index]; int const vol_mode = regs [0x08 + index];
int const vol_mode_mask = type_ == Ay8914 ? 0x30 : 0x10; int const vol_mode_mask = type_ == Ay8914 ? 0x30 : 0x10;
int volume = amp_table [vol_mode & 0x0F] >> (half_vol + env_step_scale); int volume = amp_table [vol_mode & 0x0F] >> (half_vol + env_step_scale);
int osc_env_pos = env_pos; int osc_env_pos = env_pos;
if ( vol_mode & vol_mode_mask ) if ( vol_mode & vol_mode_mask )
{ {
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale); volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale);
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 ); if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
// use envelope only if it's a repeating wave or a ramp that hasn't finished // use envelope only if it's a repeating wave or a ramp that hasn't finished
if ( !(regs [13] & 1) || osc_env_pos < -32 ) if ( !(regs [13] & 1) || osc_env_pos < -32 )
{ {
end_time = start_time + env_delay; end_time = start_time + env_delay;
if ( end_time >= final_end_time ) if ( end_time >= final_end_time )
end_time = final_end_time; end_time = final_end_time;
//if ( !(regs [12] | regs [11]) ) //if ( !(regs [12] | regs [11]) )
// dprintf( "Used envelope period 0\n" ); // dprintf( "Used envelope period 0\n" );
} }
else if ( !volume ) else if ( !volume )
{ {
osc_mode = noise_off | tone_off; osc_mode = noise_off | tone_off;
} }
} }
else if ( !volume ) else if ( !volume )
{ {
osc_mode = noise_off | tone_off; osc_mode = noise_off | tone_off;
} }
// tone time // tone time
blip_time_t const period = osc->period; blip_time_t const period = osc->period;
blip_time_t time = start_time + osc->delay; blip_time_t time = start_time + osc->delay;
if ( osc_mode & tone_off ) // maintain tone's phase when off if ( osc_mode & tone_off ) // maintain tone's phase when off
{ {
int count = (final_end_time - time + period - 1) / period; int count = (final_end_time - time + period - 1) / period;
time += count * period; time += count * period;
osc->phase ^= count & 1; osc->phase ^= count & 1;
} }
// noise time // noise time
blip_time_t ntime = final_end_time; blip_time_t ntime = final_end_time;
unsigned noise_lfsr = 1; unsigned noise_lfsr = 1;
if ( !(osc_mode & noise_off) ) if ( !(osc_mode & noise_off) )
{ {
ntime = start_time + old_noise_delay; ntime = start_time + old_noise_delay;
noise_lfsr = old_noise_lfsr; noise_lfsr = old_noise_lfsr;
//if ( (regs [6] & 0x1F) == 0 ) //if ( (regs [6] & 0x1F) == 0 )
// dprintf( "Used noise period 0\n" ); // dprintf( "Used noise period 0\n" );
} }
// The following efficiently handles several cases (least demanding first): // The following efficiently handles several cases (least demanding first):
// * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC // * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC
// * Just tone or just noise, envelope disabled // * Just tone or just noise, envelope disabled
// * Envelope controlling tone and/or noise // * Envelope controlling tone and/or noise
// * Tone and noise disabled, envelope enabled with high frequency // * Tone and noise disabled, envelope enabled with high frequency
// * Tone and noise together // * Tone and noise together
// * Tone and noise together with envelope // * Tone and noise together with envelope
// This loop only runs one iteration if envelope is disabled. If envelope // This loop only runs one iteration if envelope is disabled. If envelope
// is being used as a waveform (tone and noise disabled), this loop will // is being used as a waveform (tone and noise disabled), this loop will
// still be reasonably efficient since the bulk of it will be skipped. // still be reasonably efficient since the bulk of it will be skipped.
while ( 1 ) while ( 1 )
{ {
// current amplitude // current amplitude
int amp = 0; int amp = 0;
if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) ) if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) )
amp = volume; amp = volume;
{ {
int delta = amp - osc->last_amp; int delta = amp - osc->last_amp;
if ( delta ) if ( delta )
{ {
osc->last_amp = amp; osc->last_amp = amp;
synth_.offset( start_time, delta, osc_output ); synth_.offset( start_time, delta, osc_output );
} }
} }
// Run wave and noise interleved with each catching up to the other. // Run wave and noise interleved with each catching up to the other.
// If one or both are disabled, their "current time" will be past end time, // If one or both are disabled, their "current time" will be past end time,
// so there will be no significant performance hit. // so there will be no significant performance hit.
if ( ntime < end_time || time < end_time ) if ( ntime < end_time || time < end_time )
{ {
// Since amplitude was updated above, delta will always be +/- volume, // Since amplitude was updated above, delta will always be +/- volume,
// so we can avoid using last_amp every time to calculate the delta. // so we can avoid using last_amp every time to calculate the delta.
int delta = amp * 2 - volume; int delta = amp * 2 - volume;
int delta_non_zero = delta != 0; int delta_non_zero = delta != 0;
int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 ); int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 );
do do
{ {
// run noise // run noise
blip_time_t end = end_time; blip_time_t end = end_time;
if ( end_time > time ) end = time; if ( end_time > time ) end = time;
if ( phase & delta_non_zero ) if ( phase & delta_non_zero )
{ {
while ( ntime <= end ) // must advance *past* time to avoid hang while ( ntime <= end ) // must advance *past* time to avoid hang
{ {
int changed = noise_lfsr + 1; int changed = noise_lfsr + 1;
noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1);
if ( changed & 2 ) if ( changed & 2 )
{ {
delta = -delta; delta = -delta;
synth_.offset( ntime, delta, osc_output ); synth_.offset( ntime, delta, osc_output );
} }
ntime += noise_period; ntime += noise_period;
} }
} }
else else
{ {
// 20 or more noise periods on average for some music // 20 or more noise periods on average for some music
int remain = end - ntime; int remain = end - ntime;
int count = remain / noise_period; int count = remain / noise_period;
if ( remain >= 0 ) if ( remain >= 0 )
ntime += noise_period + count * noise_period; ntime += noise_period + count * noise_period;
} }
// run tone // run tone
end = end_time; end = end_time;
if ( end_time > ntime ) end = ntime; if ( end_time > ntime ) end = ntime;
if ( noise_lfsr & delta_non_zero ) if ( noise_lfsr & delta_non_zero )
{ {
while ( time < end ) while ( time < end )
{ {
delta = -delta; delta = -delta;
synth_.offset( time, delta, osc_output ); synth_.offset( time, delta, osc_output );
time += period; time += period;
// alternate (less-efficient) implementation // alternate (less-efficient) implementation
//phase ^= 1; //phase ^= 1;
} }
phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1); phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
check( phase == (delta > 0) ); check( phase == (delta > 0) );
} }
else else
{ {
// loop usually runs less than once // loop usually runs less than once
//SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period ); //SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period );
while ( time < end ) while ( time < end )
{ {
time += period; time += period;
phase ^= 1; phase ^= 1;
} }
} }
} }
while ( time < end_time || ntime < end_time ); while ( time < end_time || ntime < end_time );
osc->last_amp = (delta + volume) >> 1; osc->last_amp = (delta + volume) >> 1;
if ( !(osc_mode & tone_off) ) if ( !(osc_mode & tone_off) )
osc->phase = phase; osc->phase = phase;
} }
if ( end_time >= final_end_time ) if ( end_time >= final_end_time )
break; // breaks first time when envelope is disabled break; // breaks first time when envelope is disabled
// next envelope step // next envelope step
if ( ++osc_env_pos >= 0 ) if ( ++osc_env_pos >= 0 )
osc_env_pos -= 32; osc_env_pos -= 32;
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale); volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale);
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 ); if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
start_time = end_time; start_time = end_time;
end_time += env_period; end_time += env_period;
if ( end_time > final_end_time ) if ( end_time > final_end_time )
end_time = final_end_time; end_time = final_end_time;
} }
osc->delay = time - final_end_time; osc->delay = time - final_end_time;
if ( !(osc_mode & noise_off) ) if ( !(osc_mode & noise_off) )
{ {
noise_delay = ntime - final_end_time; noise_delay = ntime - final_end_time;
this->noise_lfsr = noise_lfsr; this->noise_lfsr = noise_lfsr;
} }
} }
// TODO: optimized saw wave envelope? // TODO: optimized saw wave envelope?
// maintain envelope phase // maintain envelope phase
blip_time_t remain = final_end_time - last_time - env_delay; blip_time_t remain = final_end_time - last_time - env_delay;
if ( remain >= 0 ) if ( remain >= 0 )
{ {
int count = (remain + env_period) / env_period; int count = (remain + env_period) / env_period;
env_pos += count; env_pos += count;
if ( env_pos >= 0 ) if ( env_pos >= 0 )
env_pos = (env_pos & 31) - 32; env_pos = (env_pos & 31) - 32;
remain -= count * env_period; remain -= count * env_period;
assert( -remain <= env_period ); assert( -remain <= env_period );
} }
env_delay = -remain; env_delay = -remain;
assert( env_delay > 0 ); assert( env_delay > 0 );
assert( env_pos < 0 ); assert( env_pos < 0 );
last_time = final_end_time; last_time = final_end_time;
} }

View file

@ -1,123 +1,123 @@
// AY-3-8910 sound chip emulator // AY-3-8910 sound chip emulator
// $package // $package
#ifndef AY_APU_H #ifndef AY_APU_H
#define AY_APU_H #define AY_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Ay_Apu { class Ay_Apu {
public: public:
// Basics // Basics
enum Ay_Apu_Type enum Ay_Apu_Type
{ {
Ay8910 = 0, Ay8910 = 0,
Ay8912, Ay8912,
Ay8913, Ay8913,
Ay8914, Ay8914,
Ym2149 = 0x10, Ym2149 = 0x10,
Ym3439, Ym3439,
Ymz284, Ymz284,
Ymz294, Ymz294,
Ym2203 = 0x20, Ym2203 = 0x20,
Ym2608, Ym2608,
Ym2610, Ym2610,
Ym2610b Ym2610b
}; };
void set_type( Ay_Apu_Type type ) { type_ = type; } void set_type( Ay_Apu_Type type ) { type_ = type; }
// Sets buffer to generate sound into, or 0 to mute. // Sets buffer to generate sound into, or 0 to mute.
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
// Writes to address register // Writes to address register
void write_addr( int data ) { addr_ = data & 0x0F; } void write_addr( int data ) { addr_ = data & 0x0F; }
// Emulates to time t, then writes to current data register // Emulates to time t, then writes to current data register
void write_data( blip_time_t t, int data ) { run_until( t ); write_data_( addr_, data ); } void write_data( blip_time_t t, int data ) { run_until( t ); write_data_( addr_, data ); }
// Emulates to time t, then subtracts t from the current time. // Emulates to time t, then subtracts t from the current time.
// OK if previous write call had time slightly after t. // OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); void end_frame( blip_time_t t );
// More features // More features
// Reads from current data register // Reads from current data register
int read(); int read();
// Resets sound chip // Resets sound chip
void reset(); void reset();
// Number of registers // Number of registers
enum { reg_count = 16 }; enum { reg_count = 16 };
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int chan, Blip_Buffer* ); void set_output( int chan, Blip_Buffer* );
// Sets overall volume, where 1.0 is normal // Sets overall volume, where 1.0 is normal
void volume( double v ) { synth_.volume( 0.7/osc_count/amp_range * v ); } void volume( double v ) { synth_.volume( 0.7/osc_count/amp_range * v ); }
// Sets treble equalization // Sets treble equalization
void treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); } void treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); }
private: private:
// noncopyable // noncopyable
Ay_Apu( const Ay_Apu& ); Ay_Apu( const Ay_Apu& );
Ay_Apu& operator = ( const Ay_Apu& ); Ay_Apu& operator = ( const Ay_Apu& );
// Implementation // Implementation
public: public:
Ay_Apu(); Ay_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
private: private:
struct osc_t struct osc_t
{ {
blip_time_t period; blip_time_t period;
blip_time_t delay; blip_time_t delay;
short last_amp; short last_amp;
short phase; short phase;
Blip_Buffer* output; Blip_Buffer* output;
} oscs [osc_count]; } oscs [osc_count];
Ay_Apu_Type type_; Ay_Apu_Type type_;
blip_time_t last_time; blip_time_t last_time;
byte addr_; byte addr_;
byte regs [reg_count]; byte regs [reg_count];
blip_time_t noise_delay; blip_time_t noise_delay;
unsigned noise_lfsr; unsigned noise_lfsr;
blip_time_t env_delay; blip_time_t env_delay;
byte const* env_wave; byte const* env_wave;
int env_pos; int env_pos;
byte env_modes [8] [48]; // values already passed through volume table byte env_modes [8] [48]; // values already passed through volume table
void write_data_( int addr, int data ); void write_data_( int addr, int data );
void run_until( blip_time_t ); void run_until( blip_time_t );
public: public:
enum { amp_range = 255 }; enum { amp_range = 255 };
Blip_Synth_Norm synth_; // used by Ay_Core for beeper sound Blip_Synth_Norm synth_; // used by Ay_Core for beeper sound
}; };
inline void Ay_Apu::set_output( int i, Blip_Buffer* out ) inline void Ay_Apu::set_output( int i, Blip_Buffer* out )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = out; oscs [i].output = out;
} }
inline void Ay_Apu::end_frame( blip_time_t time ) inline void Ay_Apu::end_frame( blip_time_t time )
{ {
if ( time > last_time ) if ( time > last_time )
run_until( time ); run_until( time );
last_time -= time; last_time -= time;
assert( last_time >= 0 ); assert( last_time >= 0 );
} }
#endif #endif

View file

@ -1,190 +1,190 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Ay_Core.h" #include "Ay_Core.h"
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you /* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
inline void Ay_Core::disable_beeper() inline void Ay_Core::disable_beeper()
{ {
beeper_mask = 0; beeper_mask = 0;
last_beeper = 0; last_beeper = 0;
} }
Ay_Core::Ay_Core() Ay_Core::Ay_Core()
{ {
beeper_output = NULL; beeper_output = NULL;
disable_beeper(); disable_beeper();
} }
Ay_Core::~Ay_Core() { } Ay_Core::~Ay_Core() { }
void Ay_Core::set_beeper_output( Blip_Buffer* b ) void Ay_Core::set_beeper_output( Blip_Buffer* b )
{ {
beeper_output = b; beeper_output = b;
if ( b && !cpc_mode ) if ( b && !cpc_mode )
beeper_mask = 0x10; beeper_mask = 0x10;
else else
disable_beeper(); disable_beeper();
} }
void Ay_Core::start_track( registers_t const& r, addr_t play ) void Ay_Core::start_track( registers_t const& r, addr_t play )
{ {
play_addr = play; play_addr = play;
memset( mem_.padding1, 0xFF, sizeof mem_.padding1 ); memset( mem_.padding1, 0xFF, sizeof mem_.padding1 );
int const mirrored = 0x80; // this much is mirrored after end of memory int const mirrored = 0x80; // this much is mirrored after end of memory
memset( mem_.ram + mem_size + mirrored, 0xFF, sizeof mem_.ram - mem_size - mirrored ); memset( mem_.ram + mem_size + mirrored, 0xFF, sizeof mem_.ram - mem_size - mirrored );
memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh) memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh)
cpu.reset( mem_.padding1, mem_.padding1 ); cpu.reset( mem_.padding1, mem_.padding1 );
cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram ); cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram );
cpu.r = r; cpu.r = r;
beeper_delta = (int) (apu_.amp_range * 0.8); beeper_delta = (int) (apu_.amp_range * 0.8);
last_beeper = 0; last_beeper = 0;
next_play = play_period; next_play = play_period;
spectrum_mode = false; spectrum_mode = false;
cpc_mode = false; cpc_mode = false;
cpc_latch = 0; cpc_latch = 0;
set_beeper_output( beeper_output ); set_beeper_output( beeper_output );
apu_.reset(); apu_.reset();
// a few tunes rely on channels having tone enabled at the beginning // a few tunes rely on channels having tone enabled at the beginning
apu_.write_addr( 7 ); apu_.write_addr( 7 );
apu_.write_data( 0, 0x38 ); apu_.write_data( 0, 0x38 );
} }
// Emulation // Emulation
void Ay_Core::cpu_out_( time_t time, addr_t addr, int data ) void Ay_Core::cpu_out_( time_t time, addr_t addr, int data )
{ {
// Spectrum // Spectrum
if ( !cpc_mode ) if ( !cpc_mode )
{ {
switch ( addr & 0xFEFF ) switch ( addr & 0xFEFF )
{ {
case 0xFEFD: case 0xFEFD:
spectrum_mode = true; spectrum_mode = true;
apu_.write_addr( data ); apu_.write_addr( data );
return; return;
case 0xBEFD: case 0xBEFD:
spectrum_mode = true; spectrum_mode = true;
apu_.write_data( time, data ); apu_.write_data( time, data );
return; return;
} }
} }
// CPC // CPC
if ( !spectrum_mode ) if ( !spectrum_mode )
{ {
switch ( addr >> 8 ) switch ( addr >> 8 )
{ {
case 0xF6: case 0xF6:
switch ( data & 0xC0 ) switch ( data & 0xC0 )
{ {
case 0xC0: case 0xC0:
apu_.write_addr( cpc_latch ); apu_.write_addr( cpc_latch );
goto enable_cpc; goto enable_cpc;
case 0x80: case 0x80:
apu_.write_data( time, cpc_latch ); apu_.write_data( time, cpc_latch );
goto enable_cpc; goto enable_cpc;
} }
break; break;
case 0xF4: case 0xF4:
cpc_latch = data; cpc_latch = data;
goto enable_cpc; goto enable_cpc;
} }
} }
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data ); dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
return; return;
enable_cpc: enable_cpc:
if ( !cpc_mode ) if ( !cpc_mode )
{ {
cpc_mode = true; cpc_mode = true;
disable_beeper(); disable_beeper();
set_cpc_callback.f( set_cpc_callback.data ); set_cpc_callback.f( set_cpc_callback.data );
} }
} }
int Ay_Core::cpu_in( addr_t addr ) int Ay_Core::cpu_in( addr_t addr )
{ {
// keyboard read and other things // keyboard read and other things
if ( (addr & 0xFF) == 0xFE ) if ( (addr & 0xFF) == 0xFE )
return 0xFF; // other values break some beeper tunes return 0xFF; // other values break some beeper tunes
dprintf( "Unmapped IN : $%04X\n", addr ); dprintf( "Unmapped IN : $%04X\n", addr );
return 0xFF; return 0xFF;
} }
void Ay_Core::end_frame( time_t* end ) void Ay_Core::end_frame( time_t* end )
{ {
cpu.set_time( 0 ); cpu.set_time( 0 );
// Since detection of CPC mode will halve clock rate during the frame // Since detection of CPC mode will halve clock rate during the frame
// and thus generate up to twice as much sound, we must generate half // and thus generate up to twice as much sound, we must generate half
// as much until mode is known. // as much until mode is known.
if ( !(spectrum_mode | cpc_mode) ) if ( !(spectrum_mode | cpc_mode) )
*end /= 2; *end /= 2;
while ( cpu.time() < *end ) while ( cpu.time() < *end )
{ {
run_cpu( min( *end, next_play ) ); run_cpu( min( *end, next_play ) );
if ( cpu.time() >= next_play ) if ( cpu.time() >= next_play )
{ {
// next frame // next frame
next_play += play_period; next_play += play_period;
if ( cpu.r.iff1 ) if ( cpu.r.iff1 )
{ {
// interrupt enabled // interrupt enabled
if ( mem_.ram [cpu.r.pc] == 0x76 ) if ( mem_.ram [cpu.r.pc] == 0x76 )
cpu.r.pc++; // advance past HALT instruction cpu.r.pc++; // advance past HALT instruction
cpu.r.iff1 = 0; cpu.r.iff1 = 0;
cpu.r.iff2 = 0; cpu.r.iff2 = 0;
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8); mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8);
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc); mem_.ram [--cpu.r.sp] = byte (cpu.r.pc);
// fixed interrupt // fixed interrupt
cpu.r.pc = 0x38; cpu.r.pc = 0x38;
cpu.adjust_time( 12 ); cpu.adjust_time( 12 );
if ( cpu.r.im == 2 ) if ( cpu.r.im == 2 )
{ {
// vectored interrupt // vectored interrupt
addr_t addr = cpu.r.i * 0x100 + 0xFF; addr_t addr = cpu.r.i * 0x100 + 0xFF;
cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr]; cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr];
cpu.adjust_time( 6 ); cpu.adjust_time( 6 );
} }
} }
} }
} }
// End time frame // End time frame
*end = cpu.time(); *end = cpu.time();
next_play -= *end; next_play -= *end;
check( next_play >= 0 ); check( next_play >= 0 );
cpu.adjust_time( -*end ); cpu.adjust_time( -*end );
apu_.end_frame( *end ); apu_.end_frame( *end );
} }

View file

@ -1,81 +1,81 @@
// Sinclair Spectrum AY music emulator core // Sinclair Spectrum AY music emulator core
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef AY_CORE_H #ifndef AY_CORE_H
#define AY_CORE_H #define AY_CORE_H
#include "Z80_Cpu.h" #include "Z80_Cpu.h"
#include "Ay_Apu.h" #include "Ay_Apu.h"
class Ay_Core { class Ay_Core {
public: public:
// Clock count // Clock count
typedef int time_t; typedef int time_t;
// Sound chip access, to assign it to Blip_Buffer etc. // Sound chip access, to assign it to Blip_Buffer etc.
Ay_Apu& apu() { return apu_; } Ay_Apu& apu() { return apu_; }
// Sets beeper sound buffer, or NULL to mute it. Volume and treble EQ of // Sets beeper sound buffer, or NULL to mute it. Volume and treble EQ of
// beeper are set by APU. // beeper are set by APU.
void set_beeper_output( Blip_Buffer* ); void set_beeper_output( Blip_Buffer* );
// Sets time between calls to play routine. Can be changed while playing. // Sets time between calls to play routine. Can be changed while playing.
void set_play_period( time_t p ) { play_period = p; } void set_play_period( time_t p ) { play_period = p; }
// 64K memory to load code and data into before starting track. Caller // 64K memory to load code and data into before starting track. Caller
// must parse the AY file. // must parse the AY file.
BOOST::uint8_t* mem() { return mem_.ram; } BOOST::uint8_t* mem() { return mem_.ram; }
enum { mem_size = 0x10000 }; enum { mem_size = 0x10000 };
enum { ram_addr = 0x4000 }; // where official RAM starts enum { ram_addr = 0x4000 }; // where official RAM starts
// Starts track using specified register values, and sets play routine that // Starts track using specified register values, and sets play routine that
// is called periodically // is called periodically
typedef Z80_Cpu::registers_t registers_t; typedef Z80_Cpu::registers_t registers_t;
typedef int addr_t; typedef int addr_t;
void start_track( registers_t const&, addr_t play ); void start_track( registers_t const&, addr_t play );
// Ends time frame of at most *end clocks and sets *end to number of clocks // Ends time frame of at most *end clocks and sets *end to number of clocks
// emulated. Until Spectrum/CPC mode is determined, *end is HALVED. // emulated. Until Spectrum/CPC mode is determined, *end is HALVED.
void end_frame( time_t* end ); void end_frame( time_t* end );
// Called when CPC hardware is first accessed. AY file format doesn't specify // Called when CPC hardware is first accessed. AY file format doesn't specify
// which sound hardware is used, so it must be determined during playback // which sound hardware is used, so it must be determined during playback
// based on which sound port is first used. // based on which sound port is first used.
blargg_callback<void (*)( void* )> set_cpc_callback; blargg_callback<void (*)( void* )> set_cpc_callback;
// Implementation // Implementation
public: public:
Ay_Core(); Ay_Core();
~Ay_Core(); ~Ay_Core();
private: private:
Blip_Buffer* beeper_output; Blip_Buffer* beeper_output;
int beeper_delta; int beeper_delta;
int last_beeper; int last_beeper;
int beeper_mask; int beeper_mask;
addr_t play_addr; addr_t play_addr;
time_t play_period; time_t play_period;
time_t next_play; time_t next_play;
int cpc_latch; int cpc_latch;
bool spectrum_mode; bool spectrum_mode;
bool cpc_mode; bool cpc_mode;
// large items // large items
Z80_Cpu cpu; Z80_Cpu cpu;
struct { struct {
BOOST::uint8_t padding1 [0x100]; BOOST::uint8_t padding1 [0x100];
BOOST::uint8_t ram [mem_size + 0x100]; BOOST::uint8_t ram [mem_size + 0x100];
} mem_; } mem_;
Ay_Apu apu_; Ay_Apu apu_;
int cpu_in( addr_t ); int cpu_in( addr_t );
void cpu_out( time_t, addr_t, int data ); void cpu_out( time_t, addr_t, int data );
void cpu_out_( time_t, addr_t, int data ); void cpu_out_( time_t, addr_t, int data );
bool run_cpu( time_t end ); bool run_cpu( time_t end );
void disable_beeper(); void disable_beeper();
}; };
#endif #endif

View file

@ -1,59 +1,59 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Ay_Core.h" #include "Ay_Core.h"
#include "blargg_endian.h" #include "blargg_endian.h"
//#include "z80_cpu_log.h" //#include "z80_cpu_log.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
void Ay_Core::cpu_out( time_t time, addr_t addr, int data ) void Ay_Core::cpu_out( time_t time, addr_t addr, int data )
{ {
if ( (addr & 0xFF) == 0xFE ) if ( (addr & 0xFF) == 0xFE )
{ {
check( !cpc_mode ); check( !cpc_mode );
spectrum_mode = !cpc_mode; spectrum_mode = !cpc_mode;
// beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output) // beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output)
if ( (data &= beeper_mask) != last_beeper ) if ( (data &= beeper_mask) != last_beeper )
{ {
last_beeper = data; last_beeper = data;
int delta = -beeper_delta; int delta = -beeper_delta;
beeper_delta = delta; beeper_delta = delta;
Blip_Buffer* bb = beeper_output; Blip_Buffer* bb = beeper_output;
bb->set_modified(); bb->set_modified();
apu_.synth_.offset( time, delta, bb ); apu_.synth_.offset( time, delta, bb );
} }
} }
else else
{ {
cpu_out_( time, addr, data ); cpu_out_( time, addr, data );
} }
} }
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data ) #define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
#define IN_PORT( addr ) cpu_in( addr ) #define IN_PORT( addr ) cpu_in( addr )
#define FLAT_MEM mem #define FLAT_MEM mem
#define CPU cpu #define CPU cpu
#define CPU_BEGIN \ #define CPU_BEGIN \
bool Ay_Core::run_cpu( time_t end_time ) \ bool Ay_Core::run_cpu( time_t end_time ) \
{\ {\
cpu.set_end_time( end_time );\ cpu.set_end_time( end_time );\
byte* const mem = mem_.ram; // cache byte* const mem = mem_.ram; // cache
#include "Z80_Cpu_run.h" #include "Z80_Cpu_run.h"
return warning; return warning;
} }

View file

@ -1,60 +1,60 @@
// Sinclair Spectrum AY music file emulator // Sinclair Spectrum AY music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef AY_EMU_H #ifndef AY_EMU_H
#define AY_EMU_H #define AY_EMU_H
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Ay_Core.h" #include "Ay_Core.h"
class Ay_Emu : public Classic_Emu { class Ay_Emu : public Classic_Emu {
public: public:
// AY file header // AY file header
struct header_t struct header_t
{ {
enum { size = 0x14 }; enum { size = 0x14 };
byte tag [8]; byte tag [8];
byte vers; byte vers;
byte player; byte player;
byte unused [2]; byte unused [2];
byte author [2]; byte author [2];
byte comment [2]; byte comment [2];
byte max_track; byte max_track;
byte first_track; byte first_track;
byte track_info [2]; byte track_info [2];
}; };
static gme_type_t static_type() { return gme_ay_type; } static gme_type_t static_type() { return gme_ay_type; }
// Implementation // Implementation
public: public:
Ay_Emu(); Ay_Emu();
~Ay_Emu(); ~Ay_Emu();
struct file_t { struct file_t {
header_t const* header; header_t const* header;
byte const* tracks; byte const* tracks;
byte const* end; // end of file data byte const* end; // end of file data
}; };
blargg_err_t hash_( Hash_Function& out ) const; blargg_err_t hash_( Hash_Function& out ) const;
protected: protected:
virtual blargg_err_t track_info_( track_info_t*, int track ) const; virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_mem_( byte const [], int ); virtual blargg_err_t load_mem_( byte const [], int );
virtual blargg_err_t start_track_( int ); virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int ); virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double ); virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& ); virtual void update_eq( blip_eq_t const& );
private: private:
file_t file; file_t file;
Ay_Core core; Ay_Core core;
void enable_cpc(); void enable_cpc();
static void enable_cpc_( void* data ); static void enable_cpc_( void* data );
}; };
#endif #endif

View file

@ -1,77 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "C140_Emu.h"
#include "c140.h"
C140_Emu::C140_Emu() { chip = 0; }
C140_Emu::~C140_Emu()
{
if ( chip ) device_stop_c140( chip );
}
int C140_Emu::set_rate( int type, double sample_rate, double clock_rate )
{
if ( chip )
{
device_stop_c140( chip );
chip = 0;
}
chip = device_start_c140( sample_rate, clock_rate, type );
if ( !chip )
return 1;
reset();
return 0;
}
void C140_Emu::reset()
{
device_reset_c140( chip );
c140_set_mute_mask( chip, 0 );
}
void C140_Emu::write( int addr, int data )
{
c140_w( chip, addr, data );
}
void C140_Emu::write_rom( int size, int start, int length, void * data )
{
c140_write_rom( chip, size, start, length, (const UINT8 *) data );
}
void C140_Emu::mute_voices( int mask )
{
c140_set_mute_mask( chip, mask );
}
void C140_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
c140_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,36 +0,0 @@
// C140 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef C140_EMU_H
#define C140_EMU_H
class C140_Emu {
void* chip;
public:
C140_Emu();
~C140_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int type, double sample_rate, double clock_rate );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 24 };
void mute_voices( int mask );
// Writes data to addr
void write( int addr, int data );
// Scales ROM size, then writes length bytes from data at start offset
void write_rom( int size, int start, int length, void * data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,147 +0,0 @@
// Fir_Resampler chip emulator container that mixes into the output buffer
// Game_Music_Emu $vers
#ifndef CHIP_RESAMPLER_H
#define CHIP_RESAMPLER_H
#include "blargg_source.h"
#include "Fir_Resampler.h"
typedef Fir_Resampler_Norm Chip_Resampler_Downsampler;
int const resampler_extra = 0; //34;
template<class Emu>
class Chip_Resampler_Emu : public Emu {
int last_time;
short* out;
typedef short dsample_t;
enum { disabled_time = -1 };
enum { gain_bits = 14 };
blargg_vector<dsample_t> sample_buf;
int sample_buf_size;
int oversamples_per_frame;
int buf_pos;
int buffered;
int resampler_size;
int gain_;
Chip_Resampler_Downsampler resampler;
void mix_samples( short * buf, int count )
{
dsample_t * inptr = sample_buf.begin();
for ( unsigned i = 0; i < count * 2; i++ )
{
int sample = inptr[i];
sample += buf[i];
if ((short)sample != sample) sample = 0x7FFF ^ (sample >> 31);
buf[i] = sample;
}
}
public:
Chip_Resampler_Emu() { last_time = disabled_time; out = NULL; }
blargg_err_t setup( double oversample, double rolloff, double gain )
{
gain_ = (int) ((1 << gain_bits) * gain);
RETURN_ERR( resampler.set_rate( oversample ) );
return reset_resampler();
}
blargg_err_t reset()
{
Emu::reset();
resampler.clear();
return blargg_ok;
}
blargg_err_t reset_resampler()
{
unsigned int pairs;
double rate = resampler.rate();
if ( rate >= 1.0 ) pairs = 64.0 * rate;
else pairs = 64.0 / rate;
RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
resize( pairs );
resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2);
RETURN_ERR( resampler.resize_buffer( resampler_size ) );
return blargg_ok;
}
void resize( int pairs )
{
int new_sample_buf_size = pairs * 2;
//new_sample_buf_size = new_sample_buf_size / 4 * 4; // TODO: needed only for 3:2 downsampler
if ( sample_buf_size != new_sample_buf_size )
{
if ( (unsigned) new_sample_buf_size > sample_buf.size() )
{
check( false );
return;
}
sample_buf_size = new_sample_buf_size;
oversamples_per_frame = int (pairs * resampler.rate()) * 2 + 2;
clear();
}
}
void clear()
{
buf_pos = buffered = 0;
resampler.clear();
}
void enable( bool b = true ) { last_time = b ? 0 : disabled_time; }
bool enabled() const { return last_time != disabled_time; }
void begin_frame( short* buf ) { out = buf; last_time = 0; }
int run_until( int time )
{
int count = time - last_time;
while ( count > 0 )
{
if ( last_time < 0 )
return false;
last_time = time;
if ( buffered )
{
int samples_to_copy = buffered;
if ( samples_to_copy > count ) samples_to_copy = count;
memcpy( out, sample_buf.begin(), samples_to_copy * sizeof(short) * 2 );
memcpy( sample_buf.begin(), sample_buf.begin() + samples_to_copy * 2, ( buffered - samples_to_copy ) * 2 * sizeof(short) );
buffered -= samples_to_copy;
count -= samples_to_copy;
continue;
}
int sample_count = oversamples_per_frame - resampler.written() + resampler_extra;
memset( resampler.buffer(), 0, sample_count * sizeof(*resampler.buffer()) );
Emu::run( sample_count >> 1, resampler.buffer() );
for ( unsigned i = 0; i < sample_count; i++ )
{
dsample_t * ptr = resampler.buffer() + i;
*ptr = ( *ptr * gain_ ) >> gain_bits;
}
short* p = out;
resampler.write( sample_count );
sample_count = resampler.read( sample_buf.begin(), count * 2 > sample_buf_size ? sample_buf_size : count * 2 ) >> 1;
if ( sample_count > count )
{
out += count * Emu::out_chan_count;
mix_samples( p, count );
memmove( sample_buf.begin(), sample_buf.begin() + count * 2, (sample_count - count) * 2 * sizeof(short) );
buffered = sample_count - count;
return true;
}
else if (!sample_count) return true;
out += sample_count * Emu::out_chan_count;
mix_samples( p, sample_count );
count -= sample_count;
}
return true;
}
};
#endif

View file

@ -1,124 +1,124 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Classic_Emu::Classic_Emu() Classic_Emu::Classic_Emu()
{ {
buf = NULL; buf = NULL;
stereo_buffer = NULL; stereo_buffer = NULL;
voice_types = NULL; voice_types = NULL;
// avoid inconsistency in our duplicated constants // avoid inconsistency in our duplicated constants
assert( (int) wave_type == (int) Multi_Buffer::wave_type ); assert( (int) wave_type == (int) Multi_Buffer::wave_type );
assert( (int) noise_type == (int) Multi_Buffer::noise_type ); assert( (int) noise_type == (int) Multi_Buffer::noise_type );
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type ); assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
} }
Classic_Emu::~Classic_Emu() Classic_Emu::~Classic_Emu()
{ {
delete stereo_buffer; delete stereo_buffer;
delete effects_buffer_; delete effects_buffer_;
effects_buffer_ = NULL; effects_buffer_ = NULL;
} }
void Classic_Emu::set_equalizer_( equalizer_t const& eq ) void Classic_Emu::set_equalizer_( equalizer_t const& eq )
{ {
Music_Emu::set_equalizer_( eq ); Music_Emu::set_equalizer_( eq );
update_eq( eq.treble ); update_eq( eq.treble );
if ( buf ) if ( buf )
buf->bass_freq( (int) equalizer().bass ); buf->bass_freq( (int) equalizer().bass );
} }
blargg_err_t Classic_Emu::set_sample_rate_( int rate ) blargg_err_t Classic_Emu::set_sample_rate_( int rate )
{ {
if ( !buf ) if ( !buf )
{ {
if ( !stereo_buffer ) if ( !stereo_buffer )
CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer ); CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
buf = stereo_buffer; buf = stereo_buffer;
} }
return buf->set_sample_rate( rate, 1000 / 20 ); return buf->set_sample_rate( rate, 1000 / 20 );
} }
void Classic_Emu::mute_voices_( int mask ) void Classic_Emu::mute_voices_( int mask )
{ {
Music_Emu::mute_voices_( mask ); Music_Emu::mute_voices_( mask );
for ( int i = voice_count(); i--; ) for ( int i = voice_count(); i--; )
{ {
if ( mask & (1 << i) ) if ( mask & (1 << i) )
{ {
set_voice( i, NULL, NULL, NULL ); set_voice( i, NULL, NULL, NULL );
} }
else else
{ {
Multi_Buffer::channel_t ch = buf->channel( i ); Multi_Buffer::channel_t ch = buf->channel( i );
assert( (ch.center && ch.left && ch.right) || assert( (ch.center && ch.left && ch.right) ||
(!ch.center && !ch.left && !ch.right) ); // all or nothing (!ch.center && !ch.left && !ch.right) ); // all or nothing
set_voice( i, ch.center, ch.left, ch.right ); set_voice( i, ch.center, ch.left, ch.right );
} }
} }
} }
void Classic_Emu::change_clock_rate( int rate ) void Classic_Emu::change_clock_rate( int rate )
{ {
clock_rate_ = rate; clock_rate_ = rate;
buf->clock_rate( rate ); buf->clock_rate( rate );
} }
blargg_err_t Classic_Emu::setup_buffer( int rate ) blargg_err_t Classic_Emu::setup_buffer( int rate )
{ {
change_clock_rate( rate ); change_clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) ); RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) );
set_equalizer( equalizer() ); set_equalizer( equalizer() );
buf_changed_count = buf->channels_changed_count(); buf_changed_count = buf->channels_changed_count();
return blargg_ok; return blargg_ok;
} }
blargg_err_t Classic_Emu::start_track_( int track ) blargg_err_t Classic_Emu::start_track_( int track )
{ {
RETURN_ERR( Music_Emu::start_track_( track ) ); RETURN_ERR( Music_Emu::start_track_( track ) );
buf->clear(); buf->clear();
return blargg_ok; return blargg_ok;
} }
blargg_err_t Classic_Emu::play_( int count, sample_t out [] ) blargg_err_t Classic_Emu::play_( int count, sample_t out [] )
{ {
// read from buffer, then refill buffer and repeat if necessary // read from buffer, then refill buffer and repeat if necessary
int remain = count; int remain = count;
while ( remain ) while ( remain )
{ {
buf->disable_immediate_removal(); buf->disable_immediate_removal();
remain -= buf->read_samples( &out [count - remain], remain ); remain -= buf->read_samples( &out [count - remain], remain );
if ( remain ) if ( remain )
{ {
if ( buf_changed_count != buf->channels_changed_count() ) if ( buf_changed_count != buf->channels_changed_count() )
{ {
buf_changed_count = buf->channels_changed_count(); buf_changed_count = buf->channels_changed_count();
remute_voices(); remute_voices();
} }
// TODO: use more accurate length calculation // TODO: use more accurate length calculation
int msec = buf->length(); int msec = buf->length();
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100; blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100;
RETURN_ERR( run_clocks( clocks_emulated, msec ) ); RETURN_ERR( run_clocks( clocks_emulated, msec ) );
assert( clocks_emulated ); assert( clocks_emulated );
buf->end_frame( clocks_emulated ); buf->end_frame( clocks_emulated );
} }
} }
return blargg_ok; return blargg_ok;
} }

View file

@ -1,79 +1,79 @@
// Common aspects of emulators which use Blip_Buffer for sound output // Common aspects of emulators which use Blip_Buffer for sound output
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef CLASSIC_EMU_H #ifndef CLASSIC_EMU_H
#define CLASSIC_EMU_H #define CLASSIC_EMU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
#include "Music_Emu.h" #include "Music_Emu.h"
class Classic_Emu : public Music_Emu { class Classic_Emu : public Music_Emu {
protected: protected:
// Derived interface // Derived interface
// Advertises type of sound on each voice, so Effects_Buffer can better choose // Advertises type of sound on each voice, so Effects_Buffer can better choose
// what effect to apply (pan, echo, surround). Constant can have value added so // what effect to apply (pan, echo, surround). Constant can have value added so
// that voices of the same type can be spread around the stereo sound space. // that voices of the same type can be spread around the stereo sound space.
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
void set_voice_types( int const types [] ) { voice_types = types; } void set_voice_types( int const types [] ) { voice_types = types; }
// Sets up Blip_Buffers after loading file // Sets up Blip_Buffers after loading file
blargg_err_t setup_buffer( int clock_rate ); blargg_err_t setup_buffer( int clock_rate );
// Clock rate of Blip_buffers // Clock rate of Blip_buffers
int clock_rate() const { return clock_rate_; } int clock_rate() const { return clock_rate_; }
// Changes clock rate of Blip_Buffers (experimental) // Changes clock rate of Blip_Buffers (experimental)
void change_clock_rate( int ); void change_clock_rate( int );
// Overrides should do the indicated task // Overrides should do the indicated task
// Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL // Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL
virtual void set_voice( int index, Blip_Buffer* center, virtual void set_voice( int index, Blip_Buffer* center,
Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; ) Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; )
// Update equalization // Update equalization
virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; ) virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; )
// Start track // Start track
virtual blargg_err_t start_track_( int track ) BLARGG_PURE( ; ) virtual blargg_err_t start_track_( int track ) BLARGG_PURE( ; )
// Run for at most msec or time_io clocks, then set time_io to number of clocks // Run for at most msec or time_io clocks, then set time_io to number of clocks
// actually run for. After returning, Blip_Buffers have time frame of time_io clocks // actually run for. After returning, Blip_Buffers have time frame of time_io clocks
// ended. // ended.
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; ) virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; )
// Internal // Internal
public: public:
Classic_Emu(); Classic_Emu();
~Classic_Emu(); ~Classic_Emu();
virtual void set_buffer( Multi_Buffer* ); virtual void set_buffer( Multi_Buffer* );
protected: protected:
virtual blargg_err_t set_sample_rate_( int sample_rate ); virtual blargg_err_t set_sample_rate_( int sample_rate );
virtual void mute_voices_( int ); virtual void mute_voices_( int );
virtual void set_equalizer_( equalizer_t const& ); virtual void set_equalizer_( equalizer_t const& );
virtual blargg_err_t play_( int, sample_t [] ); virtual blargg_err_t play_( int, sample_t [] );
private: private:
Multi_Buffer* buf; Multi_Buffer* buf;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer Multi_Buffer* stereo_buffer; // NULL if using custom buffer
int clock_rate_; int clock_rate_;
unsigned buf_changed_count; unsigned buf_changed_count;
int const* voice_types; int const* voice_types;
}; };
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf ) inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
{ {
assert( !buf && new_buf ); assert( !buf && new_buf );
buf = new_buf; buf = new_buf;
} }
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { } inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { }
inline void Classic_Emu::update_eq( blip_eq_t const& ) { } inline void Classic_Emu::update_eq( blip_eq_t const& ) { }
inline blargg_err_t Classic_Emu::run_clocks( blip_time_t&, int ) { return blargg_ok; } inline blargg_err_t Classic_Emu::run_clocks( blip_time_t&, int ) { return blargg_ok; }
#endif #endif

View file

@ -1,315 +1,315 @@
// File_Extractor 0.4.0. http://www.slack.net/~ant/ // File_Extractor 0.4.0. http://www.slack.net/~ant/
#include "Data_Reader.h" #include "Data_Reader.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you /* Copyright (C) 2005-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
const char Data_Reader::eof_error [] = "Unexpected end of file"; const char Data_Reader::eof_error [] = "Unexpected end of file";
blargg_err_t Data_Reader::read( void* p, long s ) blargg_err_t Data_Reader::read( void* p, long s )
{ {
long result = read_avail( p, s ); long result = read_avail( p, s );
if ( result != s ) if ( result != s )
{ {
if ( result >= 0 && result < s ) if ( result >= 0 && result < s )
return eof_error; return eof_error;
return "Read error"; return "Read error";
} }
return 0; return 0;
} }
blargg_err_t Data_Reader::skip( long count ) blargg_err_t Data_Reader::skip( long count )
{ {
char buf [512]; char buf [512];
while ( count ) while ( count )
{ {
long n = sizeof buf; long n = sizeof buf;
if ( n > count ) if ( n > count )
n = count; n = count;
count -= n; count -= n;
RETURN_ERR( read( buf, n ) ); RETURN_ERR( read( buf, n ) );
} }
return 0; return 0;
} }
long File_Reader::remain() const { return size() - tell(); } long File_Reader::remain() const { return size() - tell(); }
blargg_err_t File_Reader::skip( long n ) blargg_err_t File_Reader::skip( long n )
{ {
assert( n >= 0 ); assert( n >= 0 );
if ( !n ) if ( !n )
return 0; return 0;
return seek( tell() + n ); return seek( tell() + n );
} }
// Subset_Reader // Subset_Reader
Subset_Reader::Subset_Reader( Data_Reader* dr, long size ) Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
{ {
in = dr; in = dr;
remain_ = dr->remain(); remain_ = dr->remain();
if ( remain_ > size ) if ( remain_ > size )
remain_ = size; remain_ = size;
} }
long Subset_Reader::remain() const { return remain_; } long Subset_Reader::remain() const { return remain_; }
long Subset_Reader::read_avail( void* p, long s ) long Subset_Reader::read_avail( void* p, long s )
{ {
if ( s > remain_ ) if ( s > remain_ )
s = remain_; s = remain_;
remain_ -= s; remain_ -= s;
return in->read_avail( p, s ); return in->read_avail( p, s );
} }
// Remaining_Reader // Remaining_Reader
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r ) Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
{ {
header = (char const*) h; header = (char const*) h;
header_end = header + size; header_end = header + size;
in = r; in = r;
} }
long Remaining_Reader::remain() const { return header_end - header + in->remain(); } long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
long Remaining_Reader::read_first( void* out, long count ) long Remaining_Reader::read_first( void* out, long count )
{ {
long first = header_end - header; long first = header_end - header;
if ( first ) if ( first )
{ {
if ( first > count ) if ( first > count )
first = count; first = count;
void const* old = header; void const* old = header;
header += first; header += first;
memcpy( out, old, first ); memcpy( out, old, first );
} }
return first; return first;
} }
long Remaining_Reader::read_avail( void* out, long count ) long Remaining_Reader::read_avail( void* out, long count )
{ {
long first = read_first( out, count ); long first = read_first( out, count );
long second = count - first; long second = count - first;
if ( second ) if ( second )
{ {
second = in->read_avail( (char*) out + first, second ); second = in->read_avail( (char*) out + first, second );
if ( second <= 0 ) if ( second <= 0 )
return second; return second;
} }
return first + second; return first + second;
} }
blargg_err_t Remaining_Reader::read( void* out, long count ) blargg_err_t Remaining_Reader::read( void* out, long count )
{ {
long first = read_first( out, count ); long first = read_first( out, count );
long second = count - first; long second = count - first;
if ( !second ) if ( !second )
return 0; return 0;
return in->read( (char*) out + first, second ); return in->read( (char*) out + first, second );
} }
// Mem_File_Reader // Mem_File_Reader
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
begin( (const char*) p ), begin( (const char*) p ),
size_( s ) size_( s )
{ {
pos = 0; pos = 0;
} }
long Mem_File_Reader::size() const { return size_; } long Mem_File_Reader::size() const { return size_; }
long Mem_File_Reader::read_avail( void* p, long s ) long Mem_File_Reader::read_avail( void* p, long s )
{ {
long r = remain(); long r = remain();
if ( s > r ) if ( s > r )
s = r; s = r;
memcpy( p, begin + pos, s ); memcpy( p, begin + pos, s );
pos += s; pos += s;
return s; return s;
} }
long Mem_File_Reader::tell() const { return pos; } long Mem_File_Reader::tell() const { return pos; }
blargg_err_t Mem_File_Reader::seek( long n ) blargg_err_t Mem_File_Reader::seek( long n )
{ {
if ( n > size_ ) if ( n > size_ )
return eof_error; return eof_error;
pos = n; pos = n;
return 0; return 0;
} }
// Callback_Reader // Callback_Reader
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) : Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ), callback( c ),
data( d ) data( d )
{ {
remain_ = size; remain_ = size;
} }
long Callback_Reader::remain() const { return remain_; } long Callback_Reader::remain() const { return remain_; }
long Callback_Reader::read_avail( void* out, long count ) long Callback_Reader::read_avail( void* out, long count )
{ {
if ( count > remain_ ) if ( count > remain_ )
count = remain_; count = remain_;
if ( Callback_Reader::read( out, count ) ) if ( Callback_Reader::read( out, count ) )
count = -1; count = -1;
return count; return count;
} }
blargg_err_t Callback_Reader::read( void* out, long count ) blargg_err_t Callback_Reader::read( void* out, long count )
{ {
if ( count > remain_ ) if ( count > remain_ )
return eof_error; return eof_error;
return callback( data, out, count ); return callback( data, out, count );
} }
// Std_File_Reader // Std_File_Reader
Std_File_Reader::Std_File_Reader() : file_( 0 ) { } Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
Std_File_Reader::~Std_File_Reader() { close(); } Std_File_Reader::~Std_File_Reader() { close(); }
blargg_err_t Std_File_Reader::open( const char* path ) blargg_err_t Std_File_Reader::open( const char* path )
{ {
file_ = fopen( path, "rb" ); file_ = fopen( path, "rb" );
if ( !file_ ) if ( !file_ )
return "Couldn't open file"; return "Couldn't open file";
return 0; return 0;
} }
long Std_File_Reader::size() const long Std_File_Reader::size() const
{ {
long pos = tell(); long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END ); fseek( (FILE*) file_, 0, SEEK_END );
long result = tell(); long result = tell();
fseek( (FILE*) file_, pos, SEEK_SET ); fseek( (FILE*) file_, pos, SEEK_SET );
return result; return result;
} }
long Std_File_Reader::read_avail( void* p, long s ) long Std_File_Reader::read_avail( void* p, long s )
{ {
return fread( p, 1, s, (FILE*) file_ ); return fread( p, 1, s, (FILE*) file_ );
} }
blargg_err_t Std_File_Reader::read( void* p, long s ) blargg_err_t Std_File_Reader::read( void* p, long s )
{ {
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) ) if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
return 0; return 0;
if ( feof( (FILE*) file_ ) ) if ( feof( (FILE*) file_ ) )
return eof_error; return eof_error;
return "Couldn't read from file"; return "Couldn't read from file";
} }
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); } long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }
blargg_err_t Std_File_Reader::seek( long n ) blargg_err_t Std_File_Reader::seek( long n )
{ {
if ( !fseek( (FILE*) file_, n, SEEK_SET ) ) if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
return 0; return 0;
if ( n > size() ) if ( n > size() )
return eof_error; return eof_error;
return "Error seeking in file"; return "Error seeking in file";
} }
void Std_File_Reader::close() void Std_File_Reader::close()
{ {
if ( file_ ) if ( file_ )
{ {
fclose( (FILE*) file_ ); fclose( (FILE*) file_ );
file_ = 0; file_ = 0;
} }
} }
// Gzip_File_Reader // Gzip_File_Reader
#ifdef HAVE_ZLIB_H #ifdef HAVE_ZLIB_H
#include "zlib.h" #include "zlib.h"
static const char* get_gzip_eof( const char* path, long* eof ) static const char* get_gzip_eof( const char* path, long* eof )
{ {
FILE* file = fopen( path, "rb" ); FILE* file = fopen( path, "rb" );
if ( !file ) if ( !file )
return "Couldn't open file"; return "Couldn't open file";
unsigned char buf [4]; unsigned char buf [4];
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B ) if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{ {
fseek( file, -4, SEEK_END ); fseek( file, -4, SEEK_END );
fread( buf, 4, 1, file ); fread( buf, 4, 1, file );
*eof = get_le32( buf ); *eof = get_le32( buf );
} }
else else
{ {
fseek( file, 0, SEEK_END ); fseek( file, 0, SEEK_END );
*eof = ftell( file ); *eof = ftell( file );
} }
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0; const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
fclose( file ); fclose( file );
return err; return err;
} }
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { } Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
Gzip_File_Reader::~Gzip_File_Reader() { close(); } Gzip_File_Reader::~Gzip_File_Reader() { close(); }
blargg_err_t Gzip_File_Reader::open( const char* path ) blargg_err_t Gzip_File_Reader::open( const char* path )
{ {
close(); close();
RETURN_ERR( get_gzip_eof( path, &size_ ) ); RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" ); file_ = gzopen( path, "rb" );
if ( !file_ ) if ( !file_ )
return "Couldn't open file"; return "Couldn't open file";
return 0; return 0;
} }
long Gzip_File_Reader::size() const { return size_; } long Gzip_File_Reader::size() const { return size_; }
long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); } long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); }
long Gzip_File_Reader::tell() const { return gztell( file_ ); } long Gzip_File_Reader::tell() const { return gztell( file_ ); }
blargg_err_t Gzip_File_Reader::seek( long n ) blargg_err_t Gzip_File_Reader::seek( long n )
{ {
if ( gzseek( file_, n, SEEK_SET ) >= 0 ) if ( gzseek( file_, n, SEEK_SET ) >= 0 )
return 0; return 0;
if ( n > size_ ) if ( n > size_ )
return eof_error; return eof_error;
return "Error seeking in file"; return "Error seeking in file";
} }
void Gzip_File_Reader::close() void Gzip_File_Reader::close()
{ {
if ( file_ ) if ( file_ )
{ {
gzclose( file_ ); gzclose( file_ );
file_ = 0; file_ = 0;
} }
} }
#endif #endif

View file

@ -1,151 +1,151 @@
// Data reader interface for uniform access // Data reader interface for uniform access
// File_Extractor 0.4.0 // File_Extractor 0.4.0
#ifndef DATA_READER_H #ifndef DATA_READER_H
#define DATA_READER_H #define DATA_READER_H
#include "blargg_common.h" #include "blargg_common.h"
// Supports reading and finding out how many bytes are remaining // Supports reading and finding out how many bytes are remaining
class Data_Reader { class Data_Reader {
public: public:
virtual ~Data_Reader() { } virtual ~Data_Reader() { }
static const char eof_error []; // returned by read() when request goes beyond end static const char eof_error []; // returned by read() when request goes beyond end
// Read at most count bytes and return number actually read, or <= 0 if error // Read at most count bytes and return number actually read, or <= 0 if error
virtual long read_avail( void*, long n ) = 0; virtual long read_avail( void*, long n ) = 0;
// Read exactly count bytes and return error if they couldn't be read // Read exactly count bytes and return error if they couldn't be read
virtual blargg_err_t read( void*, long count ); virtual blargg_err_t read( void*, long count );
// Number of bytes remaining until end of file // Number of bytes remaining until end of file
virtual long remain() const = 0; virtual long remain() const = 0;
// Read and discard count bytes // Read and discard count bytes
virtual blargg_err_t skip( long count ); virtual blargg_err_t skip( long count );
public: public:
Data_Reader() { } Data_Reader() { }
typedef blargg_err_t error_t; // deprecated typedef blargg_err_t error_t; // deprecated
private: private:
// noncopyable // noncopyable
Data_Reader( const Data_Reader& ); Data_Reader( const Data_Reader& );
Data_Reader& operator = ( const Data_Reader& ); Data_Reader& operator = ( const Data_Reader& );
}; };
// Supports seeking in addition to Data_Reader operations // Supports seeking in addition to Data_Reader operations
class File_Reader : public Data_Reader { class File_Reader : public Data_Reader {
public: public:
// Size of file // Size of file
virtual long size() const = 0; virtual long size() const = 0;
// Current position in file // Current position in file
virtual long tell() const = 0; virtual long tell() const = 0;
// Go to new position // Go to new position
virtual blargg_err_t seek( long ) = 0; virtual blargg_err_t seek( long ) = 0;
long remain() const; long remain() const;
blargg_err_t skip( long n ); blargg_err_t skip( long n );
}; };
// Disk file reader // Disk file reader
class Std_File_Reader : public File_Reader { class Std_File_Reader : public File_Reader {
public: public:
blargg_err_t open( const char* path ); blargg_err_t open( const char* path );
void close(); void close();
public: public:
Std_File_Reader(); Std_File_Reader();
~Std_File_Reader(); ~Std_File_Reader();
long size() const; long size() const;
blargg_err_t read( void*, long ); blargg_err_t read( void*, long );
long read_avail( void*, long ); long read_avail( void*, long );
long tell() const; long tell() const;
blargg_err_t seek( long ); blargg_err_t seek( long );
private: private:
void* file_; void* file_;
}; };
// Treats range of memory as a file // Treats range of memory as a file
class Mem_File_Reader : public File_Reader { class Mem_File_Reader : public File_Reader {
public: public:
Mem_File_Reader( const void*, long size ); Mem_File_Reader( const void*, long size );
public: public:
long size() const; long size() const;
long read_avail( void*, long ); long read_avail( void*, long );
long tell() const; long tell() const;
blargg_err_t seek( long ); blargg_err_t seek( long );
private: private:
const char* const begin; const char* const begin;
const long size_; const long size_;
long pos; long pos;
}; };
// Makes it look like there are only count bytes remaining // Makes it look like there are only count bytes remaining
class Subset_Reader : public Data_Reader { class Subset_Reader : public Data_Reader {
public: public:
Subset_Reader( Data_Reader*, long count ); Subset_Reader( Data_Reader*, long count );
public: public:
long remain() const; long remain() const;
long read_avail( void*, long ); long read_avail( void*, long );
private: private:
Data_Reader* in; Data_Reader* in;
long remain_; long remain_;
}; };
// Joins already-read header and remaining data into original file (to avoid seeking) // Joins already-read header and remaining data into original file (to avoid seeking)
class Remaining_Reader : public Data_Reader { class Remaining_Reader : public Data_Reader {
public: public:
Remaining_Reader( void const* header, long size, Data_Reader* ); Remaining_Reader( void const* header, long size, Data_Reader* );
public: public:
long remain() const; long remain() const;
long read_avail( void*, long ); long read_avail( void*, long );
blargg_err_t read( void*, long ); blargg_err_t read( void*, long );
private: private:
char const* header; char const* header;
char const* header_end; char const* header_end;
Data_Reader* in; Data_Reader* in;
long read_first( void* out, long count ); long read_first( void* out, long count );
}; };
// Invokes callback function to read data. Size of data must be specified in advance. // Invokes callback function to read data. Size of data must be specified in advance.
class Callback_Reader : public Data_Reader { class Callback_Reader : public Data_Reader {
public: public:
typedef const char* (*callback_t)( void* data, void* out, long count ); typedef const char* (*callback_t)( void* data, void* out, long count );
Callback_Reader( callback_t, long size, void* data = 0 ); Callback_Reader( callback_t, long size, void* data = 0 );
public: public:
long read_avail( void*, long ); long read_avail( void*, long );
blargg_err_t read( void*, long ); blargg_err_t read( void*, long );
long remain() const; long remain() const;
private: private:
callback_t const callback; callback_t const callback;
void* const data; void* const data;
long remain_; long remain_;
}; };
#ifdef HAVE_ZLIB_H #ifdef HAVE_ZLIB_H
// Gzip compressed file reader // Gzip compressed file reader
class Gzip_File_Reader : public File_Reader { class Gzip_File_Reader : public File_Reader {
public: public:
blargg_err_t open( const char* path ); blargg_err_t open( const char* path );
void close(); void close();
public: public:
Gzip_File_Reader(); Gzip_File_Reader();
~Gzip_File_Reader(); ~Gzip_File_Reader();
long size() const; long size() const;
long read_avail( void*, long ); long read_avail( void*, long );
long tell() const; long tell() const;
blargg_err_t seek( long ); blargg_err_t seek( long );
private: private:
void* file_; void* file_;
long size_; long size_;
}; };
#endif #endif
#endif #endif

View file

@ -1,74 +1,74 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Downsampler.h" #include "Downsampler.h"
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you /* Copyright (C) 2004-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const shift = 14; int const shift = 14;
int const unit = 1 << shift; int const unit = 1 << shift;
void Downsampler::clear_() void Downsampler::clear_()
{ {
pos = 0; pos = 0;
Resampler::clear_(); Resampler::clear_();
} }
Downsampler::Downsampler() Downsampler::Downsampler()
{ {
clear(); clear();
} }
blargg_err_t Downsampler::set_rate_( double new_factor ) blargg_err_t Downsampler::set_rate_( double new_factor )
{ {
step = (int) (new_factor * unit + 0.5); step = (int) (new_factor * unit + 0.5);
return Resampler::set_rate_( 1.0 / unit * step ); return Resampler::set_rate_( 1.0 / unit * step );
} }
Resampler::sample_t const* Downsampler::resample_( sample_t** out_, Resampler::sample_t const* Downsampler::resample_( sample_t** out_,
sample_t const* out_end, sample_t const in [], int in_size ) sample_t const* out_end, sample_t const in [], int in_size )
{ {
in_size -= write_offset; in_size -= write_offset;
if ( in_size > 0 ) if ( in_size > 0 )
{ {
sample_t* BLARGG_RESTRICT out = *out_; sample_t* BLARGG_RESTRICT out = *out_;
sample_t const* const in_end = in + in_size; sample_t const* const in_end = in + in_size;
int const step = this->step; int const step = this->step;
int pos = this->pos; int pos = this->pos;
// TODO: IIR filter, then linear resample // TODO: IIR filter, then linear resample
// TODO: detect skipped sample, allowing merging of IIR and resample? // TODO: detect skipped sample, allowing merging of IIR and resample?
do do
{ {
#define INTERP( i, out )\ #define INTERP( i, out )\
out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\ out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\
in [8 + i] * pos) >> (shift + 2); in [8 + i] * pos) >> (shift + 2);
int out_0; int out_0;
INTERP( 0, out_0 ) INTERP( 0, out_0 )
INTERP( 1, out [0] = out_0; out [1] ) INTERP( 1, out [0] = out_0; out [1] )
out += stereo; out += stereo;
pos += step; pos += step;
in += ((unsigned) pos >> shift) * stereo; in += ((unsigned) pos >> shift) * stereo;
pos &= unit - 1; pos &= unit - 1;
} }
while ( in < in_end && out < out_end ); while ( in < in_end && out < out_end );
this->pos = pos; this->pos = pos;
*out_ = out; *out_ = out;
} }
return in; return in;
} }

View file

@ -1,25 +1,25 @@
// Linear downsampler with pre-low-pass // Linear downsampler with pre-low-pass
// $package // $package
#ifndef DOWNSAMPLER_H #ifndef DOWNSAMPLER_H
#define DOWNSAMPLER_H #define DOWNSAMPLER_H
#include "Resampler.h" #include "Resampler.h"
class Downsampler : public Resampler { class Downsampler : public Resampler {
public: public:
Downsampler(); Downsampler();
protected: protected:
virtual blargg_err_t set_rate_( double ); virtual blargg_err_t set_rate_( double );
virtual void clear_(); virtual void clear_();
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int ); virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
private: private:
enum { stereo = 2 }; enum { stereo = 2 };
enum { write_offset = 8 * stereo }; enum { write_offset = 8 * stereo };
int pos; int pos;
int step; int step;
}; };
#endif #endif

View file

@ -1,61 +1,61 @@
// Combination of Fir_Resampler and Stereo_Buffer mixing. Used by Sega FM emulators. // Combination of Fir_Resampler and Stereo_Buffer mixing. Used by Sega FM emulators.
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef DUAL_RESAMPLER_H #ifndef DUAL_RESAMPLER_H
#define DUAL_RESAMPLER_H #define DUAL_RESAMPLER_H
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
#if GME_VGM_FAST_RESAMPLER #if GME_VGM_FAST_RESAMPLER
#include "Downsampler.h" #include "Downsampler.h"
typedef Downsampler Dual_Resampler_Downsampler; typedef Downsampler Dual_Resampler_Downsampler;
#else #else
#include "Fir_Resampler.h" #include "Fir_Resampler.h"
typedef Fir_Resampler_Norm Dual_Resampler_Downsampler; typedef Fir_Resampler_Norm Dual_Resampler_Downsampler;
#endif #endif
class Dual_Resampler { class Dual_Resampler {
public: public:
typedef short dsample_t; typedef short dsample_t;
blargg_err_t setup( double oversample, double rolloff, double gain ); blargg_err_t setup( double oversample, double rolloff, double gain );
double rate() const { return resampler.rate(); } double rate() const { return resampler.rate(); }
blargg_err_t reset( int max_pairs ); blargg_err_t reset( int max_pairs );
void resize( int pairs_per_frame ); void resize( int pairs_per_frame );
void clear(); void clear();
void dual_play( int count, dsample_t out [], Stereo_Buffer&, Stereo_Buffer** secondary_buf_set = NULL, int secondary_buf_set_count = 0 ); void dual_play( int count, dsample_t out [], Stereo_Buffer&, Stereo_Buffer** secondary_buf_set = NULL, int secondary_buf_set_count = 0 );
blargg_callback<int (*)( void*, blip_time_t, int, dsample_t* )> set_callback; blargg_callback<int (*)( void*, blip_time_t, int, dsample_t* )> set_callback;
// Implementation // Implementation
public: public:
Dual_Resampler(); Dual_Resampler();
~Dual_Resampler(); ~Dual_Resampler();
private: private:
enum { gain_bits = 14 }; enum { gain_bits = 14 };
blargg_vector<dsample_t> sample_buf; blargg_vector<dsample_t> sample_buf;
int sample_buf_size; int sample_buf_size;
int oversamples_per_frame; int oversamples_per_frame;
int buf_pos; int buf_pos;
int buffered; int buffered;
int resampler_size; int resampler_size;
int gain_; int gain_;
Dual_Resampler_Downsampler resampler; Dual_Resampler_Downsampler resampler;
void mix_samples( Stereo_Buffer&, dsample_t [], int, Stereo_Buffer**, int ); void mix_samples( Stereo_Buffer&, dsample_t [], int, Stereo_Buffer**, int );
void mix_mono( Stereo_Buffer&, dsample_t [], int ); void mix_mono( Stereo_Buffer&, dsample_t [], int );
void mix_stereo( Stereo_Buffer&, dsample_t [], int ); void mix_stereo( Stereo_Buffer&, dsample_t [], int );
void mix_extra_mono( Stereo_Buffer&, dsample_t [], int ); void mix_extra_mono( Stereo_Buffer&, dsample_t [], int );
void mix_extra_stereo( Stereo_Buffer&, dsample_t [], int ); void mix_extra_stereo( Stereo_Buffer&, dsample_t [], int );
int play_frame_( Stereo_Buffer&, dsample_t [], Stereo_Buffer**, int ); int play_frame_( Stereo_Buffer&, dsample_t [], Stereo_Buffer**, int );
}; };
inline blargg_err_t Dual_Resampler::setup( double oversample, double rolloff, double gain ) inline blargg_err_t Dual_Resampler::setup( double oversample, double rolloff, double gain )
{ {
gain_ = (int) ((1 << gain_bits) * gain); gain_ = (int) ((1 << gain_bits) * gain);
return resampler.set_rate( oversample ); return resampler.set_rate( oversample );
} }
#endif #endif

View file

@ -18,13 +18,13 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#ifdef BLARGG_ENABLE_OPTIMIZER #ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER #include BLARGG_ENABLE_OPTIMIZER
#endif #endif
int const fixed_shift = 12; int const fixed_shift = 12;
#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift)) #define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift))
#define FROM_FIXED( f ) ((f) >> fixed_shift) #define FROM_FIXED( f ) ((f) >> fixed_shift)
int const max_read = 2560; // determines minimum delay int const max_read = 2560; // determines minimum delay
Effects_Buffer::Effects_Buffer( int max_bufs, int echo_size_ ) : Multi_Buffer( stereo ) Effects_Buffer::Effects_Buffer( int max_bufs, int echo_size_ ) : Multi_Buffer( stereo )
{ {
echo_size = max( max_read * (int) stereo, echo_size_ & ~1 ); echo_size = max( max_read * (int) stereo, echo_size_ & ~1 );

View file

@ -1,149 +1,149 @@
// Multi-channel effects buffer with echo and individual panning for each channel // Multi-channel effects buffer with echo and individual panning for each channel
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef EFFECTS_BUFFER_H #ifndef EFFECTS_BUFFER_H
#define EFFECTS_BUFFER_H #define EFFECTS_BUFFER_H
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
// See Simple_Effects_Buffer (below) for a simpler interface // See Simple_Effects_Buffer (below) for a simpler interface
class Effects_Buffer : public Multi_Buffer { class Effects_Buffer : public Multi_Buffer {
public: public:
// To reduce memory usage, fewer buffers can be used (with a best-fit // To reduce memory usage, fewer buffers can be used (with a best-fit
// approach if there are too few), and maximum echo delay can be reduced // approach if there are too few), and maximum echo delay can be reduced
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 ); Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 );
struct pan_vol_t struct pan_vol_t
{ {
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal
float pan; // -1.0 = left, 0.0 = center, +1.0 = right float pan; // -1.0 = left, 0.0 = center, +1.0 = right
}; };
// Global configuration // Global configuration
struct config_t struct config_t
{ {
bool enabled; // false = disable all effects bool enabled; // false = disable all effects
// Current sound is echoed at adjustable left/right delay, // Current sound is echoed at adjustable left/right delay,
// with reduced treble and volume (feedback). // with reduced treble and volume (feedback).
float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent
int delay [2]; // left, right delays (msec) int delay [2]; // left, right delays (msec)
float feedback; // 0.0 = no echo, 0.5 = each echo half previous, 1.0 = cacophony float feedback; // 0.0 = no echo, 0.5 = each echo half previous, 1.0 = cacophony
pan_vol_t side_chans [2]; // left and right side channel volume and pan pan_vol_t side_chans [2]; // left and right side channel volume and pan
}; };
config_t& config() { return config_; } config_t& config() { return config_; }
// Limits of delay (msec) // Limits of delay (msec)
int min_delay() const; int min_delay() const;
int max_delay() const; int max_delay() const;
// Per-channel configuration. Two or more channels with matching parameters are // Per-channel configuration. Two or more channels with matching parameters are
// optimized to internally use the same buffer. // optimized to internally use the same buffer.
struct chan_config_t : pan_vol_t struct chan_config_t : pan_vol_t
{ {
// (inherited from pan_vol_t) // (inherited from pan_vol_t)
//float vol; // these only affect center channel //float vol; // these only affect center channel
//float pan; //float pan;
bool surround; // if true, negates left volume to put sound in back bool surround; // if true, negates left volume to put sound in back
bool echo; // false = channel doesn't have any echo bool echo; // false = channel doesn't have any echo
}; };
chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; } chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; }
// Applies any changes made to config() and chan_config() // Applies any changes made to config() and chan_config()
virtual void apply_config(); virtual void apply_config();
// Implementation // Implementation
public: public:
~Effects_Buffer(); ~Effects_Buffer();
blargg_err_t set_sample_rate( int samples_per_sec, int msec = blip_default_length ); blargg_err_t set_sample_rate( int samples_per_sec, int msec = blip_default_length );
blargg_err_t set_channel_count( int, int const* = NULL ); blargg_err_t set_channel_count( int, int const* = NULL );
void clock_rate( int ); void clock_rate( int );
void bass_freq( int ); void bass_freq( int );
void clear(); void clear();
channel_t channel( int ); channel_t channel( int );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
int read_samples( blip_sample_t [], int ); int read_samples( blip_sample_t [], int );
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; } int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
enum { stereo = 2 }; enum { stereo = 2 };
typedef int fixed_t; typedef int fixed_t;
protected: protected:
enum { extra_chans = stereo * stereo }; enum { extra_chans = stereo * stereo };
private: private:
config_t config_; config_t config_;
int clock_rate_; int clock_rate_;
int bass_freq_; int bass_freq_;
int echo_size; int echo_size;
struct chan_t struct chan_t
{ {
fixed_t vol [stereo]; fixed_t vol [stereo];
chan_config_t cfg; chan_config_t cfg;
channel_t channel; channel_t channel;
}; };
blargg_vector<chan_t> chans; blargg_vector<chan_t> chans;
struct buf_t : Tracked_Blip_Buffer struct buf_t : Tracked_Blip_Buffer
{ {
// nasty: Blip_Buffer has something called fixed_t // nasty: Blip_Buffer has something called fixed_t
Effects_Buffer::fixed_t vol [stereo]; Effects_Buffer::fixed_t vol [stereo];
bool echo; bool echo;
void* operator new ( size_t, void* p ) { return p; } void* operator new ( size_t, void* p ) { return p; }
void operator delete ( void* ) { } void operator delete ( void* ) { }
~buf_t() { } ~buf_t() { }
}; };
buf_t* bufs; buf_t* bufs;
int bufs_size; int bufs_size;
int bufs_max; // bufs_size <= bufs_max, to limit memory usage int bufs_max; // bufs_size <= bufs_max, to limit memory usage
Stereo_Mixer mixer; Stereo_Mixer mixer;
struct { struct {
int delay [stereo]; int delay [stereo];
fixed_t treble; fixed_t treble;
fixed_t feedback; fixed_t feedback;
fixed_t low_pass [stereo]; fixed_t low_pass [stereo];
} s; } s;
blargg_vector<fixed_t> echo; blargg_vector<fixed_t> echo;
int echo_pos; int echo_pos;
bool no_effects; bool no_effects;
bool no_echo; bool no_echo;
void assign_buffers(); void assign_buffers();
void clear_echo(); void clear_echo();
void mix_effects( blip_sample_t out [], int pair_count ); void mix_effects( blip_sample_t out [], int pair_count );
blargg_err_t new_bufs( int size ); blargg_err_t new_bufs( int size );
void delete_bufs(); void delete_bufs();
}; };
// Simpler interface and lower memory usage // Simpler interface and lower memory usage
class Simple_Effects_Buffer : public Effects_Buffer { class Simple_Effects_Buffer : public Effects_Buffer {
public: public:
struct config_t struct config_t
{ {
bool enabled; // false = disable all effects bool enabled; // false = disable all effects
float echo; // 0.0 = none, 1.0 = lots float echo; // 0.0 = none, 1.0 = lots
float stereo; // 0.0 = channels in center, 1.0 = channels on left/right float stereo; // 0.0 = channels in center, 1.0 = channels on left/right
bool surround; // true = put some channels in back bool surround; // true = put some channels in back
}; };
config_t& config() { return config_; } config_t& config() { return config_; }
// Applies any changes made to config() // Applies any changes made to config()
void apply_config(); void apply_config();
// Implementation // Implementation
public: public:
Simple_Effects_Buffer(); Simple_Effects_Buffer();
private: private:
config_t config_; config_t config_;
void chan_config(); // hide void chan_config(); // hide
}; };
#endif #endif

View file

@ -1,123 +1,123 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Fir_Resampler.h" #include "Fir_Resampler.h"
#include <math.h> #include <math.h>
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you /* Copyright (C) 2004-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#undef PI #undef PI
#define PI 3.1415926535897932384626433832795029 #define PI 3.1415926535897932384626433832795029
static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale, static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
int count, short* out ) int count, short* out )
{ {
double const maxh = 256; double const maxh = 256;
double const step = PI / maxh * spacing; double const step = PI / maxh * spacing;
double const to_w = maxh * 2 / width; double const to_w = maxh * 2 / width;
double const pow_a_n = pow( rolloff, maxh ); double const pow_a_n = pow( rolloff, maxh );
scale /= maxh * 2; scale /= maxh * 2;
double angle = (count / 2 - 1 + offset) * -step; double angle = (count / 2 - 1 + offset) * -step;
while ( count-- ) while ( count-- )
{ {
*out++ = 0; *out++ = 0;
double w = angle * to_w; double w = angle * to_w;
if ( fabs( w ) < PI ) if ( fabs( w ) < PI )
{ {
double rolloff_cos_a = rolloff * cos( angle ); double rolloff_cos_a = rolloff * cos( angle );
double num = 1 - rolloff_cos_a - double num = 1 - rolloff_cos_a -
pow_a_n * cos( maxh * angle ) + pow_a_n * cos( maxh * angle ) +
pow_a_n * rolloff * cos( (maxh - 1) * angle ); pow_a_n * rolloff * cos( (maxh - 1) * angle );
double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
double sinc = scale * num / den - scale; double sinc = scale * num / den - scale;
out [-1] = (short) (cos( w ) * sinc + sinc); out [-1] = (short) (cos( w ) * sinc + sinc);
} }
angle += step; angle += step;
} }
} }
Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) : Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) :
width_( width ), width_( width ),
impulses( impulses_ ) impulses( impulses_ )
{ {
imp = NULL; imp = NULL;
} }
void Fir_Resampler_::clear_() void Fir_Resampler_::clear_()
{ {
imp = impulses; imp = impulses;
Resampler::clear_(); Resampler::clear_();
} }
blargg_err_t Fir_Resampler_::set_rate_( double new_factor ) blargg_err_t Fir_Resampler_::set_rate_( double new_factor )
{ {
double const rolloff = 0.999; double const rolloff = 0.999;
double const gain = 1.0; double const gain = 1.0;
// determine number of sub-phases that yield lowest error // determine number of sub-phases that yield lowest error
double ratio_ = 0.0; double ratio_ = 0.0;
int res = -1; int res = -1;
{ {
double least_error = 2; double least_error = 2;
double pos = 0; double pos = 0;
for ( int r = 1; r <= max_res; r++ ) for ( int r = 1; r <= max_res; r++ )
{ {
pos += new_factor; pos += new_factor;
double nearest = floor( pos + 0.5 ); double nearest = floor( pos + 0.5 );
double error = fabs( pos - nearest ); double error = fabs( pos - nearest );
if ( error < least_error ) if ( error < least_error )
{ {
res = r; res = r;
ratio_ = nearest / res; ratio_ = nearest / res;
least_error = error; least_error = error;
} }
} }
} }
RETURN_ERR( Resampler::set_rate_( ratio_ ) ); RETURN_ERR( Resampler::set_rate_( ratio_ ) );
// how much of input is used for each output sample // how much of input is used for each output sample
int const step = stereo * (int) floor( ratio_ ); int const step = stereo * (int) floor( ratio_ );
double fraction = fmod( ratio_, 1.0 ); double fraction = fmod( ratio_, 1.0 );
double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_; double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
double pos = 0.0; double pos = 0.0;
//int input_per_cycle = 0; //int input_per_cycle = 0;
sample_t* out = impulses; sample_t* out = impulses;
for ( int n = res; --n >= 0; ) for ( int n = res; --n >= 0; )
{ {
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter, gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
double (0x7FFF * gain * filter), (int) width_, out ); double (0x7FFF * gain * filter), (int) width_, out );
out += width_; out += width_;
int cur_step = step; int cur_step = step;
pos += fraction; pos += fraction;
if ( pos >= 0.9999999 ) if ( pos >= 0.9999999 )
{ {
pos -= 1.0; pos -= 1.0;
cur_step += stereo; cur_step += stereo;
} }
*out++ = (cur_step - width_ * 2 + 4) * sizeof (sample_t); *out++ = (cur_step - width_ * 2 + 4) * sizeof (sample_t);
*out++ = 4 * sizeof (sample_t); *out++ = 4 * sizeof (sample_t);
//input_per_cycle += cur_step; //input_per_cycle += cur_step;
} }
// last offset moves back to beginning of impulses // last offset moves back to beginning of impulses
out [-1] -= (char*) out - (char*) impulses; out [-1] -= (char*) out - (char*) impulses;
imp = impulses; imp = impulses;
return blargg_ok; return blargg_ok;
} }

View file

@ -1,101 +1,101 @@
// Finite impulse response (FIR) resampler with adjustable FIR size // Finite impulse response (FIR) resampler with adjustable FIR size
// $package // $package
#ifndef FIR_RESAMPLER_H #ifndef FIR_RESAMPLER_H
#define FIR_RESAMPLER_H #define FIR_RESAMPLER_H
#include "Resampler.h" #include "Resampler.h"
template<int width> template<int width>
class Fir_Resampler; class Fir_Resampler;
// Use one of these typedefs // Use one of these typedefs
typedef Fir_Resampler< 8> Fir_Resampler_Fast; typedef Fir_Resampler< 8> Fir_Resampler_Fast;
typedef Fir_Resampler<16> Fir_Resampler_Norm; typedef Fir_Resampler<16> Fir_Resampler_Norm;
typedef Fir_Resampler<24> Fir_Resampler_Good; typedef Fir_Resampler<24> Fir_Resampler_Good;
// Implementation // Implementation
class Fir_Resampler_ : public Resampler { class Fir_Resampler_ : public Resampler {
protected: protected:
virtual blargg_err_t set_rate_( double ); virtual blargg_err_t set_rate_( double );
virtual void clear_(); virtual void clear_();
protected: protected:
enum { stereo = 2 }; enum { stereo = 2 };
enum { max_res = 32 }; // TODO: eliminate and keep impulses on freestore? enum { max_res = 32 }; // TODO: eliminate and keep impulses on freestore?
sample_t const* imp; sample_t const* imp;
int const width_; int const width_;
sample_t* impulses; sample_t* impulses;
Fir_Resampler_( int width, sample_t [] ); Fir_Resampler_( int width, sample_t [] );
}; };
// Width is number of points in FIR. More points give better quality and // Width is number of points in FIR. More points give better quality and
// rolloff effectiveness, and take longer to calculate. // rolloff effectiveness, and take longer to calculate.
template<int width> template<int width>
class Fir_Resampler : public Fir_Resampler_ { class Fir_Resampler : public Fir_Resampler_ {
enum { min_width = (width < 4 ? 4 : width) }; enum { min_width = (width < 4 ? 4 : width) };
enum { adj_width = min_width / 4 * 4 + 2 }; enum { adj_width = min_width / 4 * 4 + 2 };
enum { write_offset = adj_width * stereo }; enum { write_offset = adj_width * stereo };
short impulses [max_res * (adj_width + 2)]; short impulses [max_res * (adj_width + 2)];
public: public:
Fir_Resampler() : Fir_Resampler_( adj_width, impulses ) { } Fir_Resampler() : Fir_Resampler_( adj_width, impulses ) { }
protected: protected:
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int ); virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
}; };
template<int width> template<int width>
Resampler::sample_t const* Fir_Resampler<width>::resample_( sample_t** out_, Resampler::sample_t const* Fir_Resampler<width>::resample_( sample_t** out_,
sample_t const* out_end, sample_t const in [], int in_size ) sample_t const* out_end, sample_t const in [], int in_size )
{ {
in_size -= write_offset; in_size -= write_offset;
if ( in_size > 0 ) if ( in_size > 0 )
{ {
sample_t* BLARGG_RESTRICT out = *out_; sample_t* BLARGG_RESTRICT out = *out_;
sample_t const* const in_end = in + in_size; sample_t const* const in_end = in + in_size;
sample_t const* imp = this->imp; sample_t const* imp = this->imp;
do do
{ {
// accumulate in extended precision // accumulate in extended precision
int pt = imp [0]; int pt = imp [0];
int l = pt * in [0]; int l = pt * in [0];
int r = pt * in [1]; int r = pt * in [1];
if ( out >= out_end ) if ( out >= out_end )
break; break;
for ( int n = (adj_width - 2) / 2; n; --n ) for ( int n = (adj_width - 2) / 2; n; --n )
{ {
pt = imp [1]; pt = imp [1];
l += pt * in [2]; l += pt * in [2];
r += pt * in [3]; r += pt * in [3];
// pre-increment more efficient on some RISC processors // pre-increment more efficient on some RISC processors
imp += 2; imp += 2;
pt = imp [0]; pt = imp [0];
r += pt * in [5]; r += pt * in [5];
in += 4; in += 4;
l += pt * in [0]; l += pt * in [0];
} }
pt = imp [1]; pt = imp [1];
l += pt * in [2]; l += pt * in [2];
r += pt * in [3]; r += pt * in [3];
// these two "samples" after the end of the impulse give the // these two "samples" after the end of the impulse give the
// proper offsets to the next input sample and next impulse // proper offsets to the next input sample and next impulse
in = (sample_t const*) ((char const*) in + imp [2]); // some negative value in = (sample_t const*) ((char const*) in + imp [2]); // some negative value
imp = (sample_t const*) ((char const*) imp + imp [3]); // small positive or large negative imp = (sample_t const*) ((char const*) imp + imp [3]); // small positive or large negative
out [0] = sample_t (l >> 15); out [0] = sample_t (l >> 15);
out [1] = sample_t (r >> 15); out [1] = sample_t (r >> 15);
out += 2; out += 2;
} }
while ( in < in_end ); while ( in < in_end );
this->imp = imp; this->imp = imp;
*out_ = out; *out_ = out;
} }
return in; return in;
} }
#endif #endif

View file

@ -1,407 +1,407 @@
// Gb_Snd_Emu $vers. http://www.slack.net/~ant/ // Gb_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Gb_Apu.h" #include "Gb_Apu.h"
//#include "gb_apu_logger.h" //#include "gb_apu_logger.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const vol_reg = 0xFF24; int const vol_reg = 0xFF24;
int const stereo_reg = 0xFF25; int const stereo_reg = 0xFF25;
int const status_reg = 0xFF26; int const status_reg = 0xFF26;
int const wave_ram = 0xFF30; int const wave_ram = 0xFF30;
int const power_mask = 0x80; int const power_mask = 0x80;
void Gb_Apu::treble_eq( blip_eq_t const& eq ) void Gb_Apu::treble_eq( blip_eq_t const& eq )
{ {
norm_synth.treble_eq( eq ); norm_synth.treble_eq( eq );
fast_synth.treble_eq( eq ); fast_synth.treble_eq( eq );
} }
inline int Gb_Apu::calc_output( int osc ) const inline int Gb_Apu::calc_output( int osc ) const
{ {
int bits = regs [stereo_reg - io_addr] >> osc; int bits = regs [stereo_reg - io_addr] >> osc;
return (bits >> 3 & 2) | (bits & 1); return (bits >> 3 & 2) | (bits & 1);
} }
void Gb_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) void Gb_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{ {
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
require( !center || (center && !left && !right) || (center && left && right) ); require( !center || (center && !left && !right) || (center && left && right) );
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
if ( !center || !left || !right ) if ( !center || !left || !right )
{ {
left = center; left = center;
right = center; right = center;
} }
Gb_Osc& o = *oscs [i]; Gb_Osc& o = *oscs [i];
o.outputs [1] = right; o.outputs [1] = right;
o.outputs [2] = left; o.outputs [2] = left;
o.outputs [3] = center; o.outputs [3] = center;
o.output = o.outputs [calc_output( i )]; o.output = o.outputs [calc_output( i )];
} }
void Gb_Apu::synth_volume( int iv ) void Gb_Apu::synth_volume( int iv )
{ {
double v = volume_ * 0.60 / osc_count / 15 /*steps*/ / 8 /*master vol range*/ * iv; double v = volume_ * 0.60 / osc_count / 15 /*steps*/ / 8 /*master vol range*/ * iv;
norm_synth.volume( v ); norm_synth.volume( v );
fast_synth.volume( v ); fast_synth.volume( v );
} }
void Gb_Apu::apply_volume() void Gb_Apu::apply_volume()
{ {
// TODO: Doesn't handle differing left and right volumes (panning). // TODO: Doesn't handle differing left and right volumes (panning).
// Not worth the complexity. // Not worth the complexity.
int data = regs [vol_reg - io_addr]; int data = regs [vol_reg - io_addr];
int left = data >> 4 & 7; int left = data >> 4 & 7;
int right = data & 7; int right = data & 7;
//if ( data & 0x88 ) dprintf( "Vin: %02X\n", data & 0x88 ); //if ( data & 0x88 ) dprintf( "Vin: %02X\n", data & 0x88 );
//if ( left != right ) dprintf( "l: %d r: %d\n", left, right ); //if ( left != right ) dprintf( "l: %d r: %d\n", left, right );
synth_volume( max( left, right ) + 1 ); synth_volume( max( left, right ) + 1 );
} }
void Gb_Apu::volume( double v ) void Gb_Apu::volume( double v )
{ {
if ( volume_ != v ) if ( volume_ != v )
{ {
volume_ = v; volume_ = v;
apply_volume(); apply_volume();
} }
} }
void Gb_Apu::reset_regs() void Gb_Apu::reset_regs()
{ {
for ( int i = 0; i < 0x20; i++ ) for ( int i = 0; i < 0x20; i++ )
regs [i] = 0; regs [i] = 0;
square1.reset(); square1.reset();
square2.reset(); square2.reset();
wave .reset(); wave .reset();
noise .reset(); noise .reset();
apply_volume(); apply_volume();
} }
void Gb_Apu::reset_lengths() void Gb_Apu::reset_lengths()
{ {
square1.length_ctr = 64; square1.length_ctr = 64;
square2.length_ctr = 64; square2.length_ctr = 64;
wave .length_ctr = 256; wave .length_ctr = 256;
noise .length_ctr = 64; noise .length_ctr = 64;
} }
void Gb_Apu::reduce_clicks( bool reduce ) void Gb_Apu::reduce_clicks( bool reduce )
{ {
reduce_clicks_ = reduce; reduce_clicks_ = reduce;
// Click reduction makes DAC off generate same output as volume 0 // Click reduction makes DAC off generate same output as volume 0
int dac_off_amp = 0; int dac_off_amp = 0;
if ( reduce && wave.mode != mode_agb ) // AGB already eliminates clicks if ( reduce && wave.mode != mode_agb ) // AGB already eliminates clicks
dac_off_amp = -Gb_Osc::dac_bias; dac_off_amp = -Gb_Osc::dac_bias;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
oscs [i]->dac_off_amp = dac_off_amp; oscs [i]->dac_off_amp = dac_off_amp;
// AGB always eliminates clicks on wave channel using same method // AGB always eliminates clicks on wave channel using same method
if ( wave.mode == mode_agb ) if ( wave.mode == mode_agb )
wave.dac_off_amp = -Gb_Osc::dac_bias; wave.dac_off_amp = -Gb_Osc::dac_bias;
} }
void Gb_Apu::reset( mode_t mode, bool agb_wave ) void Gb_Apu::reset( mode_t mode, bool agb_wave )
{ {
// Hardware mode // Hardware mode
if ( agb_wave ) if ( agb_wave )
mode = mode_agb; // using AGB wave features implies AGB hardware mode = mode_agb; // using AGB wave features implies AGB hardware
wave.agb_mask = agb_wave ? 0xFF : 0; wave.agb_mask = agb_wave ? 0xFF : 0;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
oscs [i]->mode = mode; oscs [i]->mode = mode;
reduce_clicks( reduce_clicks_ ); reduce_clicks( reduce_clicks_ );
// Reset state // Reset state
frame_time = 0; frame_time = 0;
last_time = 0; last_time = 0;
frame_phase = 0; frame_phase = 0;
reset_regs(); reset_regs();
reset_lengths(); reset_lengths();
// Load initial wave RAM // Load initial wave RAM
static byte const initial_wave [2] [16] = { static byte const initial_wave [2] [16] = {
{0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C,0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA}, {0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C,0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA},
{0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF}, {0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF},
}; };
for ( int b = 2; --b >= 0; ) for ( int b = 2; --b >= 0; )
{ {
// Init both banks (does nothing if not in AGB mode) // Init both banks (does nothing if not in AGB mode)
// TODO: verify that this works // TODO: verify that this works
write_register( 0, 0xFF1A, b * 0x40 ); write_register( 0, 0xFF1A, b * 0x40 );
for ( unsigned i = 0; i < sizeof initial_wave [0]; i++ ) for ( unsigned i = 0; i < sizeof initial_wave [0]; i++ )
write_register( 0, i + wave_ram, initial_wave [(mode != mode_dmg)] [i] ); write_register( 0, i + wave_ram, initial_wave [(mode != mode_dmg)] [i] );
} }
} }
void Gb_Apu::set_tempo( double t ) void Gb_Apu::set_tempo( double t )
{ {
frame_period = 4194304 / 512; // 512 Hz frame_period = 4194304 / 512; // 512 Hz
if ( t != 1.0 ) if ( t != 1.0 )
frame_period = t ? blip_time_t (frame_period / t) : blip_time_t(0); frame_period = t ? blip_time_t (frame_period / t) : blip_time_t(0);
} }
Gb_Apu::Gb_Apu() Gb_Apu::Gb_Apu()
{ {
wave.wave_ram = &regs [wave_ram - io_addr]; wave.wave_ram = &regs [wave_ram - io_addr];
oscs [0] = &square1; oscs [0] = &square1;
oscs [1] = &square2; oscs [1] = &square2;
oscs [2] = &wave; oscs [2] = &wave;
oscs [3] = &noise; oscs [3] = &noise;
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
Gb_Osc& o = *oscs [i]; Gb_Osc& o = *oscs [i];
o.regs = &regs [i * 5]; o.regs = &regs [i * 5];
o.output = NULL; o.output = NULL;
o.outputs [0] = NULL; o.outputs [0] = NULL;
o.outputs [1] = NULL; o.outputs [1] = NULL;
o.outputs [2] = NULL; o.outputs [2] = NULL;
o.outputs [3] = NULL; o.outputs [3] = NULL;
o.norm_synth = &norm_synth; o.norm_synth = &norm_synth;
o.fast_synth = &fast_synth; o.fast_synth = &fast_synth;
} }
reduce_clicks_ = false; reduce_clicks_ = false;
set_tempo( 1.0 ); set_tempo( 1.0 );
volume_ = 1.0; volume_ = 1.0;
reset(); reset();
} }
void Gb_Apu::run_until_( blip_time_t end_time ) void Gb_Apu::run_until_( blip_time_t end_time )
{ {
if ( !frame_period ) if ( !frame_period )
frame_time += end_time - last_time; frame_time += end_time - last_time;
while ( true ) while ( true )
{ {
// run oscillators // run oscillators
blip_time_t time = end_time; blip_time_t time = end_time;
if ( time > frame_time ) if ( time > frame_time )
time = frame_time; time = frame_time;
square1.run( last_time, time ); square1.run( last_time, time );
square2.run( last_time, time ); square2.run( last_time, time );
wave .run( last_time, time ); wave .run( last_time, time );
noise .run( last_time, time ); noise .run( last_time, time );
last_time = time; last_time = time;
if ( time == end_time ) if ( time == end_time )
break; break;
// run frame sequencer // run frame sequencer
assert( frame_period ); assert( frame_period );
frame_time += frame_period * Gb_Osc::clk_mul; frame_time += frame_period * Gb_Osc::clk_mul;
switch ( frame_phase++ ) switch ( frame_phase++ )
{ {
case 2: case 2:
case 6: case 6:
// 128 Hz // 128 Hz
square1.clock_sweep(); square1.clock_sweep();
case 0: case 0:
case 4: case 4:
// 256 Hz // 256 Hz
square1.clock_length(); square1.clock_length();
square2.clock_length(); square2.clock_length();
wave .clock_length(); wave .clock_length();
noise .clock_length(); noise .clock_length();
break; break;
case 7: case 7:
// 64 Hz // 64 Hz
frame_phase = 0; frame_phase = 0;
square1.clock_envelope(); square1.clock_envelope();
square2.clock_envelope(); square2.clock_envelope();
noise .clock_envelope(); noise .clock_envelope();
} }
} }
} }
inline void Gb_Apu::run_until( blip_time_t time ) inline void Gb_Apu::run_until( blip_time_t time )
{ {
require( time >= last_time ); // end_time must not be before previous time require( time >= last_time ); // end_time must not be before previous time
if ( time > last_time ) if ( time > last_time )
run_until_( time ); run_until_( time );
} }
void Gb_Apu::end_frame( blip_time_t end_time ) void Gb_Apu::end_frame( blip_time_t end_time )
{ {
#ifdef LOG_FRAME #ifdef LOG_FRAME
LOG_FRAME( end_time ); LOG_FRAME( end_time );
#endif #endif
if ( end_time > last_time ) if ( end_time > last_time )
run_until( end_time ); run_until( end_time );
frame_time -= end_time; frame_time -= end_time;
assert( frame_time >= 0 ); assert( frame_time >= 0 );
last_time -= end_time; last_time -= end_time;
assert( last_time >= 0 ); assert( last_time >= 0 );
} }
void Gb_Apu::silence_osc( Gb_Osc& o ) void Gb_Apu::silence_osc( Gb_Osc& o )
{ {
int delta = -o.last_amp; int delta = -o.last_amp;
if ( reduce_clicks_ ) if ( reduce_clicks_ )
delta += o.dac_off_amp; delta += o.dac_off_amp;
if ( delta ) if ( delta )
{ {
o.last_amp = o.dac_off_amp; o.last_amp = o.dac_off_amp;
if ( o.output ) if ( o.output )
{ {
o.output->set_modified(); o.output->set_modified();
fast_synth.offset( last_time, delta, o.output ); fast_synth.offset( last_time, delta, o.output );
} }
} }
} }
void Gb_Apu::apply_stereo() void Gb_Apu::apply_stereo()
{ {
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
Gb_Osc& o = *oscs [i]; Gb_Osc& o = *oscs [i];
Blip_Buffer* out = o.outputs [calc_output( i )]; Blip_Buffer* out = o.outputs [calc_output( i )];
if ( o.output != out ) if ( o.output != out )
{ {
silence_osc( o ); silence_osc( o );
o.output = out; o.output = out;
} }
} }
} }
void Gb_Apu::write_register( blip_time_t time, int addr, int data ) void Gb_Apu::write_register( blip_time_t time, int addr, int data )
{ {
require( (unsigned) data < 0x100 ); require( (unsigned) data < 0x100 );
int reg = addr - io_addr; int reg = addr - io_addr;
if ( (unsigned) reg >= io_size ) if ( (unsigned) reg >= io_size )
{ {
require( false ); require( false );
return; return;
} }
#ifdef LOG_WRITE #ifdef LOG_WRITE
LOG_WRITE( time, addr, data ); LOG_WRITE( time, addr, data );
#endif #endif
if ( addr < status_reg && !(regs [status_reg - io_addr] & power_mask) ) if ( addr < status_reg && !(regs [status_reg - io_addr] & power_mask) )
{ {
// Power is off // Power is off
// length counters can only be written in DMG mode // length counters can only be written in DMG mode
if ( wave.mode != mode_dmg || (reg != 1 && reg != 5+1 && reg != 10+1 && reg != 15+1) ) if ( wave.mode != mode_dmg || (reg != 1 && reg != 5+1 && reg != 10+1 && reg != 15+1) )
return; return;
if ( reg < 10 ) if ( reg < 10 )
data &= 0x3F; // clear square duty data &= 0x3F; // clear square duty
} }
run_until( time ); run_until( time );
if ( addr >= wave_ram ) if ( addr >= wave_ram )
{ {
wave.write( addr, data ); wave.write( addr, data );
} }
else else
{ {
int old_data = regs [reg]; int old_data = regs [reg];
regs [reg] = data; regs [reg] = data;
if ( addr < vol_reg ) if ( addr < vol_reg )
{ {
// Oscillator // Oscillator
write_osc( reg, old_data, data ); write_osc( reg, old_data, data );
} }
else if ( addr == vol_reg && data != old_data ) else if ( addr == vol_reg && data != old_data )
{ {
// Master volume // Master volume
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
silence_osc( *oscs [i] ); silence_osc( *oscs [i] );
apply_volume(); apply_volume();
} }
else if ( addr == stereo_reg ) else if ( addr == stereo_reg )
{ {
// Stereo panning // Stereo panning
apply_stereo(); apply_stereo();
} }
else if ( addr == status_reg && (data ^ old_data) & power_mask ) else if ( addr == status_reg && (data ^ old_data) & power_mask )
{ {
// Power control // Power control
frame_phase = 0; frame_phase = 0;
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
silence_osc( *oscs [i] ); silence_osc( *oscs [i] );
reset_regs(); reset_regs();
if ( wave.mode != mode_dmg ) if ( wave.mode != mode_dmg )
reset_lengths(); reset_lengths();
regs [status_reg - io_addr] = data; regs [status_reg - io_addr] = data;
} }
} }
} }
int Gb_Apu::read_register( blip_time_t time, int addr ) int Gb_Apu::read_register( blip_time_t time, int addr )
{ {
if ( addr >= status_reg ) if ( addr >= status_reg )
run_until( time ); run_until( time );
int reg = addr - io_addr; int reg = addr - io_addr;
if ( (unsigned) reg >= io_size ) if ( (unsigned) reg >= io_size )
{ {
require( false ); require( false );
return 0; return 0;
} }
if ( addr >= wave_ram ) if ( addr >= wave_ram )
return wave.read( addr ); return wave.read( addr );
// Value read back has some bits always set // Value read back has some bits always set
static byte const masks [] = { static byte const masks [] = {
0x80,0x3F,0x00,0xFF,0xBF, 0x80,0x3F,0x00,0xFF,0xBF,
0xFF,0x3F,0x00,0xFF,0xBF, 0xFF,0x3F,0x00,0xFF,0xBF,
0x7F,0xFF,0x9F,0xFF,0xBF, 0x7F,0xFF,0x9F,0xFF,0xBF,
0xFF,0xFF,0x00,0x00,0xBF, 0xFF,0xFF,0x00,0x00,0xBF,
0x00,0x00,0x70, 0x00,0x00,0x70,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
}; };
int mask = masks [reg]; int mask = masks [reg];
if ( wave.agb_mask && (reg == 10 || reg == 12) ) if ( wave.agb_mask && (reg == 10 || reg == 12) )
mask = 0x1F; // extra implemented bits in wave regs on AGB mask = 0x1F; // extra implemented bits in wave regs on AGB
int data = regs [reg] | mask; int data = regs [reg] | mask;
// Status register // Status register
if ( addr == status_reg ) if ( addr == status_reg )
{ {
data &= 0xF0; data &= 0xF0;
data |= (int) square1.enabled << 0; data |= (int) square1.enabled << 0;
data |= (int) square2.enabled << 1; data |= (int) square2.enabled << 1;
data |= (int) wave .enabled << 2; data |= (int) wave .enabled << 2;
data |= (int) noise .enabled << 3; data |= (int) noise .enabled << 3;
} }
return data; return data;
} }

View file

@ -1,193 +1,193 @@
// Nintendo Game Boy sound hardware emulator with save state support // Nintendo Game Boy sound hardware emulator with save state support
// Gb_Snd_Emu $vers // Gb_Snd_Emu $vers
#ifndef GB_APU_H #ifndef GB_APU_H
#define GB_APU_H #define GB_APU_H
#include "Gb_Oscs.h" #include "Gb_Oscs.h"
struct gb_apu_state_t; struct gb_apu_state_t;
class Gb_Apu { class Gb_Apu {
public: public:
// Basics // Basics
// Sets buffer(s) to generate sound into, or NULL to mute. If only center is not NULL, // Sets buffer(s) to generate sound into, or NULL to mute. If only center is not NULL,
// output is mono. // output is mono.
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
// Emulates to time t, then writes data to addr // Emulates to time t, then writes data to addr
void write_register( blip_time_t t, int addr, int data ); void write_register( blip_time_t t, int addr, int data );
// Emulates to time t, then subtracts t from the current time. // Emulates to time t, then subtracts t from the current time.
// OK if previous write call had time slightly after t. // OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); void end_frame( blip_time_t t );
// More features // More features
// Clock rate sound hardware runs at // Clock rate sound hardware runs at
enum { clock_rate = 4194304 * GB_APU_OVERCLOCK }; enum { clock_rate = 4194304 * GB_APU_OVERCLOCK };
// Registers are at io_addr to io_addr+io_size-1 // Registers are at io_addr to io_addr+io_size-1
enum { io_addr = 0xFF10 }; enum { io_addr = 0xFF10 };
enum { io_size = 0x30 }; enum { io_size = 0x30 };
// Emulates to time t, then reads from addr // Emulates to time t, then reads from addr
int read_register( blip_time_t t, int addr ); int read_register( blip_time_t t, int addr );
// Resets hardware to state after power, BEFORE boot ROM runs. Mode selects // Resets hardware to state after power, BEFORE boot ROM runs. Mode selects
// sound hardware. If agb_wave is true, enables AGB's extra wave features. // sound hardware. If agb_wave is true, enables AGB's extra wave features.
enum mode_t { enum mode_t {
mode_dmg, // Game Boy monochrome mode_dmg, // Game Boy monochrome
mode_cgb, // Game Boy Color mode_cgb, // Game Boy Color
mode_agb // Game Boy Advance mode_agb // Game Boy Advance
}; };
void reset( mode_t mode = mode_cgb, bool agb_wave = false ); void reset( mode_t mode = mode_cgb, bool agb_wave = false );
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
// 0: Square 1, 1: Square 2, 2: Wave, 3: Noise // 0: Square 1, 1: Square 2, 2: Wave, 3: Noise
enum { osc_count = 4 }; // 0 <= chan < osc_count enum { osc_count = 4 }; // 0 <= chan < osc_count
void set_output( int chan, Blip_Buffer* center, void set_output( int chan, Blip_Buffer* center,
Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
// Sets overall volume, where 1.0 is normal // Sets overall volume, where 1.0 is normal
void volume( double ); void volume( double );
// Sets treble equalization // Sets treble equalization
void treble_eq( blip_eq_t const& ); void treble_eq( blip_eq_t const& );
// Treble and bass values for various hardware. // Treble and bass values for various hardware.
enum { enum {
speaker_treble = -47, // speaker on system speaker_treble = -47, // speaker on system
speaker_bass = 2000, speaker_bass = 2000,
dmg_treble = 0, // headphones on each system dmg_treble = 0, // headphones on each system
dmg_bass = 30, dmg_bass = 30,
cgb_treble = 0, cgb_treble = 0,
cgb_bass = 300, // CGB has much less bass cgb_bass = 300, // CGB has much less bass
agb_treble = 0, agb_treble = 0,
agb_bass = 30 agb_bass = 30
}; };
// If true, reduces clicking by disabling DAC biasing. Note that this reduces // If true, reduces clicking by disabling DAC biasing. Note that this reduces
// emulation accuracy, since the clicks are authentic. // emulation accuracy, since the clicks are authentic.
void reduce_clicks( bool reduce = true ); void reduce_clicks( bool reduce = true );
// Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the // Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the
// tempo in a music player. // tempo in a music player.
void set_tempo( double ); void set_tempo( double );
// Saves full emulation state to state_out. Data format is portable and // Saves full emulation state to state_out. Data format is portable and
// includes some extra space to avoid expansion in case more state needs // includes some extra space to avoid expansion in case more state needs
// to be stored in the future. // to be stored in the future.
void save_state( gb_apu_state_t* state_out ); void save_state( gb_apu_state_t* state_out );
// Loads state. You should call reset() BEFORE this. // Loads state. You should call reset() BEFORE this.
blargg_err_t load_state( gb_apu_state_t const& in ); blargg_err_t load_state( gb_apu_state_t const& in );
private: private:
// noncopyable // noncopyable
Gb_Apu( const Gb_Apu& ); Gb_Apu( const Gb_Apu& );
Gb_Apu& operator = ( const Gb_Apu& ); Gb_Apu& operator = ( const Gb_Apu& );
// Implementation // Implementation
public: public:
Gb_Apu(); Gb_Apu();
// Use set_output() in place of these // Use set_output() in place of these
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); ) BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); )
BLARGG_DEPRECATED( void output ( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ); ) BLARGG_DEPRECATED( void output ( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ); )
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ) { set_output( i, c, c, c ); } ) BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ) { set_output( i, c, c, c ); } )
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( i, c, l, r ); } ) BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( i, c, l, r ); } )
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0xFF10 }; ) BLARGG_DEPRECATED_TEXT( enum { start_addr = 0xFF10 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0xFF3F }; ) BLARGG_DEPRECATED_TEXT( enum { end_addr = 0xFF3F }; )
BLARGG_DEPRECATED_TEXT( enum { register_count = end_addr - start_addr + 1 }; ) BLARGG_DEPRECATED_TEXT( enum { register_count = end_addr - start_addr + 1 }; )
private: private:
Gb_Osc* oscs [osc_count]; Gb_Osc* oscs [osc_count];
blip_time_t last_time; // time sound emulator has been run to blip_time_t last_time; // time sound emulator has been run to
blip_time_t frame_period; // clocks between each frame sequencer step blip_time_t frame_period; // clocks between each frame sequencer step
double volume_; double volume_;
bool reduce_clicks_; bool reduce_clicks_;
Gb_Sweep_Square square1; Gb_Sweep_Square square1;
Gb_Square square2; Gb_Square square2;
Gb_Wave wave; Gb_Wave wave;
Gb_Noise noise; Gb_Noise noise;
blip_time_t frame_time; // time of next frame sequencer action blip_time_t frame_time; // time of next frame sequencer action
int frame_phase; // phase of next frame sequencer step int frame_phase; // phase of next frame sequencer step
enum { regs_size = io_size + 0x10 }; enum { regs_size = io_size + 0x10 };
BOOST::uint8_t regs [regs_size];// last values written to registers BOOST::uint8_t regs [regs_size];// last values written to registers
// large objects after everything else // large objects after everything else
Blip_Synth_Norm norm_synth; Blip_Synth_Norm norm_synth;
Blip_Synth_Fast fast_synth; Blip_Synth_Fast fast_synth;
void reset_lengths(); void reset_lengths();
void reset_regs(); void reset_regs();
int calc_output( int osc ) const; int calc_output( int osc ) const;
void apply_stereo(); void apply_stereo();
void apply_volume(); void apply_volume();
void synth_volume( int ); void synth_volume( int );
void run_until_( blip_time_t ); void run_until_( blip_time_t );
void run_until( blip_time_t ); void run_until( blip_time_t );
void silence_osc( Gb_Osc& ); void silence_osc( Gb_Osc& );
void write_osc( int reg, int old_data, int data ); void write_osc( int reg, int old_data, int data );
const char* save_load( gb_apu_state_t*, bool save ); const char* save_load( gb_apu_state_t*, bool save );
void save_load2( gb_apu_state_t*, bool save ); void save_load2( gb_apu_state_t*, bool save );
friend class Gb_Apu2; friend class Gb_Apu2;
}; };
// Format of save state. Should be stable across versions of the library, // Format of save state. Should be stable across versions of the library,
// with earlier versions properly opening later save states. Includes some // with earlier versions properly opening later save states. Includes some
// room for expansion so the state size shouldn't increase. // room for expansion so the state size shouldn't increase.
struct gb_apu_state_t struct gb_apu_state_t
{ {
#if GB_APU_CUSTOM_STATE #if GB_APU_CUSTOM_STATE
// Values stored as plain int so your code can read/write them easily. // Values stored as plain int so your code can read/write them easily.
// Structure can NOT be written to disk, since format is not portable. // Structure can NOT be written to disk, since format is not portable.
typedef int val_t; typedef int val_t;
#else #else
// Values written in portable little-endian format, allowing structure // Values written in portable little-endian format, allowing structure
// to be written directly to disk. // to be written directly to disk.
typedef unsigned char val_t [4]; typedef unsigned char val_t [4];
#endif #endif
enum { format0 = 0x50414247 }; // 'GBAP' enum { format0 = 0x50414247 }; // 'GBAP'
val_t format; // format of all following data val_t format; // format of all following data
val_t version; // later versions just add fields to end val_t version; // later versions just add fields to end
unsigned char regs [0x40]; unsigned char regs [0x40];
val_t frame_time; val_t frame_time;
val_t frame_phase; val_t frame_phase;
val_t sweep_freq; val_t sweep_freq;
val_t sweep_delay; val_t sweep_delay;
val_t sweep_enabled; val_t sweep_enabled;
val_t sweep_neg; val_t sweep_neg;
val_t noise_divider; val_t noise_divider;
val_t wave_buf; val_t wave_buf;
val_t delay [4]; val_t delay [4];
val_t length_ctr [4]; val_t length_ctr [4];
val_t phase [4]; val_t phase [4];
val_t enabled [4]; val_t enabled [4];
val_t env_delay [3]; val_t env_delay [3];
val_t env_volume [3]; val_t env_volume [3];
val_t env_enabled [3]; val_t env_enabled [3];
val_t unused [13]; // for future expansion val_t unused [13]; // for future expansion
}; };
inline void Gb_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) inline void Gb_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{ {
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
set_output( i, c, l, r ); set_output( i, c, l, r );
} }
BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c ) { set_output( c, c, c ); } ) BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c ) { set_output( c, c, c ); } )
BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( c, l, r ); } ) BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( c, l, r ); } )
#endif #endif

View file

@ -1,51 +1,51 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gb_Cpu.h" #include "Gb_Cpu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
inline void Gb_Cpu::set_code_page( int i, void* p ) inline void Gb_Cpu::set_code_page( int i, void* p )
{ {
byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size ); byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size );
cpu_state_.code_map [i] = p2; cpu_state_.code_map [i] = p2;
cpu_state->code_map [i] = p2; cpu_state->code_map [i] = p2;
} }
void Gb_Cpu::reset( void* unmapped ) void Gb_Cpu::reset( void* unmapped )
{ {
check( cpu_state == &cpu_state_ ); check( cpu_state == &cpu_state_ );
cpu_state = &cpu_state_; cpu_state = &cpu_state_;
cpu_state_.time = 0; cpu_state_.time = 0;
for ( int i = 0; i < page_count + 1; ++i ) for ( int i = 0; i < page_count + 1; ++i )
set_code_page( i, unmapped ); set_code_page( i, unmapped );
memset( &r, 0, sizeof r ); memset( &r, 0, sizeof r );
blargg_verify_byte_order(); blargg_verify_byte_order();
} }
void Gb_Cpu::map_code( addr_t start, int size, void* data ) void Gb_Cpu::map_code( addr_t start, int size, void* data )
{ {
// address range must begin and end on page boundaries // address range must begin and end on page boundaries
require( start % page_size == 0 ); require( start % page_size == 0 );
require( size % page_size == 0 ); require( size % page_size == 0 );
require( start + size <= mem_size ); require( start + size <= mem_size );
for ( int offset = 0; offset < size; offset += page_size ) for ( int offset = 0; offset < size; offset += page_size )
set_code_page( (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset ); set_code_page( (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset );
} }

View file

@ -1,82 +1,82 @@
// Nintendo Game Boy CPU emulator // Nintendo Game Boy CPU emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef GB_CPU_H #ifndef GB_CPU_H
#define GB_CPU_H #define GB_CPU_H
#include "blargg_common.h" #include "blargg_common.h"
class Gb_Cpu { class Gb_Cpu {
public: public:
typedef int addr_t; typedef int addr_t;
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
enum { mem_size = 0x10000 }; enum { mem_size = 0x10000 };
// Clears registers and map all pages to unmapped // Clears registers and map all pages to unmapped
void reset( void* unmapped = NULL ); void reset( void* unmapped = NULL );
// Maps code memory (memory accessed via the program counter). Start and size // Maps code memory (memory accessed via the program counter). Start and size
// must be multiple of page_size. // must be multiple of page_size.
enum { page_bits = 13 }; enum { page_bits = 13 };
enum { page_size = 1 << page_bits }; enum { page_size = 1 << page_bits };
void map_code( addr_t start, int size, void* code ); void map_code( addr_t start, int size, void* code );
// Accesses emulated memory as CPU does // Accesses emulated memory as CPU does
byte* get_code( addr_t ); byte* get_code( addr_t );
// Game Boy Z-80 registers. NOT kept updated during emulation. // Game Boy Z-80 registers. NOT kept updated during emulation.
struct core_regs_t { struct core_regs_t {
BOOST::uint16_t bc, de, hl, fa; BOOST::uint16_t bc, de, hl, fa;
}; };
struct registers_t : core_regs_t { struct registers_t : core_regs_t {
int pc; // more than 16 bits to allow overflow detection int pc; // more than 16 bits to allow overflow detection
BOOST::uint16_t sp; BOOST::uint16_t sp;
}; };
registers_t r; registers_t r;
// Base address for RST vectors, to simplify GBS player (normally 0) // Base address for RST vectors, to simplify GBS player (normally 0)
addr_t rst_base; addr_t rst_base;
// Current time. // Current time.
int time() const { return cpu_state->time; } int time() const { return cpu_state->time; }
// Changes time. Must not be called during emulation. // Changes time. Must not be called during emulation.
// Should be negative, because emulation stops once it becomes >= 0. // Should be negative, because emulation stops once it becomes >= 0.
void set_time( int t ) { cpu_state->time = t; } void set_time( int t ) { cpu_state->time = t; }
// Emulator reads this many bytes past end of a page // Emulator reads this many bytes past end of a page
enum { cpu_padding = 8 }; enum { cpu_padding = 8 };
// Implementation // Implementation
public: public:
Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; } Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; }
enum { page_count = mem_size >> page_bits }; enum { page_count = mem_size >> page_bits };
struct cpu_state_t { struct cpu_state_t {
byte* code_map [page_count + 1]; byte* code_map [page_count + 1];
int time; int time;
}; };
cpu_state_t* cpu_state; // points to state_ or a local copy within run() cpu_state_t* cpu_state; // points to state_ or a local copy within run()
cpu_state_t cpu_state_; cpu_state_t cpu_state_;
private: private:
void set_code_page( int, void* ); void set_code_page( int, void* );
}; };
#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits) #define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits)
#if BLARGG_NONPORTABLE #if BLARGG_NONPORTABLE
#define GB_CPU_OFFSET( addr ) (addr) #define GB_CPU_OFFSET( addr ) (addr)
#else #else
#define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1)) #define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1))
#endif #endif
inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr ) inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr )
{ {
return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr ); return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr );
} }
#endif #endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,188 +1,188 @@
// Private oscillators used by Gb_Apu // Private oscillators used by Gb_Apu
// Gb_Snd_Emu $vers // Gb_Snd_Emu $vers
#ifndef GB_OSCS_H #ifndef GB_OSCS_H
#define GB_OSCS_H #define GB_OSCS_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
#ifndef GB_APU_OVERCLOCK #ifndef GB_APU_OVERCLOCK
#define GB_APU_OVERCLOCK 1 #define GB_APU_OVERCLOCK 1
#endif #endif
#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1) #if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1)
#error "GB_APU_OVERCLOCK must be a power of 2" #error "GB_APU_OVERCLOCK must be a power of 2"
#endif #endif
class Gb_Osc { class Gb_Osc {
protected: protected:
// 11-bit frequency in NRx3 and NRx4 // 11-bit frequency in NRx3 and NRx4
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; } int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
void update_amp( blip_time_t, int new_amp ); void update_amp( blip_time_t, int new_amp );
int write_trig( int frame_phase, int max_len, int old_data ); int write_trig( int frame_phase, int max_len, int old_data );
public: public:
enum { clk_mul = GB_APU_OVERCLOCK }; enum { clk_mul = GB_APU_OVERCLOCK };
enum { dac_bias = 7 }; enum { dac_bias = 7 };
Blip_Buffer* outputs [4];// NULL, right, left, center Blip_Buffer* outputs [4];// NULL, right, left, center
Blip_Buffer* output; // where to output sound Blip_Buffer* output; // where to output sound
BOOST::uint8_t* regs; // osc's 5 registers BOOST::uint8_t* regs; // osc's 5 registers
int mode; // mode_dmg, mode_cgb, mode_agb int mode; // mode_dmg, mode_cgb, mode_agb
int dac_off_amp;// amplitude when DAC is off int dac_off_amp;// amplitude when DAC is off
int last_amp; // current amplitude in Blip_Buffer int last_amp; // current amplitude in Blip_Buffer
Blip_Synth_Norm const* norm_synth; Blip_Synth_Norm const* norm_synth;
Blip_Synth_Fast const* fast_synth; Blip_Synth_Fast const* fast_synth;
int delay; // clocks until frequency timer expires int delay; // clocks until frequency timer expires
int length_ctr; // length counter int length_ctr; // length counter
unsigned phase; // waveform phase (or equivalent) unsigned phase; // waveform phase (or equivalent)
bool enabled; // internal enabled flag bool enabled; // internal enabled flag
void clock_length(); void clock_length();
void reset(); void reset();
}; };
class Gb_Env : public Gb_Osc { class Gb_Env : public Gb_Osc {
public: public:
int env_delay; int env_delay;
int volume; int volume;
bool env_enabled; bool env_enabled;
void clock_envelope(); void clock_envelope();
bool write_register( int frame_phase, int reg, int old_data, int data ); bool write_register( int frame_phase, int reg, int old_data, int data );
void reset() void reset()
{ {
env_delay = 0; env_delay = 0;
volume = 0; volume = 0;
Gb_Osc::reset(); Gb_Osc::reset();
} }
protected: protected:
// Non-zero if DAC is enabled // Non-zero if DAC is enabled
int dac_enabled() const { return regs [2] & 0xF8; } int dac_enabled() const { return regs [2] & 0xF8; }
private: private:
void zombie_volume( int old, int data ); void zombie_volume( int old, int data );
int reload_env_timer(); int reload_env_timer();
}; };
class Gb_Square : public Gb_Env { class Gb_Square : public Gb_Env {
public: public:
bool write_register( int frame_phase, int reg, int old_data, int data ); bool write_register( int frame_phase, int reg, int old_data, int data );
void run( blip_time_t, blip_time_t ); void run( blip_time_t, blip_time_t );
void reset() void reset()
{ {
Gb_Env::reset(); Gb_Env::reset();
delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger) delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger)
} }
private: private:
// Frequency timer period // Frequency timer period
int period() const { return (2048 - frequency()) * (4 * clk_mul); } int period() const { return (2048 - frequency()) * (4 * clk_mul); }
}; };
class Gb_Sweep_Square : public Gb_Square { class Gb_Sweep_Square : public Gb_Square {
public: public:
int sweep_freq; int sweep_freq;
int sweep_delay; int sweep_delay;
bool sweep_enabled; bool sweep_enabled;
bool sweep_neg; bool sweep_neg;
void clock_sweep(); void clock_sweep();
void write_register( int frame_phase, int reg, int old_data, int data ); void write_register( int frame_phase, int reg, int old_data, int data );
void reset() void reset()
{ {
sweep_freq = 0; sweep_freq = 0;
sweep_delay = 0; sweep_delay = 0;
sweep_enabled = false; sweep_enabled = false;
sweep_neg = false; sweep_neg = false;
Gb_Square::reset(); Gb_Square::reset();
} }
private: private:
enum { period_mask = 0x70 }; enum { period_mask = 0x70 };
enum { shift_mask = 0x07 }; enum { shift_mask = 0x07 };
void calc_sweep( bool update ); void calc_sweep( bool update );
void reload_sweep_timer(); void reload_sweep_timer();
}; };
class Gb_Noise : public Gb_Env { class Gb_Noise : public Gb_Env {
public: public:
int divider; // noise has more complex frequency divider setup int divider; // noise has more complex frequency divider setup
void run( blip_time_t, blip_time_t ); void run( blip_time_t, blip_time_t );
void write_register( int frame_phase, int reg, int old_data, int data ); void write_register( int frame_phase, int reg, int old_data, int data );
void reset() void reset()
{ {
divider = 0; divider = 0;
Gb_Env::reset(); Gb_Env::reset();
delay = 4 * clk_mul; // TODO: remove? delay = 4 * clk_mul; // TODO: remove?
} }
private: private:
enum { period2_mask = 0x1FFFF }; enum { period2_mask = 0x1FFFF };
int period2_index() const { return regs [3] >> 4; } int period2_index() const { return regs [3] >> 4; }
int period2( int base = 8 ) const { return base << period2_index(); } int period2( int base = 8 ) const { return base << period2_index(); }
unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; } unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; }
}; };
class Gb_Wave : public Gb_Osc { class Gb_Wave : public Gb_Osc {
public: public:
int sample_buf; // last wave RAM byte read (hardware has this as well) int sample_buf; // last wave RAM byte read (hardware has this as well)
void write_register( int frame_phase, int reg, int old_data, int data ); void write_register( int frame_phase, int reg, int old_data, int data );
void run( blip_time_t, blip_time_t ); void run( blip_time_t, blip_time_t );
// Reads/writes wave RAM // Reads/writes wave RAM
int read( int addr ) const; int read( int addr ) const;
void write( int addr, int data ); void write( int addr, int data );
void reset() void reset()
{ {
sample_buf = 0; sample_buf = 0;
Gb_Osc::reset(); Gb_Osc::reset();
} }
private: private:
enum { bank40_mask = 0x40 }; enum { bank40_mask = 0x40 };
enum { bank_size = 32 }; enum { bank_size = 32 };
int agb_mask; // 0xFF if AGB features enabled, 0 otherwise int agb_mask; // 0xFF if AGB features enabled, 0 otherwise
BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU
friend class Gb_Apu; friend class Gb_Apu;
// Frequency timer period // Frequency timer period
int period() const { return (2048 - frequency()) * (2 * clk_mul); } int period() const { return (2048 - frequency()) * (2 * clk_mul); }
// Non-zero if DAC is enabled // Non-zero if DAC is enabled
int dac_enabled() const { return regs [0] & 0x80; } int dac_enabled() const { return regs [0] & 0x80; }
void corrupt_wave(); void corrupt_wave();
BOOST::uint8_t* wave_bank() const { return &wave_ram [(~regs [0] & bank40_mask) >> 2 & agb_mask]; } BOOST::uint8_t* wave_bank() const { return &wave_ram [(~regs [0] & bank40_mask) >> 2 & agb_mask]; }
// Wave index that would be accessed, or -1 if no access would occur // Wave index that would be accessed, or -1 if no access would occur
int access( int addr ) const; int access( int addr ) const;
}; };
inline int Gb_Wave::read( int addr ) const inline int Gb_Wave::read( int addr ) const
{ {
int index = access( addr ); int index = access( addr );
return (index < 0 ? 0xFF : wave_bank() [index]); return (index < 0 ? 0xFF : wave_bank() [index]);
} }
inline void Gb_Wave::write( int addr, int data ) inline void Gb_Wave::write( int addr, int data )
{ {
int index = access( addr ); int index = access( addr );
if ( index >= 0 ) if ( index >= 0 )
wave_bank() [index] = data;; wave_bank() [index] = data;;
} }
#endif #endif

View file

@ -1,208 +1,208 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Core.h" #include "Gbs_Core.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you /* Copyright (C) 2003-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const tempo_unit = 16; int const tempo_unit = 16;
int const idle_addr = 0xF00D; int const idle_addr = 0xF00D;
int const bank_size = 0x4000; int const bank_size = 0x4000;
Gbs_Core::Gbs_Core() : rom( bank_size ) Gbs_Core::Gbs_Core() : rom( bank_size )
{ {
tempo = tempo_unit; tempo = tempo_unit;
assert( offsetof (header_t,copyright [32]) == header_t::size ); assert( offsetof (header_t,copyright [32]) == header_t::size );
} }
Gbs_Core::~Gbs_Core() { } Gbs_Core::~Gbs_Core() { }
void Gbs_Core::unload() void Gbs_Core::unload()
{ {
header_.timer_mode = 0; // set_tempo() reads this header_.timer_mode = 0; // set_tempo() reads this
rom.clear(); rom.clear();
Gme_Loader::unload(); Gme_Loader::unload();
} }
bool Gbs_Core::header_t::valid_tag() const bool Gbs_Core::header_t::valid_tag() const
{ {
return 0 == memcmp( tag, "GBS", 3 ); return 0 == memcmp( tag, "GBS", 3 );
} }
blargg_err_t Gbs_Core::load_( Data_Reader& in ) blargg_err_t Gbs_Core::load_( Data_Reader& in )
{ {
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) ); RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
if ( !header_.valid_tag() ) if ( !header_.valid_tag() )
return blargg_err_file_type; return blargg_err_file_type;
if ( header_.vers != 1 ) if ( header_.vers != 1 )
set_warning( "Unknown file version" ); set_warning( "Unknown file version" );
if ( header_.timer_mode & 0x78 ) if ( header_.timer_mode & 0x78 )
set_warning( "Invalid timer mode" ); set_warning( "Invalid timer mode" );
addr_t load_addr = get_le16( header_.load_addr ); addr_t load_addr = get_le16( header_.load_addr );
if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F || if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
load_addr < 0x400 ) load_addr < 0x400 )
set_warning( "Invalid load/init/play address" ); set_warning( "Invalid load/init/play address" );
cpu.rst_base = load_addr; cpu.rst_base = load_addr;
rom.set_addr( load_addr ); rom.set_addr( load_addr );
return blargg_ok; return blargg_ok;
} }
void Gbs_Core::set_bank( int n ) void Gbs_Core::set_bank( int n )
{ {
addr_t addr = rom.mask_addr( n * bank_size ); addr_t addr = rom.mask_addr( n * bank_size );
if ( addr == 0 && rom.size() > bank_size ) if ( addr == 0 && rom.size() > bank_size )
addr = bank_size; // MBC1&2 behavior, bank 0 acts like bank 1 addr = bank_size; // MBC1&2 behavior, bank 0 acts like bank 1
cpu.map_code( bank_size, bank_size, rom.at_addr( addr ) ); cpu.map_code( bank_size, bank_size, rom.at_addr( addr ) );
} }
void Gbs_Core::update_timer() void Gbs_Core::update_timer()
{ {
play_period_ = 70224 / tempo_unit; // 59.73 Hz play_period_ = 70224 / tempo_unit; // 59.73 Hz
if ( header_.timer_mode & 0x04 ) if ( header_.timer_mode & 0x04 )
{ {
// Using custom rate // Using custom rate
static byte const rates [4] = { 6, 0, 2, 4 }; static byte const rates [4] = { 6, 0, 2, 4 };
// TODO: emulate double speed CPU mode rather than halving timer rate // TODO: emulate double speed CPU mode rather than halving timer rate
int double_speed = header_.timer_mode >> 7; int double_speed = header_.timer_mode >> 7;
int shift = rates [ram [hi_page + 7] & 3] - double_speed; int shift = rates [ram [hi_page + 7] & 3] - double_speed;
play_period_ = (256 - ram [hi_page + 6]) << shift; play_period_ = (256 - ram [hi_page + 6]) << shift;
} }
play_period_ *= tempo; play_period_ *= tempo;
} }
void Gbs_Core::set_tempo( double t ) void Gbs_Core::set_tempo( double t )
{ {
tempo = (int) (tempo_unit / t + 0.5); tempo = (int) (tempo_unit / t + 0.5);
apu_.set_tempo( t ); apu_.set_tempo( t );
update_timer(); update_timer();
} }
// Jumps to routine, given pointer to address in file header. Pushes idle_addr // Jumps to routine, given pointer to address in file header. Pushes idle_addr
// as return address, NOT old PC. // as return address, NOT old PC.
void Gbs_Core::jsr_then_stop( byte const addr [] ) void Gbs_Core::jsr_then_stop( byte const addr [] )
{ {
check( cpu.r.sp == get_le16( header_.stack_ptr ) ); check( cpu.r.sp == get_le16( header_.stack_ptr ) );
cpu.r.pc = get_le16( addr ); cpu.r.pc = get_le16( addr );
write_mem( --cpu.r.sp, idle_addr >> 8 ); write_mem( --cpu.r.sp, idle_addr >> 8 );
write_mem( --cpu.r.sp, idle_addr ); write_mem( --cpu.r.sp, idle_addr );
} }
blargg_err_t Gbs_Core::start_track( int track, Gb_Apu::mode_t mode ) blargg_err_t Gbs_Core::start_track( int track, Gb_Apu::mode_t mode )
{ {
// Reset APU to state expected by most rips // Reset APU to state expected by most rips
static byte const sound_data [] = { static byte const sound_data [] = {
0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled 0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled
0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled 0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled
0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled 0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled
0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled 0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled
0x77, 0xFF, 0x80, // max volume, all chans in center, power on 0x77, 0xFF, 0x80, // max volume, all chans in center, power on
}; };
apu_.reset( mode ); apu_.reset( mode );
apu_.write_register( 0, 0xFF26, 0x80 ); // power on apu_.write_register( 0, 0xFF26, 0x80 ); // power on
for ( int i = 0; i < (int) sizeof sound_data; i++ ) for ( int i = 0; i < (int) sizeof sound_data; i++ )
apu_.write_register( 0, i + apu_.io_addr, sound_data [i] ); apu_.write_register( 0, i + apu_.io_addr, sound_data [i] );
apu_.end_frame( 1 ); // necessary to get click out of the way apu_.end_frame( 1 ); // necessary to get click out of the way
// Init memory and I/O registers // Init memory and I/O registers
memset( ram, 0, 0x4000 ); memset( ram, 0, 0x4000 );
memset( ram + 0x4000, 0xFF, 0x1F80 ); memset( ram + 0x4000, 0xFF, 0x1F80 );
memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 ); memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
ram [hi_page] = 0; // joypad reads back as 0 ram [hi_page] = 0; // joypad reads back as 0
ram [idle_addr - ram_addr] = 0xED; // illegal instruction ram [idle_addr - ram_addr] = 0xED; // illegal instruction
ram [hi_page + 6] = header_.timer_modulo; ram [hi_page + 6] = header_.timer_modulo;
ram [hi_page + 7] = header_.timer_mode; ram [hi_page + 7] = header_.timer_mode;
// Map memory // Map memory
cpu.reset( rom.unmapped() ); cpu.reset( rom.unmapped() );
cpu.map_code( ram_addr, 0x10000 - ram_addr, ram ); cpu.map_code( ram_addr, 0x10000 - ram_addr, ram );
cpu.map_code( 0, bank_size, rom.at_addr( 0 ) ); cpu.map_code( 0, bank_size, rom.at_addr( 0 ) );
set_bank( rom.size() > bank_size ); set_bank( rom.size() > bank_size );
// CPU registers, timing // CPU registers, timing
update_timer(); update_timer();
next_play = play_period_; next_play = play_period_;
cpu.r.fa = track; cpu.r.fa = track;
cpu.r.sp = get_le16( header_.stack_ptr ); cpu.r.sp = get_le16( header_.stack_ptr );
jsr_then_stop( header_.init_addr ); jsr_then_stop( header_.init_addr );
return blargg_ok; return blargg_ok;
} }
blargg_err_t Gbs_Core::run_until( int end ) blargg_err_t Gbs_Core::run_until( int end )
{ {
end_time = end; end_time = end;
cpu.set_time( cpu.time() - end ); cpu.set_time( cpu.time() - end );
while ( true ) while ( true )
{ {
run_cpu(); run_cpu();
if ( cpu.time() >= 0 ) if ( cpu.time() >= 0 )
break; break;
if ( cpu.r.pc == idle_addr ) if ( cpu.r.pc == idle_addr )
{ {
if ( next_play > end_time ) if ( next_play > end_time )
{ {
cpu.set_time( 0 ); cpu.set_time( 0 );
break; break;
} }
if ( cpu.time() < next_play - end_time ) if ( cpu.time() < next_play - end_time )
cpu.set_time( next_play - end_time ); cpu.set_time( next_play - end_time );
next_play += play_period_; next_play += play_period_;
jsr_then_stop( header_.play_addr ); jsr_then_stop( header_.play_addr );
} }
else if ( cpu.r.pc > 0xFFFF ) else if ( cpu.r.pc > 0xFFFF )
{ {
dprintf( "PC wrapped around\n" ); dprintf( "PC wrapped around\n" );
cpu.r.pc &= 0xFFFF; cpu.r.pc &= 0xFFFF;
} }
else else
{ {
set_warning( "Emulation error (illegal/unsupported instruction)" ); set_warning( "Emulation error (illegal/unsupported instruction)" );
dprintf( "Bad opcode $%02X at $%04X\n", dprintf( "Bad opcode $%02X at $%04X\n",
(int) *cpu.get_code( cpu.r.pc ), (int) cpu.r.pc ); (int) *cpu.get_code( cpu.r.pc ), (int) cpu.r.pc );
cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF; cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF;
cpu.set_time( cpu.time() + 6 ); cpu.set_time( cpu.time() + 6 );
} }
} }
return blargg_ok; return blargg_ok;
} }
blargg_err_t Gbs_Core::end_frame( int end ) blargg_err_t Gbs_Core::end_frame( int end )
{ {
RETURN_ERR( run_until( end ) ); RETURN_ERR( run_until( end ) );
next_play -= end; next_play -= end;
if ( next_play < 0 ) // happens when play routine takes too long if ( next_play < 0 ) // happens when play routine takes too long
{ {
#if !GBS_IGNORE_STARVED_PLAY #if !GBS_IGNORE_STARVED_PLAY
check( false ); check( false );
#endif #endif
next_play = 0; next_play = 0;
} }
apu_.end_frame( end ); apu_.end_frame( end );
return blargg_ok; return blargg_ok;
} }

View file

@ -1,109 +1,109 @@
// Nintendo Game Boy GBS music file emulator core // Nintendo Game Boy GBS music file emulator core
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef GBS_CORE_H #ifndef GBS_CORE_H
#define GBS_CORE_H #define GBS_CORE_H
#include "Gme_Loader.h" #include "Gme_Loader.h"
#include "Rom_Data.h" #include "Rom_Data.h"
#include "Gb_Cpu.h" #include "Gb_Cpu.h"
#include "Gb_Apu.h" #include "Gb_Apu.h"
class Gbs_Core : public Gme_Loader { class Gbs_Core : public Gme_Loader {
public: public:
// GBS file header // GBS file header
struct header_t struct header_t
{ {
enum { size = 112 }; enum { size = 112 };
char tag [ 3]; char tag [ 3];
byte vers; byte vers;
byte track_count; byte track_count;
byte first_track; byte first_track;
byte load_addr [ 2]; byte load_addr [ 2];
byte init_addr [ 2]; byte init_addr [ 2];
byte play_addr [ 2]; byte play_addr [ 2];
byte stack_ptr [ 2]; byte stack_ptr [ 2];
byte timer_modulo; byte timer_modulo;
byte timer_mode; byte timer_mode;
char game [32]; // strings can be 32 chars, NOT terminated char game [32]; // strings can be 32 chars, NOT terminated
char author [32]; char author [32];
char copyright [32]; char copyright [32];
// True if header has valid file signature // True if header has valid file signature
bool valid_tag() const; bool valid_tag() const;
}; };
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return header_; } header_t const& header() const { return header_; }
// Sound chip // Sound chip
Gb_Apu& apu() { return apu_; } Gb_Apu& apu() { return apu_; }
// ROM data // ROM data
Rom_Data const& rom_() const { return rom; } Rom_Data const& rom_() const { return rom; }
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing. // Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
void set_tempo( double ); void set_tempo( double );
// Starts track, where 0 is the first. Uses specified APU mode. // Starts track, where 0 is the first. Uses specified APU mode.
blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb ); blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb );
// Ends time frame at time t // Ends time frame at time t
typedef int time_t; // clock count typedef int time_t; // clock count
blargg_err_t end_frame( time_t t ); blargg_err_t end_frame( time_t t );
// Clocks between calls to play routine // Clocks between calls to play routine
time_t play_period() const { return play_period_; } time_t play_period() const { return play_period_; }
protected: protected:
typedef int addr_t; typedef int addr_t;
// Current time // Current time
time_t time() const { return cpu.time() + end_time; } time_t time() const { return cpu.time() + end_time; }
// Runs emulator to time t // Runs emulator to time t
blargg_err_t run_until( time_t t ); blargg_err_t run_until( time_t t );
// Runs CPU until time becomes >= 0 // Runs CPU until time becomes >= 0
void run_cpu(); void run_cpu();
// Reads/writes memory and I/O // Reads/writes memory and I/O
int read_mem( addr_t ); int read_mem( addr_t );
void write_mem( addr_t, int ); void write_mem( addr_t, int );
// Implementation // Implementation
public: public:
Gbs_Core(); Gbs_Core();
~Gbs_Core(); ~Gbs_Core();
virtual void unload(); virtual void unload();
protected: protected:
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
private: private:
enum { ram_addr = 0xA000 }; enum { ram_addr = 0xA000 };
enum { io_base = 0xFF00 }; enum { io_base = 0xFF00 };
enum { hi_page = io_base - ram_addr }; enum { hi_page = io_base - ram_addr };
Rom_Data rom; Rom_Data rom;
int tempo; int tempo;
time_t end_time; time_t end_time;
time_t play_period_; time_t play_period_;
time_t next_play; time_t next_play;
header_t header_; header_t header_;
Gb_Cpu cpu; Gb_Cpu cpu;
Gb_Apu apu_; Gb_Apu apu_;
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding]; byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
void update_timer(); void update_timer();
void jsr_then_stop( byte const [] ); void jsr_then_stop( byte const [] );
void set_bank( int n ); void set_bank( int n );
void write_io_inline( int offset, int data, int base ); void write_io_inline( int offset, int data, int base );
void write_io_( int offset, int data ); void write_io_( int offset, int data );
int read_io( int offset ); int read_io( int offset );
void write_io( int offset, int data ); void write_io( int offset, int data );
}; };
#endif #endif

View file

@ -1,134 +1,134 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Core.h" #include "Gbs_Core.h"
#include "blargg_endian.h" #include "blargg_endian.h"
//#include "gb_cpu_log.h" //#include "gb_cpu_log.h"
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you /* Copyright (C) 2003-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#ifndef LOG_MEM #ifndef LOG_MEM
#define LOG_MEM( addr, str, data ) data #define LOG_MEM( addr, str, data ) data
#endif #endif
int Gbs_Core::read_mem( addr_t addr ) int Gbs_Core::read_mem( addr_t addr )
{ {
int result = *cpu.get_code( addr ); int result = *cpu.get_code( addr );
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size ) if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
result = apu_.read_register( time(), addr ); result = apu_.read_register( time(), addr );
#ifndef NDEBUG #ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
dprintf( "Unmapped read $%04X\n", (unsigned) addr ); dprintf( "Unmapped read $%04X\n", (unsigned) addr );
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 ) else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 )
dprintf( "Unmapped read $%04X\n", (unsigned) addr ); dprintf( "Unmapped read $%04X\n", (unsigned) addr );
#endif #endif
return LOG_MEM( addr, ">", result ); return LOG_MEM( addr, ">", result );
} }
inline void Gbs_Core::write_io_inline( int offset, int data, int base ) inline void Gbs_Core::write_io_inline( int offset, int data, int base )
{ {
if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size ) if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size )
apu_.write_register( time(), offset + base, data & 0xFF ); apu_.write_register( time(), offset + base, data & 0xFF );
else if ( (unsigned) (offset - (0xFF06 - base)) < 2 ) else if ( (unsigned) (offset - (0xFF06 - base)) < 2 )
update_timer(); update_timer();
else if ( offset == io_base - base ) else if ( offset == io_base - base )
ram [base - ram_addr + offset] = 0; // keep joypad return value 0 ram [base - ram_addr + offset] = 0; // keep joypad return value 0
else else
ram [base - ram_addr + offset] = 0xFF; ram [base - ram_addr + offset] = 0xFF;
//if ( offset == 0xFFFF - base ) //if ( offset == 0xFFFF - base )
// dprintf( "Wrote interrupt mask\n" ); // dprintf( "Wrote interrupt mask\n" );
} }
void Gbs_Core::write_mem( addr_t addr, int data ) void Gbs_Core::write_mem( addr_t addr, int data )
{ {
(void) LOG_MEM( addr, "<", data ); (void) LOG_MEM( addr, "<", data );
int offset = addr - ram_addr; int offset = addr - ram_addr;
if ( (unsigned) offset < 0x10000 - ram_addr ) if ( (unsigned) offset < 0x10000 - ram_addr )
{ {
ram [offset] = data; ram [offset] = data;
offset -= 0xE000 - ram_addr; offset -= 0xE000 - ram_addr;
if ( (unsigned) offset < 0x1F80 ) if ( (unsigned) offset < 0x1F80 )
write_io_inline( offset, data, 0xE000 ); write_io_inline( offset, data, 0xE000 );
} }
else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 ) else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 )
{ {
set_bank( data & 0xFF ); set_bank( data & 0xFF );
} }
#ifndef NDEBUG #ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
{ {
dprintf( "Unmapped write $%04X\n", (unsigned) addr ); dprintf( "Unmapped write $%04X\n", (unsigned) addr );
} }
#endif #endif
} }
void Gbs_Core::write_io_( int offset, int data ) void Gbs_Core::write_io_( int offset, int data )
{ {
write_io_inline( offset, data, io_base ); write_io_inline( offset, data, io_base );
} }
inline void Gbs_Core::write_io( int offset, int data ) inline void Gbs_Core::write_io( int offset, int data )
{ {
(void) LOG_MEM( offset + io_base, "<", data ); (void) LOG_MEM( offset + io_base, "<", data );
ram [io_base - ram_addr + offset] = data; ram [io_base - ram_addr + offset] = data;
if ( (unsigned) offset < 0x80 ) if ( (unsigned) offset < 0x80 )
write_io_( offset, data ); write_io_( offset, data );
} }
int Gbs_Core::read_io( int offset ) int Gbs_Core::read_io( int offset )
{ {
int const io_base = 0xFF00; int const io_base = 0xFF00;
int result = ram [io_base - ram_addr + offset]; int result = ram [io_base - ram_addr + offset];
if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size ) if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size )
{ {
result = apu_.read_register( time(), offset + io_base ); result = apu_.read_register( time(), offset + io_base );
(void) LOG_MEM( offset + io_base, ">", result ); (void) LOG_MEM( offset + io_base, ">", result );
} }
else else
{ {
check( result == read_mem( offset + io_base ) ); check( result == read_mem( offset + io_base ) );
} }
return result; return result;
} }
#define READ_FAST( addr, out ) \ #define READ_FAST( addr, out ) \
{\ {\
out = READ_CODE( addr );\ out = READ_CODE( addr );\
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\ if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\
out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\ out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\
else\ else\
check( out == read_mem( addr ) );\ check( out == read_mem( addr ) );\
} }
#define READ_MEM( addr ) read_mem( addr ) #define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data ) #define WRITE_MEM( addr, data ) write_mem( addr, data )
#define WRITE_IO( addr, data ) write_io( addr, data ) #define WRITE_IO( addr, data ) write_io( addr, data )
#define READ_IO( addr, out ) out = read_io( addr ) #define READ_IO( addr, out ) out = read_io( addr )
#define CPU cpu #define CPU cpu
#define CPU_BEGIN \ #define CPU_BEGIN \
void Gbs_Core::run_cpu()\ void Gbs_Core::run_cpu()\
{ {
#include "Gb_Cpu_run.h" #include "Gb_Cpu_run.h"
} }

View file

@ -1,167 +1,167 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Emu.h" #include "Gbs_Emu.h"
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you /* Copyright (C) 2003-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000, 0,0,0,0,0,0,0,0 }; Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000, 0,0,0,0,0,0,0,0 };
Gbs_Emu::equalizer_t const Gbs_Emu::cgb_eq = { 0.0, 300, 0,0,0,0,0,0,0,0 }; Gbs_Emu::equalizer_t const Gbs_Emu::cgb_eq = { 0.0, 300, 0,0,0,0,0,0,0,0 };
Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 30, 0,0,0,0,0,0,0,0 }; // DMG Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 30, 0,0,0,0,0,0,0,0 }; // DMG
Gbs_Emu::Gbs_Emu() Gbs_Emu::Gbs_Emu()
{ {
sound_hardware = sound_gbs; sound_hardware = sound_gbs;
enable_clicking( false ); enable_clicking( false );
set_type( gme_gbs_type ); set_type( gme_gbs_type );
set_silence_lookahead( 6 ); set_silence_lookahead( 6 );
set_max_initial_silence( 21 ); set_max_initial_silence( 21 );
set_gain( 1.2 ); set_gain( 1.2 );
// kind of midway between headphones and speaker // kind of midway between headphones and speaker
static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 }; static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 };
set_equalizer( eq ); set_equalizer( eq );
} }
Gbs_Emu::~Gbs_Emu() { } Gbs_Emu::~Gbs_Emu() { }
void Gbs_Emu::unload() void Gbs_Emu::unload()
{ {
core_.unload(); core_.unload();
Music_Emu::unload(); Music_Emu::unload();
} }
// Track info // Track info
static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out ) static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
{ {
GME_COPY_FIELD( h, out, game ); GME_COPY_FIELD( h, out, game );
GME_COPY_FIELD( h, out, author ); GME_COPY_FIELD( h, out, author );
GME_COPY_FIELD( h, out, copyright ); GME_COPY_FIELD( h, out, copyright );
} }
static void hash_gbs_file( Gbs_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) static void hash_gbs_file( Gbs_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
{ {
out.hash_( &h.vers, sizeof(h.vers) ); out.hash_( &h.vers, sizeof(h.vers) );
out.hash_( &h.track_count, sizeof(h.track_count) ); out.hash_( &h.track_count, sizeof(h.track_count) );
out.hash_( &h.first_track, sizeof(h.first_track) ); out.hash_( &h.first_track, sizeof(h.first_track) );
out.hash_( &h.load_addr[0], sizeof(h.load_addr) ); out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) ); out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) ); out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) ); out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) );
out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) ); out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) );
out.hash_( &h.timer_mode, sizeof(h.timer_mode) ); out.hash_( &h.timer_mode, sizeof(h.timer_mode) );
out.hash_( data, data_size ); out.hash_( data, data_size );
} }
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
{ {
copy_gbs_fields( header(), out ); copy_gbs_fields( header(), out );
return blargg_ok; return blargg_ok;
} }
struct Gbs_File : Gme_Info_ struct Gbs_File : Gme_Info_
{ {
Gbs_Emu::header_t const* h; Gbs_Emu::header_t const* h;
Gbs_File() { set_type( gme_gbs_type ); } Gbs_File() { set_type( gme_gbs_type ); }
blargg_err_t load_mem_( byte const begin [], int size ) blargg_err_t load_mem_( byte const begin [], int size )
{ {
h = ( Gbs_Emu::header_t * ) begin; h = ( Gbs_Emu::header_t * ) begin;
set_track_count( h->track_count ); set_track_count( h->track_count );
if ( !h->valid_tag() ) if ( !h->valid_tag() )
return blargg_err_file_type; return blargg_err_file_type;
return blargg_ok; return blargg_ok;
} }
blargg_err_t track_info_( track_info_t* out, int ) const blargg_err_t track_info_( track_info_t* out, int ) const
{ {
copy_gbs_fields( Gbs_Emu::header_t( *h ), out ); copy_gbs_fields( Gbs_Emu::header_t( *h ), out );
return blargg_ok; return blargg_ok;
} }
blargg_err_t hash_( Hash_Function& out ) const blargg_err_t hash_( Hash_Function& out ) const
{ {
hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out ); hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
return blargg_ok; return blargg_ok;
} }
}; };
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; } static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; } static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
gme_type_t_ const gme_gbs_type [1] = {{ "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }}; gme_type_t_ const gme_gbs_type [1] = {{ "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }};
// Setup // Setup
blargg_err_t Gbs_Emu::load_( Data_Reader& in ) blargg_err_t Gbs_Emu::load_( Data_Reader& in )
{ {
RETURN_ERR( core_.load( in ) ); RETURN_ERR( core_.load( in ) );
set_warning( core_.warning() ); set_warning( core_.warning() );
set_track_count( header().track_count ); set_track_count( header().track_count );
set_voice_count( Gb_Apu::osc_count ); set_voice_count( Gb_Apu::osc_count );
core_.apu().volume( gain() ); core_.apu().volume( gain() );
static const char* const names [Gb_Apu::osc_count] = { static const char* const names [Gb_Apu::osc_count] = {
"Square 1", "Square 2", "Wave", "Noise" "Square 1", "Square 2", "Wave", "Noise"
}; };
set_voice_names( names ); set_voice_names( names );
static int const types [Gb_Apu::osc_count] = { static int const types [Gb_Apu::osc_count] = {
wave_type+1, wave_type+2, wave_type+3, mixed_type+1 wave_type+1, wave_type+2, wave_type+3, mixed_type+1
}; };
set_voice_types( types ); set_voice_types( types );
return setup_buffer( 4194304 ); return setup_buffer( 4194304 );
} }
void Gbs_Emu::update_eq( blip_eq_t const& eq ) void Gbs_Emu::update_eq( blip_eq_t const& eq )
{ {
core_.apu().treble_eq( eq ); core_.apu().treble_eq( eq );
} }
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{ {
core_.apu().set_output( i, c, l, r ); core_.apu().set_output( i, c, l, r );
} }
void Gbs_Emu::set_tempo_( double t ) void Gbs_Emu::set_tempo_( double t )
{ {
core_.set_tempo( t ); core_.set_tempo( t );
} }
blargg_err_t Gbs_Emu::start_track_( int track ) blargg_err_t Gbs_Emu::start_track_( int track )
{ {
sound_t mode = sound_hardware; sound_t mode = sound_hardware;
if ( mode == sound_gbs ) if ( mode == sound_gbs )
mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg; mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg;
RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) ); RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) );
// clear buffer AFTER track is started, eliminating initial click // clear buffer AFTER track is started, eliminating initial click
return Classic_Emu::start_track_( track ); return Classic_Emu::start_track_( track );
} }
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int ) blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
{ {
return core_.end_frame( duration ); return core_.end_frame( duration );
} }
blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const
{ {
hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out ); hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
return blargg_ok; return blargg_ok;
} }

View file

@ -1,63 +1,63 @@
// Nintendo Game Boy GBS music file emulator // Nintendo Game Boy GBS music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef GBS_EMU_H #ifndef GBS_EMU_H
#define GBS_EMU_H #define GBS_EMU_H
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Gbs_Core.h" #include "Gbs_Core.h"
class Gbs_Emu : public Classic_Emu { class Gbs_Emu : public Classic_Emu {
public: public:
// Equalizer profiles for Game Boy speaker and headphones // Equalizer profiles for Game Boy speaker and headphones
static equalizer_t const handheld_eq; static equalizer_t const handheld_eq;
static equalizer_t const headphones_eq; static equalizer_t const headphones_eq;
static equalizer_t const cgb_eq; // Game Boy Color headphones have less bass static equalizer_t const cgb_eq; // Game Boy Color headphones have less bass
// GBS file header (see Gbs_Core.h) // GBS file header (see Gbs_Core.h)
typedef Gbs_Core::header_t header_t; typedef Gbs_Core::header_t header_t;
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return core_.header(); } header_t const& header() const { return core_.header(); }
// Selects which sound hardware to use. AGB hardware is cleaner than the // Selects which sound hardware to use. AGB hardware is cleaner than the
// others. Doesn't take effect until next start_track(). // others. Doesn't take effect until next start_track().
enum sound_t { enum sound_t {
sound_dmg = Gb_Apu::mode_dmg, // Game Boy monochrome sound_dmg = Gb_Apu::mode_dmg, // Game Boy monochrome
sound_cgb = Gb_Apu::mode_cgb, // Game Boy Color sound_cgb = Gb_Apu::mode_cgb, // Game Boy Color
sound_agb = Gb_Apu::mode_agb, // Game Boy Advance sound_agb = Gb_Apu::mode_agb, // Game Boy Advance
sound_gbs // Use DMG/CGB based on GBS (default) sound_gbs // Use DMG/CGB based on GBS (default)
}; };
void set_sound( sound_t s ) { sound_hardware = s; } void set_sound( sound_t s ) { sound_hardware = s; }
// If true, makes APU more accurate, which results in more clicking. // If true, makes APU more accurate, which results in more clicking.
void enable_clicking( bool enable = true ) { core_.apu().reduce_clicks( !enable ); } void enable_clicking( bool enable = true ) { core_.apu().reduce_clicks( !enable ); }
static gme_type_t static_type() { return gme_gbs_type; } static gme_type_t static_type() { return gme_gbs_type; }
Gbs_Core& core() { return core_; } Gbs_Core& core() { return core_; }
blargg_err_t hash_( Hash_Function& ) const; blargg_err_t hash_( Hash_Function& ) const;
// Internal // Internal
public: public:
Gbs_Emu(); Gbs_Emu();
~Gbs_Emu(); ~Gbs_Emu();
protected: protected:
// Overrides // Overrides
virtual blargg_err_t track_info_( track_info_t*, int track ) const; virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int ); virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int ); virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double ); virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& ); virtual void update_eq( blip_eq_t const& );
virtual void unload(); virtual void unload();
private: private:
sound_t sound_hardware; sound_t sound_hardware;
Gbs_Core core_; Gbs_Core core_;
}; };
#endif #endif

View file

@ -1,86 +1,86 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gme_Loader.h" #include "Gme_Loader.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
void Gme_Loader::unload() void Gme_Loader::unload()
{ {
file_begin_ = NULL; file_begin_ = NULL;
file_end_ = NULL; file_end_ = NULL;
file_data.clear(); file_data.clear();
} }
Gme_Loader::Gme_Loader() Gme_Loader::Gme_Loader()
{ {
warning_ = NULL; warning_ = NULL;
Gme_Loader::unload(); Gme_Loader::unload();
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
} }
Gme_Loader::~Gme_Loader() { } Gme_Loader::~Gme_Loader() { }
blargg_err_t Gme_Loader::load_mem_( byte const data [], int size ) blargg_err_t Gme_Loader::load_mem_( byte const data [], int size )
{ {
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
Mem_File_Reader in( data, size ); Mem_File_Reader in( data, size );
return load_( in ); return load_( in );
} }
inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size ) inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size )
{ {
file_begin_ = data; file_begin_ = data;
file_end_ = data + size; file_end_ = data + size;
return load_mem_( data, size ); return load_mem_( data, size );
} }
blargg_err_t Gme_Loader::load_( Data_Reader& in ) blargg_err_t Gme_Loader::load_( Data_Reader& in )
{ {
RETURN_ERR( file_data.resize( in.remain() ) ); RETURN_ERR( file_data.resize( in.remain() ) );
RETURN_ERR( in.read( file_data.begin(), file_data.size() ) ); RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
return load_mem_wrapper( file_data.begin(), file_data.size() ); return load_mem_wrapper( file_data.begin(), file_data.size() );
} }
blargg_err_t Gme_Loader::post_load_( blargg_err_t err ) blargg_err_t Gme_Loader::post_load_( blargg_err_t err )
{ {
if ( err ) if ( err )
{ {
unload(); unload();
return err; return err;
} }
return post_load(); return post_load();
} }
blargg_err_t Gme_Loader::load_mem( void const* in, long size ) blargg_err_t Gme_Loader::load_mem( void const* in, long size )
{ {
pre_load(); pre_load();
return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) ); return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) );
} }
blargg_err_t Gme_Loader::load( Data_Reader& in ) blargg_err_t Gme_Loader::load( Data_Reader& in )
{ {
pre_load(); pre_load();
return post_load_( load_( in ) ); return post_load_( load_( in ) );
} }
blargg_err_t Gme_Loader::load_file( const char path [] ) blargg_err_t Gme_Loader::load_file( const char path [] )
{ {
pre_load(); pre_load();
GME_FILE_READER in; GME_FILE_READER in;
RETURN_ERR( in.open( path ) ); RETURN_ERR( in.open( path ) );
return post_load_( load_( in ) ); return post_load_( load_( in ) );
} }

View file

@ -1,92 +1,92 @@
// Common interface for loading file data from various sources // Common interface for loading file data from various sources
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef GME_LOADER_H #ifndef GME_LOADER_H
#define GME_LOADER_H #define GME_LOADER_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Data_Reader.h" #include "Data_Reader.h"
class Gme_Loader { class Gme_Loader {
public: public:
// Each loads game music data from a file and returns an error if // Each loads game music data from a file and returns an error if
// file is wrong type or is seriously corrupt. Minor problems are // file is wrong type or is seriously corrupt. Minor problems are
// reported using warning(). // reported using warning().
// Loads from file on disk // Loads from file on disk
blargg_err_t load_file( const char path [] ); blargg_err_t load_file( const char path [] );
// Loads from custom data source (see Data_Reader.h) // Loads from custom data source (see Data_Reader.h)
blargg_err_t load( Data_Reader& ); blargg_err_t load( Data_Reader& );
// Loads from file already read into memory. Object might keep pointer to // Loads from file already read into memory. Object might keep pointer to
// data; if it does, you MUST NOT free it until you're done with the file. // data; if it does, you MUST NOT free it until you're done with the file.
blargg_err_t load_mem( void const* data, long size ); blargg_err_t load_mem( void const* data, long size );
// Most recent warning string, or NULL if none. Clears current warning after // Most recent warning string, or NULL if none. Clears current warning after
// returning. // returning.
const char* warning(); const char* warning();
// Unloads file from memory // Unloads file from memory
virtual void unload(); virtual void unload();
virtual ~Gme_Loader(); virtual ~Gme_Loader();
protected: protected:
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
// File data in memory, or 0 if data was loaded with load_() // File data in memory, or 0 if data was loaded with load_()
byte const* file_begin() const { return file_begin_; } byte const* file_begin() const { return file_begin_; }
byte const* file_end() const { return file_end_; } byte const* file_end() const { return file_end_; }
int file_size() const { return (int) (file_end_ - file_begin_); } int file_size() const { return (int) (file_end_ - file_begin_); }
// Sets warning string // Sets warning string
void set_warning( const char s [] ) { warning_ = s; } void set_warning( const char s [] ) { warning_ = s; }
// At least one must be overridden // At least one must be overridden
virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_() virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
virtual blargg_err_t load_mem_( byte const data [], int size ); // use data in memory virtual blargg_err_t load_mem_( byte const data [], int size ); // use data in memory
// Optionally overridden // Optionally overridden
virtual void pre_load() { unload(); } // called before load_()/load_mem_() virtual void pre_load() { unload(); } // called before load_()/load_mem_()
virtual blargg_err_t post_load() { return blargg_ok; } // called after load_()/load_mem_() succeeds virtual blargg_err_t post_load() { return blargg_ok; } // called after load_()/load_mem_() succeeds
private: private:
// noncopyable // noncopyable
Gme_Loader( const Gme_Loader& ); Gme_Loader( const Gme_Loader& );
Gme_Loader& operator = ( const Gme_Loader& ); Gme_Loader& operator = ( const Gme_Loader& );
// Implementation // Implementation
public: public:
Gme_Loader(); Gme_Loader();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
blargg_vector<byte> file_data; // used only when loading from file to load_mem_() blargg_vector<byte> file_data; // used only when loading from file to load_mem_()
byte const* file_begin_; byte const* file_begin_;
byte const* file_end_; byte const* file_end_;
const char* warning_; const char* warning_;
blargg_err_t load_mem_wrapper( byte const [], int ); blargg_err_t load_mem_wrapper( byte const [], int );
blargg_err_t post_load_( blargg_err_t err ); blargg_err_t post_load_( blargg_err_t err );
}; };
// Files are read with GME_FILE_READER. Default supports gzip if zlib is available. // Files are read with GME_FILE_READER. Default supports gzip if zlib is available.
#ifndef GME_FILE_READER #ifndef GME_FILE_READER
#ifdef HAVE_ZLIB_H #ifdef HAVE_ZLIB_H
#define GME_FILE_READER Gzip_File_Reader #define GME_FILE_READER Gzip_File_Reader
#else #else
#define GME_FILE_READER Std_File_Reader #define GME_FILE_READER Std_File_Reader
#endif #endif
#elif defined (GME_FILE_READER_INCLUDE) #elif defined (GME_FILE_READER_INCLUDE)
#include GME_FILE_READER_INCLUDE #include GME_FILE_READER_INCLUDE
#endif #endif
inline const char* Gme_Loader::warning() inline const char* Gme_Loader::warning()
{ {
const char* s = warning_; const char* s = warning_;
warning_ = NULL; warning_ = NULL;
return s; return s;
} }
#endif #endif

View file

@ -1,361 +1,361 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Hes_Apu.h" #include "Hes_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
bool const center_waves = true; // reduces asymmetry and clamping when starting notes bool const center_waves = true; // reduces asymmetry and clamping when starting notes
Hes_Apu::Hes_Apu() Hes_Apu::Hes_Apu()
{ {
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{ {
osc--; osc--;
osc->output [0] = NULL; osc->output [0] = NULL;
osc->output [1] = NULL; osc->output [1] = NULL;
osc->outputs [0] = NULL; osc->outputs [0] = NULL;
osc->outputs [1] = NULL; osc->outputs [1] = NULL;
osc->outputs [2] = NULL; osc->outputs [2] = NULL;
} }
reset(); reset();
} }
void Hes_Apu::reset() void Hes_Apu::reset()
{ {
latch = 0; latch = 0;
balance = 0xFF; balance = 0xFF;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{ {
osc--; osc--;
memset( osc, 0, offsetof (Osc,output) ); memset( osc, 0, offsetof (Osc,output) );
osc->lfsr = 0; osc->lfsr = 0;
osc->control = 0x40; osc->control = 0x40;
osc->balance = 0xFF; osc->balance = 0xFF;
} }
// Only last two oscs support noise // Only last two oscs support noise
oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR
oscs [osc_count - 1].lfsr = 0x200C3; oscs [osc_count - 1].lfsr = 0x200C3;
} }
void Hes_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) void Hes_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{ {
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
require( !center || (center && !left && !right) || (center && left && right) ); require( !center || (center && !left && !right) || (center && left && right) );
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
if ( !center || !left || !right ) if ( !center || !left || !right )
{ {
left = center; left = center;
right = center; right = center;
} }
Osc& o = oscs [i]; Osc& o = oscs [i];
o.outputs [0] = center; o.outputs [0] = center;
o.outputs [1] = left; o.outputs [1] = left;
o.outputs [2] = right; o.outputs [2] = right;
balance_changed( o ); balance_changed( o );
} }
void Hes_Apu::run_osc( Blip_Synth_Fast& syn, Osc& o, blip_time_t end_time ) void Hes_Apu::run_osc( Blip_Synth_Fast& syn, Osc& o, blip_time_t end_time )
{ {
int vol0 = o.volume [0]; int vol0 = o.volume [0];
int vol1 = o.volume [1]; int vol1 = o.volume [1];
int dac = o.dac; int dac = o.dac;
Blip_Buffer* out0 = o.output [0]; // cache often-used values Blip_Buffer* out0 = o.output [0]; // cache often-used values
Blip_Buffer* out1 = o.output [1]; Blip_Buffer* out1 = o.output [1];
if ( !(o.control & 0x80) ) if ( !(o.control & 0x80) )
out0 = NULL; out0 = NULL;
if ( out0 ) if ( out0 )
{ {
// Update amplitudes // Update amplitudes
if ( out1 ) if ( out1 )
{ {
int delta = dac * vol1 - o.last_amp [1]; int delta = dac * vol1 - o.last_amp [1];
if ( delta ) if ( delta )
{ {
syn.offset( o.last_time, delta, out1 ); syn.offset( o.last_time, delta, out1 );
out1->set_modified(); out1->set_modified();
} }
} }
int delta = dac * vol0 - o.last_amp [0]; int delta = dac * vol0 - o.last_amp [0];
if ( delta ) if ( delta )
{ {
syn.offset( o.last_time, delta, out0 ); syn.offset( o.last_time, delta, out0 );
out0->set_modified(); out0->set_modified();
} }
// Don't generate if silent // Don't generate if silent
if ( !(vol0 | vol1) ) if ( !(vol0 | vol1) )
out0 = NULL; out0 = NULL;
} }
// Generate noise // Generate noise
int noise = 0; int noise = 0;
if ( o.lfsr ) if ( o.lfsr )
{ {
noise = o.noise & 0x80; noise = o.noise & 0x80;
blip_time_t time = o.last_time + o.noise_delay; blip_time_t time = o.last_time + o.noise_delay;
if ( time < end_time ) if ( time < end_time )
{ {
int period = (~o.noise & 0x1F) * 128; int period = (~o.noise & 0x1F) * 128;
if ( !period ) if ( !period )
period = 64; period = 64;
if ( noise && out0 ) if ( noise && out0 )
{ {
unsigned lfsr = o.lfsr; unsigned lfsr = o.lfsr;
do do
{ {
int new_dac = -(lfsr & 1); int new_dac = -(lfsr & 1);
lfsr = (lfsr >> 1) ^ (0x30061 & new_dac); lfsr = (lfsr >> 1) ^ (0x30061 & new_dac);
int delta = (new_dac &= 0x1F) - dac; int delta = (new_dac &= 0x1F) - dac;
if ( delta ) if ( delta )
{ {
dac = new_dac; dac = new_dac;
syn.offset( time, delta * vol0, out0 ); syn.offset( time, delta * vol0, out0 );
if ( out1 ) if ( out1 )
syn.offset( time, delta * vol1, out1 ); syn.offset( time, delta * vol1, out1 );
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
if ( !lfsr ) if ( !lfsr )
{ {
lfsr = 1; lfsr = 1;
check( false ); check( false );
} }
o.lfsr = lfsr; o.lfsr = lfsr;
out0->set_modified(); out0->set_modified();
if ( out1 ) if ( out1 )
out1->set_modified(); out1->set_modified();
} }
else else
{ {
// Maintain phase when silent // Maintain phase when silent
int count = (end_time - time + period - 1) / period; int count = (end_time - time + period - 1) / period;
time += count * period; time += count * period;
// not worth it // not worth it
//while ( count-- ) //while ( count-- )
// o.lfsr = (o.lfsr >> 1) ^ (0x30061 * (o.lfsr & 1)); // o.lfsr = (o.lfsr >> 1) ^ (0x30061 * (o.lfsr & 1));
} }
} }
o.noise_delay = time - end_time; o.noise_delay = time - end_time;
} }
// Generate wave // Generate wave
blip_time_t time = o.last_time + o.delay; blip_time_t time = o.last_time + o.delay;
if ( time < end_time ) if ( time < end_time )
{ {
int phase = (o.phase + 1) & 0x1F; // pre-advance for optimal inner loop int phase = (o.phase + 1) & 0x1F; // pre-advance for optimal inner loop
int period = o.period * 2; int period = o.period * 2;
if ( period >= 14 && out0 && !((o.control & 0x40) | noise) ) if ( period >= 14 && out0 && !((o.control & 0x40) | noise) )
{ {
do do
{ {
int new_dac = o.wave [phase]; int new_dac = o.wave [phase];
phase = (phase + 1) & 0x1F; phase = (phase + 1) & 0x1F;
int delta = new_dac - dac; int delta = new_dac - dac;
if ( delta ) if ( delta )
{ {
dac = new_dac; dac = new_dac;
syn.offset( time, delta * vol0, out0 ); syn.offset( time, delta * vol0, out0 );
if ( out1 ) if ( out1 )
syn.offset( time, delta * vol1, out1 ); syn.offset( time, delta * vol1, out1 );
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
out0->set_modified(); out0->set_modified();
if ( out1 ) if ( out1 )
out1->set_modified(); out1->set_modified();
} }
else else
{ {
// Maintain phase when silent // Maintain phase when silent
int count = end_time - time; int count = end_time - time;
if ( !period ) if ( !period )
period = 1; period = 1;
count = (count + period - 1) / period; count = (count + period - 1) / period;
phase += count; // phase will be masked below phase += count; // phase will be masked below
time += count * period; time += count * period;
} }
// TODO: Find whether phase increments even when both volumes are zero. // TODO: Find whether phase increments even when both volumes are zero.
// CAN'T simply check for out0 being non-NULL, since it could be NULL // CAN'T simply check for out0 being non-NULL, since it could be NULL
// if channel is muted in player, but still has non-zero volume. // if channel is muted in player, but still has non-zero volume.
// City Hunter breaks when this check is removed. // City Hunter breaks when this check is removed.
if ( !(o.control & 0x40) && (vol0 | vol1) ) if ( !(o.control & 0x40) && (vol0 | vol1) )
o.phase = (phase - 1) & 0x1F; // undo pre-advance o.phase = (phase - 1) & 0x1F; // undo pre-advance
} }
o.delay = time - end_time; o.delay = time - end_time;
check( o.delay >= 0 ); check( o.delay >= 0 );
o.last_time = end_time; o.last_time = end_time;
o.dac = dac; o.dac = dac;
o.last_amp [0] = dac * vol0; o.last_amp [0] = dac * vol0;
o.last_amp [1] = dac * vol1; o.last_amp [1] = dac * vol1;
} }
void Hes_Apu::balance_changed( Osc& osc ) void Hes_Apu::balance_changed( Osc& osc )
{ {
static short const log_table [32] = { // ~1.5 db per step static short const log_table [32] = { // ~1.5 db per step
#define ENTRY( factor ) short (factor * amp_range / 31.0 + 0.5) #define ENTRY( factor ) short (factor * amp_range / 31.0 + 0.5)
ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ), ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ),
ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ), ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ), ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ),
ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ), ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ),
ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ), ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ),
ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ), ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ),
ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ), ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ),
ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ), ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ),
#undef ENTRY #undef ENTRY
}; };
int vol = (osc.control & 0x1F) - 0x1E * 2; int vol = (osc.control & 0x1F) - 0x1E * 2;
int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E); int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E);
if ( left < 0 ) left = 0; if ( left < 0 ) left = 0;
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E); int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
if ( right < 0 ) right = 0; if ( right < 0 ) right = 0;
// optimizing for the common case of being centered also allows easy // optimizing for the common case of being centered also allows easy
// panning using Effects_Buffer // panning using Effects_Buffer
// Separate balance into center volume and additional on either left or right // Separate balance into center volume and additional on either left or right
osc.output [0] = osc.outputs [0]; // center osc.output [0] = osc.outputs [0]; // center
osc.output [1] = osc.outputs [2]; // right osc.output [1] = osc.outputs [2]; // right
int base = log_table [left ]; int base = log_table [left ];
int side = log_table [right] - base; int side = log_table [right] - base;
if ( side < 0 ) if ( side < 0 )
{ {
base += side; base += side;
side = -side; side = -side;
osc.output [1] = osc.outputs [1]; // left osc.output [1] = osc.outputs [1]; // left
} }
// Optimize when output is far left, center, or far right // Optimize when output is far left, center, or far right
if ( !base || osc.output [0] == osc.output [1] ) if ( !base || osc.output [0] == osc.output [1] )
{ {
base += side; base += side;
side = 0; side = 0;
osc.output [0] = osc.output [1]; osc.output [0] = osc.output [1];
osc.output [1] = NULL; osc.output [1] = NULL;
osc.last_amp [1] = 0; osc.last_amp [1] = 0;
} }
if ( center_waves ) if ( center_waves )
{ {
// TODO: this can leave a non-zero level in a buffer (minor) // TODO: this can leave a non-zero level in a buffer (minor)
osc.last_amp [0] += (base - osc.volume [0]) * 16; osc.last_amp [0] += (base - osc.volume [0]) * 16;
osc.last_amp [1] += (side - osc.volume [1]) * 16; osc.last_amp [1] += (side - osc.volume [1]) * 16;
} }
osc.volume [0] = base; osc.volume [0] = base;
osc.volume [1] = side; osc.volume [1] = side;
} }
void Hes_Apu::write_data( blip_time_t time, int addr, int data ) void Hes_Apu::write_data( blip_time_t time, int addr, int data )
{ {
if ( addr == 0x800 ) if ( addr == 0x800 )
{ {
latch = data & 7; latch = data & 7;
} }
else if ( addr == 0x801 ) else if ( addr == 0x801 )
{ {
if ( balance != data ) if ( balance != data )
{ {
balance = data; balance = data;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{ {
osc--; osc--;
run_osc( synth, *osc, time ); run_osc( synth, *osc, time );
balance_changed( *oscs ); balance_changed( *oscs );
} }
} }
} }
else if ( latch < osc_count ) else if ( latch < osc_count )
{ {
Osc& osc = oscs [latch]; Osc& osc = oscs [latch];
run_osc( synth, osc, time ); run_osc( synth, osc, time );
switch ( addr ) switch ( addr )
{ {
case 0x802: case 0x802:
osc.period = (osc.period & 0xF00) | data; osc.period = (osc.period & 0xF00) | data;
break; break;
case 0x803: case 0x803:
osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8); osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8);
break; break;
case 0x804: case 0x804:
if ( osc.control & 0x40 & ~data ) if ( osc.control & 0x40 & ~data )
osc.phase = 0; osc.phase = 0;
osc.control = data; osc.control = data;
balance_changed( osc ); balance_changed( osc );
break; break;
case 0x805: case 0x805:
osc.balance = data; osc.balance = data;
balance_changed( osc ); balance_changed( osc );
break; break;
case 0x806: case 0x806:
data &= 0x1F; data &= 0x1F;
if ( !(osc.control & 0x40) ) if ( !(osc.control & 0x40) )
{ {
osc.wave [osc.phase] = data; osc.wave [osc.phase] = data;
osc.phase = (osc.phase + 1) & 0x1F; osc.phase = (osc.phase + 1) & 0x1F;
} }
else if ( osc.control & 0x80 ) else if ( osc.control & 0x80 )
{ {
osc.dac = data; osc.dac = data;
} }
break; break;
case 0x807: case 0x807:
osc.noise = data; osc.noise = data;
break; break;
case 0x809: case 0x809:
if ( !(data & 0x80) && (data & 0x03) != 0 ) if ( !(data & 0x80) && (data & 0x03) != 0 )
dprintf( "HES LFO not supported\n" ); dprintf( "HES LFO not supported\n" );
} }
} }
} }
void Hes_Apu::end_frame( blip_time_t end_time ) void Hes_Apu::end_frame( blip_time_t end_time )
{ {
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{ {
osc--; osc--;
if ( end_time > osc->last_time ) if ( end_time > osc->last_time )
run_osc( synth, *osc, end_time ); run_osc( synth, *osc, end_time );
osc->last_time -= end_time; osc->last_time -= end_time;
check( osc->last_time >= 0 ); check( osc->last_time >= 0 );
} }
} }

View file

@ -1,87 +1,87 @@
// Turbo Grafx 16 (PC Engine) PSG sound chip emulator // Turbo Grafx 16 (PC Engine) PSG sound chip emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef HES_APU_H #ifndef HES_APU_H
#define HES_APU_H #define HES_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Hes_Apu { class Hes_Apu {
public: public:
// Basics // Basics
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, // Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
// output is mono. // output is mono.
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
// Emulates to time t, then writes data to addr // Emulates to time t, then writes data to addr
void write_data( blip_time_t t, int addr, int data ); void write_data( blip_time_t t, int addr, int data );
// Emulates to time t, then subtracts t from the current time. // Emulates to time t, then subtracts t from the current time.
// OK if previous write call had time slightly after t. // OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); void end_frame( blip_time_t t );
// More features // More features
// Resets sound chip // Resets sound chip
void reset(); void reset();
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
enum { osc_count = 6 }; // 0 <= chan < osc_count enum { osc_count = 6 }; // 0 <= chan < osc_count
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
// Sets treble equalization // Sets treble equalization
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
// Sets overall volume, where 1.0 is normal // Sets overall volume, where 1.0 is normal
void volume( double v ) { synth.volume( 1.8 / osc_count / amp_range * v ); } void volume( double v ) { synth.volume( 1.8 / osc_count / amp_range * v ); }
// Registers are at io_addr to io_addr+io_size-1 // Registers are at io_addr to io_addr+io_size-1
enum { io_addr = 0x0800 }; enum { io_addr = 0x0800 };
enum { io_size = 10 }; enum { io_size = 10 };
// Implementation // Implementation
public: public:
Hes_Apu(); Hes_Apu();
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
private: private:
enum { amp_range = 0x8000 }; enum { amp_range = 0x8000 };
struct Osc struct Osc
{ {
byte wave [32]; byte wave [32];
int delay; int delay;
int period; int period;
int phase; int phase;
int noise_delay; int noise_delay;
byte noise; byte noise;
unsigned lfsr; unsigned lfsr;
byte control; byte control;
byte balance; byte balance;
byte dac; byte dac;
short volume [2]; short volume [2];
int last_amp [2]; int last_amp [2];
blip_time_t last_time; blip_time_t last_time;
Blip_Buffer* output [2]; Blip_Buffer* output [2];
Blip_Buffer* outputs [3]; Blip_Buffer* outputs [3];
}; };
Osc oscs [osc_count]; Osc oscs [osc_count];
int latch; int latch;
int balance; int balance;
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
void balance_changed( Osc& ); void balance_changed( Osc& );
static void run_osc( Blip_Synth_Fast&, Osc&, blip_time_t ); static void run_osc( Blip_Synth_Fast&, Osc&, blip_time_t );
}; };
inline void Hes_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) inline void Hes_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{ {
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
set_output( i, c, l, r ); set_output( i, c, l, r );
} }
#endif #endif

View file

@ -1,94 +1,94 @@
// Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator // Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef HES_APU_ADPCM_H #ifndef HES_APU_ADPCM_H
#define HES_APU_ADPCM_H #define HES_APU_ADPCM_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Hes_Apu_Adpcm { class Hes_Apu_Adpcm {
public: public:
// Basics // Basics
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, // Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
// output is mono. // output is mono.
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
// Emulates to time t, then writes data to addr // Emulates to time t, then writes data to addr
void write_data( blip_time_t t, int addr, int data ); void write_data( blip_time_t t, int addr, int data );
// Emulates to time t, then reads from addr // Emulates to time t, then reads from addr
int read_data( blip_time_t t, int addr ); int read_data( blip_time_t t, int addr );
// Emulates to time t, then subtracts t from the current time. // Emulates to time t, then subtracts t from the current time.
// OK if previous write call had time slightly after t. // OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); void end_frame( blip_time_t t );
// More features // More features
// Resets sound chip // Resets sound chip
void reset(); void reset();
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
enum { osc_count = 1 }; // 0 <= chan < osc_count enum { osc_count = 1 }; // 0 <= chan < osc_count
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
// Sets treble equalization // Sets treble equalization
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
// Sets overall volume, where 1.0 is normal // Sets overall volume, where 1.0 is normal
void volume( double v ) { synth.volume( 0.6 / osc_count / amp_range * v ); } void volume( double v ) { synth.volume( 0.6 / osc_count / amp_range * v ); }
// Registers are at io_addr to io_addr+io_size-1 // Registers are at io_addr to io_addr+io_size-1
enum { io_addr = 0x1800 }; enum { io_addr = 0x1800 };
enum { io_size = 0x400 }; enum { io_size = 0x400 };
// Implementation // Implementation
public: public:
Hes_Apu_Adpcm(); Hes_Apu_Adpcm();
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
private: private:
enum { amp_range = 2048 }; enum { amp_range = 2048 };
struct State struct State
{ {
byte pcmbuf [0x10000]; byte pcmbuf [0x10000];
byte port [0x10]; byte port [0x10];
int ad_sample; int ad_sample;
int ad_ref_index; int ad_ref_index;
bool ad_low_nibble; bool ad_low_nibble;
int freq; int freq;
unsigned short addr; unsigned short addr;
unsigned short writeptr; unsigned short writeptr;
unsigned short readptr; unsigned short readptr;
unsigned short playptr; unsigned short playptr;
byte playflag; byte playflag;
byte repeatflag; byte repeatflag;
int length; int length;
int playlength; int playlength;
int playedsamplecount; int playedsamplecount;
int volume; int volume;
int fadetimer; int fadetimer;
int fadecount; int fadecount;
}; };
State state; State state;
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
Blip_Buffer* output; Blip_Buffer* output;
blip_time_t last_time; blip_time_t last_time;
double next_timer; double next_timer;
int last_amp; int last_amp;
void run_until( blip_time_t ); void run_until( blip_time_t );
int adpcm_decode( int ); int adpcm_decode( int );
}; };
inline void Hes_Apu_Adpcm::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) inline void Hes_Apu_Adpcm::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{ {
set_output( 0, c, l, r ); set_output( 0, c, l, r );
} }
#endif #endif

View file

@ -1,408 +1,408 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Hes_Core.h" #include "Hes_Core.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const timer_mask = 0x04; int const timer_mask = 0x04;
int const vdp_mask = 0x02; int const vdp_mask = 0x02;
int const i_flag_mask = 0x04; int const i_flag_mask = 0x04;
int const unmapped = 0xFF; int const unmapped = 0xFF;
int const period_60hz = 262 * 455; // scanlines * clocks per scanline int const period_60hz = 262 * 455; // scanlines * clocks per scanline
Hes_Core::Hes_Core() : rom( Hes_Cpu::page_size ) Hes_Core::Hes_Core() : rom( Hes_Cpu::page_size )
{ {
timer.raw_load = 0; timer.raw_load = 0;
} }
Hes_Core::~Hes_Core() { } Hes_Core::~Hes_Core() { }
void Hes_Core::unload() void Hes_Core::unload()
{ {
rom.clear(); rom.clear();
Gme_Loader::unload(); Gme_Loader::unload();
} }
bool Hes_Core::header_t::valid_tag() const bool Hes_Core::header_t::valid_tag() const
{ {
return 0 == memcmp( tag, "HESM", 4 ); return 0 == memcmp( tag, "HESM", 4 );
} }
blargg_err_t Hes_Core::load_( Data_Reader& in ) blargg_err_t Hes_Core::load_( Data_Reader& in )
{ {
assert( offsetof (header_t,unused [4]) == header_t::size ); assert( offsetof (header_t,unused [4]) == header_t::size );
RETURN_ERR( rom.load( in, header_t::size, &header_, unmapped ) ); RETURN_ERR( rom.load( in, header_t::size, &header_, unmapped ) );
if ( !header_.valid_tag() ) if ( !header_.valid_tag() )
return blargg_err_file_type; return blargg_err_file_type;
if ( header_.vers != 0 ) if ( header_.vers != 0 )
set_warning( "Unknown file version" ); set_warning( "Unknown file version" );
if ( memcmp( header_.data_tag, "DATA", 4 ) ) if ( memcmp( header_.data_tag, "DATA", 4 ) )
set_warning( "Data header missing" ); set_warning( "Data header missing" );
if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
set_warning( "Unknown header data" ); set_warning( "Unknown header data" );
// File spec supports multiple blocks, but I haven't found any, and // File spec supports multiple blocks, but I haven't found any, and
// many files have bad sizes in the only block, so it's simpler to // many files have bad sizes in the only block, so it's simpler to
// just try to load the damn data as best as possible. // just try to load the damn data as best as possible.
int addr = get_le32( header_.addr ); int addr = get_le32( header_.addr );
int size = get_le32( header_.data_size ); int size = get_le32( header_.data_size );
int const rom_max = 0x100000; int const rom_max = 0x100000;
if ( (unsigned) addr >= (unsigned) rom_max ) if ( (unsigned) addr >= (unsigned) rom_max )
{ {
set_warning( "Invalid address" ); set_warning( "Invalid address" );
addr &= rom_max - 1; addr &= rom_max - 1;
} }
if ( (unsigned) (addr + size) > (unsigned) rom_max ) if ( (unsigned) (addr + size) > (unsigned) rom_max )
set_warning( "Invalid size" ); set_warning( "Invalid size" );
if ( size != rom.file_size() ) if ( size != rom.file_size() )
{ {
if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
set_warning( "Multiple DATA not supported" ); set_warning( "Multiple DATA not supported" );
else if ( size < rom.file_size() ) else if ( size < rom.file_size() )
set_warning( "Extra file data" ); set_warning( "Extra file data" );
else else
set_warning( "Missing file data" ); set_warning( "Missing file data" );
} }
rom.set_addr( addr ); rom.set_addr( addr );
return blargg_ok; return blargg_ok;
} }
void Hes_Core::recalc_timer_load() void Hes_Core::recalc_timer_load()
{ {
timer.load = timer.raw_load * timer_base + 1; timer.load = timer.raw_load * timer_base + 1;
} }
void Hes_Core::set_tempo( double t ) void Hes_Core::set_tempo( double t )
{ {
play_period = (time_t) (period_60hz / t); play_period = (time_t) (period_60hz / t);
timer_base = (int) (1024 / t); timer_base = (int) (1024 / t);
recalc_timer_load(); recalc_timer_load();
} }
blargg_err_t Hes_Core::start_track( int track ) blargg_err_t Hes_Core::start_track( int track )
{ {
memset( ram, 0, sizeof ram ); // some HES music relies on zero fill memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
memset( sgx, 0, sizeof sgx ); memset( sgx, 0, sizeof sgx );
apu_.reset(); apu_.reset();
adpcm_.reset(); adpcm_.reset();
cpu.reset(); cpu.reset();
for ( int i = 0; i < (int) sizeof header_.banks; i++ ) for ( int i = 0; i < (int) sizeof header_.banks; i++ )
set_mmr( i, header_.banks [i] ); set_mmr( i, header_.banks [i] );
set_mmr( cpu.page_count, 0xFF ); // unmapped beyond end of address space set_mmr( cpu.page_count, 0xFF ); // unmapped beyond end of address space
irq.disables = timer_mask | vdp_mask; irq.disables = timer_mask | vdp_mask;
irq.timer = cpu.future_time; irq.timer = cpu.future_time;
irq.vdp = cpu.future_time; irq.vdp = cpu.future_time;
timer.enabled = false; timer.enabled = false;
timer.raw_load = 0x80; timer.raw_load = 0x80;
timer.count = timer.load; timer.count = timer.load;
timer.fired = false; timer.fired = false;
timer.last_time = 0; timer.last_time = 0;
vdp.latch = 0; vdp.latch = 0;
vdp.control = 0; vdp.control = 0;
vdp.next_vbl = 0; vdp.next_vbl = 0;
ram [0x1FF] = (idle_addr - 1) >> 8; ram [0x1FF] = (idle_addr - 1) >> 8;
ram [0x1FE] = (idle_addr - 1) & 0xFF; ram [0x1FE] = (idle_addr - 1) & 0xFF;
cpu.r.sp = 0xFD; cpu.r.sp = 0xFD;
cpu.r.pc = get_le16( header_.init_addr ); cpu.r.pc = get_le16( header_.init_addr );
cpu.r.a = track; cpu.r.a = track;
recalc_timer_load(); recalc_timer_load();
return blargg_ok; return blargg_ok;
} }
// Hardware // Hardware
void Hes_Core::run_until( time_t present ) void Hes_Core::run_until( time_t present )
{ {
while ( vdp.next_vbl < present ) while ( vdp.next_vbl < present )
vdp.next_vbl += play_period; vdp.next_vbl += play_period;
time_t elapsed = present - timer.last_time; time_t elapsed = present - timer.last_time;
if ( elapsed > 0 ) if ( elapsed > 0 )
{ {
if ( timer.enabled ) if ( timer.enabled )
{ {
timer.count -= elapsed; timer.count -= elapsed;
if ( timer.count <= 0 ) if ( timer.count <= 0 )
timer.count += timer.load; timer.count += timer.load;
} }
timer.last_time = present; timer.last_time = present;
} }
} }
void Hes_Core::write_vdp( int addr, int data ) void Hes_Core::write_vdp( int addr, int data )
{ {
switch ( addr ) switch ( addr )
{ {
case 0: case 0:
vdp.latch = data & 0x1F; vdp.latch = data & 0x1F;
break; break;
case 2: case 2:
if ( vdp.latch == 5 ) if ( vdp.latch == 5 )
{ {
if ( data & 0x04 ) if ( data & 0x04 )
set_warning( "Scanline interrupt unsupported" ); set_warning( "Scanline interrupt unsupported" );
run_until( cpu.time() ); run_until( cpu.time() );
vdp.control = data; vdp.control = data;
irq_changed(); irq_changed();
} }
else else
{ {
dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
} }
break; break;
case 3: case 3:
dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
break; break;
} }
} }
void Hes_Core::write_mem_( addr_t addr, int data ) void Hes_Core::write_mem_( addr_t addr, int data )
{ {
time_t time = cpu.time(); time_t time = cpu.time();
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size ) if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
{ {
// Avoid going way past end when a long block xfer is writing to I/O space. // Avoid going way past end when a long block xfer is writing to I/O space.
// Not a problem for other registers below because they don't write to // Not a problem for other registers below because they don't write to
// Blip_Buffer. // Blip_Buffer.
time_t t = min( time, cpu.end_time() + 8 ); time_t t = min( time, cpu.end_time() + 8 );
apu_.write_data( t, addr, data ); apu_.write_data( t, addr, data );
return; return;
} }
if ( (unsigned) (addr - adpcm_.io_addr) < adpcm_.io_size ) if ( (unsigned) (addr - adpcm_.io_addr) < adpcm_.io_size )
{ {
time_t t = min( time, cpu.end_time() + 6 ); time_t t = min( time, cpu.end_time() + 6 );
adpcm_.write_data( t, addr, data ); adpcm_.write_data( t, addr, data );
return; return;
} }
switch ( addr ) switch ( addr )
{ {
case 0x0000: case 0x0000:
case 0x0002: case 0x0002:
case 0x0003: case 0x0003:
write_vdp( addr, data ); write_vdp( addr, data );
return; return;
case 0x0C00: { case 0x0C00: {
run_until( time ); run_until( time );
timer.raw_load = (data & 0x7F) + 1; timer.raw_load = (data & 0x7F) + 1;
recalc_timer_load(); recalc_timer_load();
timer.count = timer.load; timer.count = timer.load;
break; break;
} }
case 0x0C01: case 0x0C01:
data &= 1; data &= 1;
if ( timer.enabled == data ) if ( timer.enabled == data )
return; return;
run_until( time ); run_until( time );
timer.enabled = data; timer.enabled = data;
if ( data ) if ( data )
timer.count = timer.load; timer.count = timer.load;
break; break;
case 0x1402: case 0x1402:
run_until( time ); run_until( time );
irq.disables = data; irq.disables = data;
if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
dprintf( "Int mask: $%02X\n", data ); dprintf( "Int mask: $%02X\n", data );
break; break;
case 0x1403: case 0x1403:
run_until( time ); run_until( time );
if ( timer.enabled ) if ( timer.enabled )
timer.count = timer.load; timer.count = timer.load;
timer.fired = false; timer.fired = false;
break; break;
#ifndef NDEBUG #ifndef NDEBUG
case 0x1000: // I/O port case 0x1000: // I/O port
case 0x0402: // palette case 0x0402: // palette
case 0x0403: case 0x0403:
case 0x0404: case 0x0404:
case 0x0405: case 0x0405:
return; return;
default: default:
dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); dprintf( "unmapped write $%04X <- $%02X\n", addr, data );
return; return;
#endif #endif
} }
irq_changed(); irq_changed();
} }
int Hes_Core::read_mem_( addr_t addr ) int Hes_Core::read_mem_( addr_t addr )
{ {
time_t time = cpu.time(); time_t time = cpu.time();
addr &= cpu.page_size - 1; addr &= cpu.page_size - 1;
switch ( addr ) switch ( addr )
{ {
case 0x0000: case 0x0000:
if ( irq.vdp > time ) if ( irq.vdp > time )
return 0; return 0;
irq.vdp = cpu.future_time; irq.vdp = cpu.future_time;
run_until( time ); run_until( time );
irq_changed(); irq_changed();
return 0x20; return 0x20;
case 0x0002: case 0x0002:
case 0x0003: case 0x0003:
dprintf( "VDP read not supported: %d\n", addr ); dprintf( "VDP read not supported: %d\n", addr );
return 0; return 0;
case 0x0C01: case 0x0C01:
//return timer.enabled; // TODO: remove? //return timer.enabled; // TODO: remove?
case 0x0C00: case 0x0C00:
run_until( time ); run_until( time );
dprintf( "Timer count read\n" ); dprintf( "Timer count read\n" );
return (unsigned) (timer.count - 1) / timer_base; return (unsigned) (timer.count - 1) / timer_base;
case 0x1402: case 0x1402:
return irq.disables; return irq.disables;
case 0x1403: case 0x1403:
{ {
int status = 0; int status = 0;
if ( irq.timer <= time ) status |= timer_mask; if ( irq.timer <= time ) status |= timer_mask;
if ( irq.vdp <= time ) status |= vdp_mask; if ( irq.vdp <= time ) status |= vdp_mask;
return status; return status;
} }
case 0x180A: case 0x180A:
case 0x180B: case 0x180B:
case 0x180C: case 0x180C:
case 0x180D: case 0x180D:
return adpcm_.read_data( time, addr ); return adpcm_.read_data( time, addr );
#ifndef NDEBUG #ifndef NDEBUG
case 0x1000: // I/O port case 0x1000: // I/O port
//case 0x180C: // CD-ROM //case 0x180C: // CD-ROM
//case 0x180D: //case 0x180D:
break; break;
default: default:
dprintf( "unmapped read $%04X\n", addr ); dprintf( "unmapped read $%04X\n", addr );
#endif #endif
} }
return unmapped; return unmapped;
} }
void Hes_Core::irq_changed() void Hes_Core::irq_changed()
{ {
time_t present = cpu.time(); time_t present = cpu.time();
if ( irq.timer > present ) if ( irq.timer > present )
{ {
irq.timer = cpu.future_time; irq.timer = cpu.future_time;
if ( timer.enabled && !timer.fired ) if ( timer.enabled && !timer.fired )
irq.timer = present + timer.count; irq.timer = present + timer.count;
} }
if ( irq.vdp > present ) if ( irq.vdp > present )
{ {
irq.vdp = cpu.future_time; irq.vdp = cpu.future_time;
if ( vdp.control & 0x08 ) if ( vdp.control & 0x08 )
irq.vdp = vdp.next_vbl; irq.vdp = vdp.next_vbl;
} }
time_t time = cpu.future_time; time_t time = cpu.future_time;
if ( !(irq.disables & timer_mask) ) time = irq.timer; if ( !(irq.disables & timer_mask) ) time = irq.timer;
if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp ); if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp );
cpu.set_irq_time( time ); cpu.set_irq_time( time );
} }
int Hes_Core::cpu_done() int Hes_Core::cpu_done()
{ {
check( cpu.time() >= cpu.end_time() || check( cpu.time() >= cpu.end_time() ||
(!(cpu.r.flags & i_flag_mask) && cpu.time() >= cpu.irq_time()) ); (!(cpu.r.flags & i_flag_mask) && cpu.time() >= cpu.irq_time()) );
if ( !(cpu.r.flags & i_flag_mask) ) if ( !(cpu.r.flags & i_flag_mask) )
{ {
time_t present = cpu.time(); time_t present = cpu.time();
if ( irq.timer <= present && !(irq.disables & timer_mask) ) if ( irq.timer <= present && !(irq.disables & timer_mask) )
{ {
timer.fired = true; timer.fired = true;
irq.timer = cpu.future_time; irq.timer = cpu.future_time;
irq_changed(); // overkill, but not worth writing custom code irq_changed(); // overkill, but not worth writing custom code
return 0x0A; return 0x0A;
} }
if ( irq.vdp <= present && !(irq.disables & vdp_mask) ) if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
{ {
// work around for bugs with music not acknowledging VDP // work around for bugs with music not acknowledging VDP
//run_until( present ); //run_until( present );
//irq.vdp = cpu.future_time; //irq.vdp = cpu.future_time;
//irq_changed(); //irq_changed();
return 0x08; return 0x08;
} }
} }
return -1; return -1;
} }
static void adjust_time( Hes_Core::time_t& time, Hes_Core::time_t delta ) static void adjust_time( Hes_Core::time_t& time, Hes_Core::time_t delta )
{ {
if ( time < Hes_Cpu::future_time ) if ( time < Hes_Cpu::future_time )
{ {
time -= delta; time -= delta;
if ( time < 0 ) if ( time < 0 )
time = 0; time = 0;
} }
} }
blargg_err_t Hes_Core::end_frame( time_t duration ) blargg_err_t Hes_Core::end_frame( time_t duration )
{ {
if ( run_cpu( duration ) ) if ( run_cpu( duration ) )
set_warning( "Emulation error (illegal instruction)" ); set_warning( "Emulation error (illegal instruction)" );
check( cpu.time() >= duration ); check( cpu.time() >= duration );
//check( time() - duration < 20 ); // Txx instruction could cause going way over //check( time() - duration < 20 ); // Txx instruction could cause going way over
run_until( duration ); run_until( duration );
// end time frame // end time frame
timer.last_time -= duration; timer.last_time -= duration;
vdp.next_vbl -= duration; vdp.next_vbl -= duration;
cpu.end_frame( duration ); cpu.end_frame( duration );
::adjust_time( irq.timer, duration ); ::adjust_time( irq.timer, duration );
::adjust_time( irq.vdp, duration ); ::adjust_time( irq.vdp, duration );
apu_.end_frame( duration ); apu_.end_frame( duration );
adpcm_.end_frame( duration ); adpcm_.end_frame( duration );
return blargg_ok; return blargg_ok;
} }

View file

@ -1,120 +1,120 @@
// TurboGrafx-16/PC Engine HES music file emulator core // TurboGrafx-16/PC Engine HES music file emulator core
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef HES_CORE_H #ifndef HES_CORE_H
#define HES_CORE_H #define HES_CORE_H
#include "Gme_Loader.h" #include "Gme_Loader.h"
#include "Rom_Data.h" #include "Rom_Data.h"
#include "Hes_Apu.h" #include "Hes_Apu.h"
#include "Hes_Apu_Adpcm.h" #include "Hes_Apu_Adpcm.h"
#include "Hes_Cpu.h" #include "Hes_Cpu.h"
class Hes_Core : public Gme_Loader { class Hes_Core : public Gme_Loader {
public: public:
// HES file header // HES file header
enum { info_offset = 0x20 }; enum { info_offset = 0x20 };
struct header_t struct header_t
{ {
enum { size = 0x20 }; enum { size = 0x20 };
byte tag [4]; byte tag [4];
byte vers; byte vers;
byte first_track; byte first_track;
byte init_addr [2]; byte init_addr [2];
byte banks [8]; byte banks [8];
byte data_tag [4]; byte data_tag [4];
byte data_size [4]; byte data_size [4];
byte addr [4]; byte addr [4];
byte unused [4]; byte unused [4];
// True if header has valid file signature // True if header has valid file signature
bool valid_tag() const; bool valid_tag() const;
}; };
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return header_; } header_t const& header() const { return header_; }
// Pointer to ROM data, for getting track information from // Pointer to ROM data, for getting track information from
byte const* data() const { return rom.begin(); } byte const* data() const { return rom.begin(); }
int data_size() const { return rom.file_size(); } int data_size() const { return rom.file_size(); }
// Adjusts rate play routine is called at, where 1.0 is normal. // Adjusts rate play routine is called at, where 1.0 is normal.
// Can be changed while track is playing. // Can be changed while track is playing.
void set_tempo( double ); void set_tempo( double );
// Sound chip // Sound chip
Hes_Apu& apu() { return apu_; } Hes_Apu& apu() { return apu_; }
Hes_Apu_Adpcm& adpcm() { return adpcm_; } Hes_Apu_Adpcm& adpcm() { return adpcm_; }
// Starts track // Starts track
blargg_err_t start_track( int ); blargg_err_t start_track( int );
// Ends time frame at time t // Ends time frame at time t
typedef int time_t; typedef int time_t;
blargg_err_t end_frame( time_t ); blargg_err_t end_frame( time_t );
// Implementation // Implementation
public: public:
Hes_Core(); Hes_Core();
~Hes_Core(); ~Hes_Core();
virtual void unload(); virtual void unload();
protected: protected:
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
private: private:
enum { idle_addr = 0x1FFF }; enum { idle_addr = 0x1FFF };
typedef int addr_t; typedef int addr_t;
Hes_Cpu cpu; Hes_Cpu cpu;
Rom_Data rom; Rom_Data rom;
header_t header_; header_t header_;
time_t play_period; time_t play_period;
int timer_base; int timer_base;
struct { struct {
time_t last_time; time_t last_time;
int count; int count;
int load; int load;
int raw_load; int raw_load;
byte enabled; byte enabled;
byte fired; byte fired;
} timer; } timer;
struct { struct {
time_t next_vbl; time_t next_vbl;
byte latch; byte latch;
byte control; byte control;
} vdp; } vdp;
struct { struct {
time_t timer; time_t timer;
time_t vdp; time_t vdp;
byte disables; byte disables;
} irq; } irq;
void recalc_timer_load(); void recalc_timer_load();
// large items // large items
byte* write_pages [Hes_Cpu::page_count + 1]; // 0 if unmapped or I/O space byte* write_pages [Hes_Cpu::page_count + 1]; // 0 if unmapped or I/O space
Hes_Apu apu_; Hes_Apu apu_;
Hes_Apu_Adpcm adpcm_; Hes_Apu_Adpcm adpcm_;
byte ram [Hes_Cpu::page_size]; byte ram [Hes_Cpu::page_size];
byte sgx [3 * Hes_Cpu::page_size + Hes_Cpu::cpu_padding]; byte sgx [3 * Hes_Cpu::page_size + Hes_Cpu::cpu_padding];
void irq_changed(); void irq_changed();
void run_until( time_t ); void run_until( time_t );
bool run_cpu( time_t end ); bool run_cpu( time_t end );
int read_mem_( addr_t ); int read_mem_( addr_t );
int read_mem( addr_t ); int read_mem( addr_t );
void write_mem_( addr_t, int data ); void write_mem_( addr_t, int data );
void write_mem( addr_t, int ); void write_mem( addr_t, int );
void write_vdp( int addr, int data ); void write_vdp( int addr, int data );
void set_mmr( int reg, int bank ); void set_mmr( int reg, int bank );
int cpu_done(); int cpu_done();
}; };
#endif #endif

View file

@ -1,123 +1,123 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Hes_Cpu.h" #include "Hes_Cpu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include "Hes_Core.h" #include "Hes_Core.h"
//#include "hes_cpu_log.h" //#include "hes_cpu_log.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#define PAGE HES_CPU_PAGE #define PAGE HES_CPU_PAGE
int Hes_Core::read_mem( addr_t addr ) int Hes_Core::read_mem( addr_t addr )
{ {
check( addr < 0x10000 ); check( addr < 0x10000 );
int result = *cpu.get_code( addr ); int result = *cpu.get_code( addr );
if ( cpu.mmr [PAGE( addr )] == 0xFF ) if ( cpu.mmr [PAGE( addr )] == 0xFF )
result = read_mem_( addr ); result = read_mem_( addr );
return result; return result;
} }
void Hes_Core::write_mem( addr_t addr, int data ) void Hes_Core::write_mem( addr_t addr, int data )
{ {
check( addr < 0x10000 ); check( addr < 0x10000 );
byte* out = write_pages [PAGE( addr )]; byte* out = write_pages [PAGE( addr )];
if ( out ) if ( out )
out [addr & (cpu.page_size - 1)] = data; out [addr & (cpu.page_size - 1)] = data;
else if ( cpu.mmr [PAGE( addr )] == 0xFF ) else if ( cpu.mmr [PAGE( addr )] == 0xFF )
write_mem_( addr, data ); write_mem_( addr, data );
} }
void Hes_Core::set_mmr( int page, int bank ) void Hes_Core::set_mmr( int page, int bank )
{ {
write_pages [page] = 0; write_pages [page] = 0;
byte* data = rom.at_addr( bank * cpu.page_size ); byte* data = rom.at_addr( bank * cpu.page_size );
if ( bank >= 0x80 ) if ( bank >= 0x80 )
{ {
data = 0; data = 0;
switch ( bank ) switch ( bank )
{ {
case 0xF8: case 0xF8:
data = ram; data = ram;
break; break;
case 0xF9: case 0xF9:
case 0xFA: case 0xFA:
case 0xFB: case 0xFB:
data = &sgx [(bank - 0xF9) * cpu.page_size]; data = &sgx [(bank - 0xF9) * cpu.page_size];
break; break;
default: default:
if ( bank != 0xFF ) if ( bank != 0xFF )
dprintf( "Unmapped bank $%02X\n", bank ); dprintf( "Unmapped bank $%02X\n", bank );
data = rom.unmapped(); data = rom.unmapped();
goto end; goto end;
} }
write_pages [page] = data; write_pages [page] = data;
} }
end: end:
cpu.set_mmr( page, bank, data ); cpu.set_mmr( page, bank, data );
} }
#define READ_FAST( addr, out ) \ #define READ_FAST( addr, out ) \
{\ {\
out = READ_CODE( addr );\ out = READ_CODE( addr );\
if ( CPU.mmr [PAGE( addr )] == 0xFF )\ if ( CPU.mmr [PAGE( addr )] == 0xFF )\
{\ {\
FLUSH_TIME();\ FLUSH_TIME();\
out = read_mem_( addr );\ out = read_mem_( addr );\
CACHE_TIME();\ CACHE_TIME();\
}\ }\
} }
#define WRITE_FAST( addr, data ) \ #define WRITE_FAST( addr, data ) \
{\ {\
int page = PAGE( addr );\ int page = PAGE( addr );\
byte* out = write_pages [page];\ byte* out = write_pages [page];\
addr &= CPU.page_size - 1;\ addr &= CPU.page_size - 1;\
if ( out )\ if ( out )\
{\ {\
out [addr] = data;\ out [addr] = data;\
}\ }\
else if ( CPU.mmr [page] == 0xFF )\ else if ( CPU.mmr [page] == 0xFF )\
{\ {\
FLUSH_TIME();\ FLUSH_TIME();\
write_mem_( addr, data );\ write_mem_( addr, data );\
CACHE_TIME();\ CACHE_TIME();\
}\ }\
} }
#define READ_LOW( addr ) (ram [addr]) #define READ_LOW( addr ) (ram [addr])
#define WRITE_LOW( addr, data ) (ram [addr] = data) #define WRITE_LOW( addr, data ) (ram [addr] = data)
#define READ_MEM( addr ) read_mem( addr ) #define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data ) #define WRITE_MEM( addr, data ) write_mem( addr, data )
#define WRITE_VDP( addr, data ) write_vdp( addr, data ) #define WRITE_VDP( addr, data ) write_vdp( addr, data )
#define CPU_DONE( result_out ) { FLUSH_TIME(); result_out = cpu_done(); CACHE_TIME(); } #define CPU_DONE( result_out ) { FLUSH_TIME(); result_out = cpu_done(); CACHE_TIME(); }
#define SET_MMR( reg, bank ) set_mmr( reg, bank ) #define SET_MMR( reg, bank ) set_mmr( reg, bank )
#define CPU cpu #define CPU cpu
#define IDLE_ADDR idle_addr #define IDLE_ADDR idle_addr
#define CPU_BEGIN \ #define CPU_BEGIN \
bool Hes_Core::run_cpu( time_t end_time )\ bool Hes_Core::run_cpu( time_t end_time )\
{\ {\
cpu.set_end_time( end_time ); cpu.set_end_time( end_time );
#include "Hes_Cpu_run.h" #include "Hes_Cpu_run.h"
return illegal_encountered; return illegal_encountered;
} }

View file

@ -1,139 +1,139 @@
// PC Engine CPU emulator for use with HES music files // PC Engine CPU emulator for use with HES music files
// $package // $package
#ifndef HES_CPU_H #ifndef HES_CPU_H
#define HES_CPU_H #define HES_CPU_H
#include "blargg_common.h" #include "blargg_common.h"
class Hes_Cpu { class Hes_Cpu {
public: public:
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
typedef int time_t; typedef int time_t;
typedef int addr_t; typedef int addr_t;
enum { future_time = INT_MAX/2 + 1 }; enum { future_time = INT_MAX/2 + 1 };
void reset(); void reset();
enum { page_bits = 13 }; enum { page_bits = 13 };
enum { page_size = 1 << page_bits }; enum { page_size = 1 << page_bits };
enum { page_count = 0x10000 / page_size }; enum { page_count = 0x10000 / page_size };
void set_mmr( int reg, int bank, void const* code ); void set_mmr( int reg, int bank, void const* code );
byte const* get_code( addr_t ); byte const* get_code( addr_t );
// NOT kept updated during emulation. // NOT kept updated during emulation.
struct registers_t { struct registers_t {
BOOST::uint16_t pc; BOOST::uint16_t pc;
byte a; byte a;
byte x; byte x;
byte y; byte y;
byte flags; byte flags;
byte sp; byte sp;
}; };
registers_t r; registers_t r;
// page mapping registers // page mapping registers
byte mmr [page_count + 1]; byte mmr [page_count + 1];
// Time of beginning of next instruction to be executed // Time of beginning of next instruction to be executed
time_t time() const { return cpu_state->time + cpu_state->base; } time_t time() const { return cpu_state->time + cpu_state->base; }
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; } void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; }
void adjust_time( int delta ) { cpu_state->time += delta; } void adjust_time( int delta ) { cpu_state->time += delta; }
// Clocks past end (negative if before) // Clocks past end (negative if before)
int time_past_end() const { return cpu_state->time; } int time_past_end() const { return cpu_state->time; }
// Time of next IRQ // Time of next IRQ
time_t irq_time() const { return irq_time_; } time_t irq_time() const { return irq_time_; }
void set_irq_time( time_t ); void set_irq_time( time_t );
// Emulation stops once time >= end_time // Emulation stops once time >= end_time
time_t end_time() const { return end_time_; } time_t end_time() const { return end_time_; }
void set_end_time( time_t ); void set_end_time( time_t );
// Subtracts t from all times // Subtracts t from all times
void end_frame( time_t t ); void end_frame( time_t t );
// Can read this many bytes past end of a page // Can read this many bytes past end of a page
enum { cpu_padding = 8 }; enum { cpu_padding = 8 };
private: private:
// noncopyable // noncopyable
Hes_Cpu( const Hes_Cpu& ); Hes_Cpu( const Hes_Cpu& );
Hes_Cpu& operator = ( const Hes_Cpu& ); Hes_Cpu& operator = ( const Hes_Cpu& );
// Implementation // Implementation
public: public:
Hes_Cpu() { cpu_state = &cpu_state_; } Hes_Cpu() { cpu_state = &cpu_state_; }
enum { irq_inhibit_mask = 0x04 }; enum { irq_inhibit_mask = 0x04 };
struct cpu_state_t { struct cpu_state_t {
byte const* code_map [page_count + 1]; byte const* code_map [page_count + 1];
time_t base; time_t base;
int time; int time;
}; };
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
cpu_state_t cpu_state_; cpu_state_t cpu_state_;
time_t irq_time_; time_t irq_time_;
time_t end_time_; time_t end_time_;
private: private:
void set_code_page( int, void const* ); void set_code_page( int, void const* );
inline void update_end_time( time_t end, time_t irq ); inline void update_end_time( time_t end, time_t irq );
}; };
#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits) #define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits)
#if BLARGG_NONPORTABLE #if BLARGG_NONPORTABLE
#define HES_CPU_OFFSET( addr ) (addr) #define HES_CPU_OFFSET( addr ) (addr)
#else #else
#define HES_CPU_OFFSET( addr ) ((addr) & (Hes_Cpu::page_size - 1)) #define HES_CPU_OFFSET( addr ) ((addr) & (Hes_Cpu::page_size - 1))
#endif #endif
inline BOOST::uint8_t const* Hes_Cpu::get_code( addr_t addr ) inline BOOST::uint8_t const* Hes_Cpu::get_code( addr_t addr )
{ {
return cpu_state_.code_map [HES_CPU_PAGE( addr )] + HES_CPU_OFFSET( addr ); return cpu_state_.code_map [HES_CPU_PAGE( addr )] + HES_CPU_OFFSET( addr );
} }
inline void Hes_Cpu::update_end_time( time_t end, time_t irq ) inline void Hes_Cpu::update_end_time( time_t end, time_t irq )
{ {
if ( end > irq && !(r.flags & irq_inhibit_mask) ) if ( end > irq && !(r.flags & irq_inhibit_mask) )
end = irq; end = irq;
cpu_state->time += cpu_state->base - end; cpu_state->time += cpu_state->base - end;
cpu_state->base = end; cpu_state->base = end;
} }
inline void Hes_Cpu::set_irq_time( time_t t ) inline void Hes_Cpu::set_irq_time( time_t t )
{ {
irq_time_ = t; irq_time_ = t;
update_end_time( end_time_, t ); update_end_time( end_time_, t );
} }
inline void Hes_Cpu::set_end_time( time_t t ) inline void Hes_Cpu::set_end_time( time_t t )
{ {
end_time_ = t; end_time_ = t;
update_end_time( t, irq_time_ ); update_end_time( t, irq_time_ );
} }
inline void Hes_Cpu::end_frame( time_t t ) inline void Hes_Cpu::end_frame( time_t t )
{ {
assert( cpu_state == &cpu_state_ ); assert( cpu_state == &cpu_state_ );
cpu_state_.base -= t; cpu_state_.base -= t;
if ( irq_time_ < future_time ) irq_time_ -= t; if ( irq_time_ < future_time ) irq_time_ -= t;
if ( end_time_ < future_time ) end_time_ -= t; if ( end_time_ < future_time ) end_time_ -= t;
} }
inline void Hes_Cpu::set_mmr( int reg, int bank, void const* code ) inline void Hes_Cpu::set_mmr( int reg, int bank, void const* code )
{ {
assert( (unsigned) reg <= page_count ); // allow page past end to be set assert( (unsigned) reg <= page_count ); // allow page past end to be set
assert( (unsigned) bank < 0x100 ); assert( (unsigned) bank < 0x100 );
mmr [reg] = bank; mmr [reg] = bank;
byte const* p = STATIC_CAST(byte const*,code) - HES_CPU_OFFSET( reg << page_bits ); byte const* p = STATIC_CAST(byte const*,code) - HES_CPU_OFFSET( reg << page_bits );
cpu_state->code_map [reg] = p; cpu_state->code_map [reg] = p;
cpu_state_.code_map [reg] = p; cpu_state_.code_map [reg] = p;
} }
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,42 @@
// TurboGrafx-16/PC Engine HES music file emulator // TurboGrafx-16/PC Engine HES music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef HES_EMU_H #ifndef HES_EMU_H
#define HES_EMU_H #define HES_EMU_H
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Hes_Core.h" #include "Hes_Core.h"
class Hes_Emu : public Classic_Emu { class Hes_Emu : public Classic_Emu {
public: public:
static gme_type_t static_type() { return gme_hes_type; } static gme_type_t static_type() { return gme_hes_type; }
// HES file header (see Hes_Core.h) // HES file header (see Hes_Core.h)
typedef Hes_Core::header_t header_t; typedef Hes_Core::header_t header_t;
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return core.header(); } header_t const& header() const { return core.header(); }
blargg_err_t hash_( Hash_Function& ) const; blargg_err_t hash_( Hash_Function& ) const;
// Implementation // Implementation
public: public:
Hes_Emu(); Hes_Emu();
~Hes_Emu(); ~Hes_Emu();
virtual void unload(); virtual void unload();
protected: protected:
virtual blargg_err_t track_info_( track_info_t*, int track ) const; virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int ); virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int ); virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double ); virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& ); virtual void update_eq( blip_eq_t const& );
private: private:
Hes_Core core; Hes_Core core;
}; };
#endif #endif

View file

@ -1,73 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "K051649_Emu.h"
#include "k051649.h"
K051649_Emu::K051649_Emu() { SCC = 0; }
K051649_Emu::~K051649_Emu()
{
if ( SCC ) device_stop_k051649( SCC );
}
int K051649_Emu::set_rate( int clock_rate )
{
if ( SCC )
{
device_stop_k051649( SCC );
SCC = 0;
}
SCC = device_start_k051649( clock_rate );
if ( !SCC )
return 1;
reset();
return 0;
}
void K051649_Emu::reset()
{
device_reset_k051649( SCC );
k051649_set_mute_mask( SCC, 0 );
}
void K051649_Emu::write( int port, int offset, int data )
{
k051649_w( SCC, (port << 1) | 0x00, offset);
k051649_w( SCC, (port << 1) | 0x01, data);
}
void K051649_Emu::mute_voices( int mask )
{
k051649_set_mute_mask( SCC, mask );
}
void K051649_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
k051649_update( SCC, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,33 +0,0 @@
// K051649 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef K051649_EMU_H
#define K051649_EMU_H
class K051649_Emu {
void* SCC;
public:
K051649_Emu();
~K051649_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock_rate );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 5 };
void mute_voices( int mask );
// Writes data to addr
void write( int port, int offset, int data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,77 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "K053260_Emu.h"
#include "k053260.h"
K053260_Emu::K053260_Emu() { chip = 0; }
K053260_Emu::~K053260_Emu()
{
if ( chip ) device_stop_k053260( chip );
}
int K053260_Emu::set_rate( int clock_rate )
{
if ( chip )
{
device_stop_k053260( chip );
chip = 0;
}
chip = device_start_k053260( clock_rate );
if ( !chip )
return 1;
reset();
return 0;
}
void K053260_Emu::reset()
{
device_reset_k053260( chip );
k053260_set_mute_mask( chip, 0 );
}
void K053260_Emu::write( int addr, int data )
{
k053260_w( chip, addr, data);
}
void K053260_Emu::write_rom( int size, int start, int length, void * data )
{
k053260_write_rom( chip, size, start, length, (const UINT8 *) data );
}
void K053260_Emu::mute_voices( int mask )
{
k053260_set_mute_mask( chip, mask );
}
void K053260_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
k053260_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,36 +0,0 @@
// K053260 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef K053260_EMU_H
#define K053260_EMU_H
class K053260_Emu {
void* chip;
public:
K053260_Emu();
~K053260_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock_rate );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 5 };
void mute_voices( int mask );
// Writes data to addr
void write( int addr, int data );
// Scales ROM size, then writes length bytes from data at start offset
void write_rom( int size, int start, int length, void * data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,79 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "K054539_Emu.h"
#include "k054539.h"
K054539_Emu::K054539_Emu() { chip = 0; }
K054539_Emu::~K054539_Emu()
{
if ( chip ) device_stop_k054539( chip );
}
int K054539_Emu::set_rate( int clock_rate, int flags )
{
if ( chip )
{
device_stop_k054539( chip );
chip = 0;
}
chip = device_start_k054539( clock_rate );
if ( !chip )
return 1;
k054539_init_flags( chip, flags );
reset();
return 0;
}
void K054539_Emu::reset()
{
device_reset_k054539( chip );
k054539_set_mute_mask( chip, 0 );
}
void K054539_Emu::write( int addr, int data )
{
k054539_w( chip, addr, data);
}
void K054539_Emu::write_rom( int size, int start, int length, void * data )
{
k054539_write_rom( chip, size, start, length, (const UINT8 *) data );
}
void K054539_Emu::mute_voices( int mask )
{
k054539_set_mute_mask( chip, mask );
}
void K054539_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
k054539_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,36 +0,0 @@
// K054539 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef K054539_EMU_H
#define K054539_EMU_H
class K054539_Emu {
void* chip;
public:
K054539_Emu();
~K054539_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock_rate, int flags );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 5 };
void mute_voices( int mask );
// Writes data to addr
void write( int addr, int data );
// Scales ROM size, then writes length bytes from data at start offset
void write_rom( int size, int start, int length, void * data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,214 +1,214 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Kss_Core.h" #include "Kss_Core.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you /* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size ) Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size )
{ {
memset( unmapped_read, 0xFF, sizeof unmapped_read ); memset( unmapped_read, 0xFF, sizeof unmapped_read );
} }
Kss_Core::~Kss_Core() { } Kss_Core::~Kss_Core() { }
void Kss_Core::unload() void Kss_Core::unload()
{ {
rom.clear(); rom.clear();
} }
static blargg_err_t check_kss_header( void const* header ) static blargg_err_t check_kss_header( void const* header )
{ {
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
return blargg_err_file_type; return blargg_err_file_type;
return blargg_ok; return blargg_ok;
} }
blargg_err_t Kss_Core::load_( Data_Reader& in ) blargg_err_t Kss_Core::load_( Data_Reader& in )
{ {
memset( &header_, 0, sizeof header_ ); memset( &header_, 0, sizeof header_ );
assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 ); assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 );
RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) ); RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) );
RETURN_ERR( check_kss_header( header_.tag ) ); RETURN_ERR( check_kss_header( header_.tag ) );
header_.last_track [0] = 255; header_.last_track [0] = 255;
if ( header_.tag [3] == 'C' ) if ( header_.tag [3] == 'C' )
{ {
if ( header_.extra_header ) if ( header_.extra_header )
{ {
header_.extra_header = 0; header_.extra_header = 0;
set_warning( "Unknown data in header" ); set_warning( "Unknown data in header" );
} }
if ( header_.device_flags & ~0x0F ) if ( header_.device_flags & ~0x0F )
{ {
header_.device_flags &= 0x0F; header_.device_flags &= 0x0F;
set_warning( "Unknown data in header" ); set_warning( "Unknown data in header" );
} }
} }
else if ( header_.extra_header ) else if ( header_.extra_header )
{ {
if ( header_.extra_header != header_.ext_size ) if ( header_.extra_header != header_.ext_size )
{ {
header_.extra_header = 0; header_.extra_header = 0;
set_warning( "Invalid extra_header_size" ); set_warning( "Invalid extra_header_size" );
} }
else else
{ {
memcpy( header_.data_size, rom.begin(), header_.ext_size ); memcpy( header_.data_size, rom.begin(), header_.ext_size );
} }
} }
#ifndef NDEBUG #ifndef NDEBUG
{ {
int ram_mode = header_.device_flags & 0x84; // MSX int ram_mode = header_.device_flags & 0x84; // MSX
if ( header_.device_flags & 0x02 ) // SMS if ( header_.device_flags & 0x02 ) // SMS
ram_mode = (header_.device_flags & 0x88); ram_mode = (header_.device_flags & 0x88);
if ( ram_mode ) if ( ram_mode )
dprintf( "RAM not supported\n" ); // TODO: support dprintf( "RAM not supported\n" ); // TODO: support
} }
#endif #endif
return blargg_ok; return blargg_ok;
} }
void Kss_Core::jsr( byte const (&addr) [2] ) void Kss_Core::jsr( byte const (&addr) [2] )
{ {
ram [--cpu.r.sp] = idle_addr >> 8; ram [--cpu.r.sp] = idle_addr >> 8;
ram [--cpu.r.sp] = idle_addr & 0xFF; ram [--cpu.r.sp] = idle_addr & 0xFF;
cpu.r.pc = get_le16( addr ); cpu.r.pc = get_le16( addr );
} }
blargg_err_t Kss_Core::start_track( int track ) blargg_err_t Kss_Core::start_track( int track )
{ {
memset( ram, 0xC9, 0x4000 ); memset( ram, 0xC9, 0x4000 );
memset( ram + 0x4000, 0, sizeof ram - 0x4000 ); memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
// copy driver code to lo RAM // copy driver code to lo RAM
static byte const bios [] = { static byte const bios [] = {
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG 0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG 0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
}; };
static byte const vectors [] = { static byte const vectors [] = {
0xC3, 0x01, 0x00, // $0093: WRTPSG vector 0xC3, 0x01, 0x00, // $0093: WRTPSG vector
0xC3, 0x09, 0x00, // $0096: RDPSG vector 0xC3, 0x09, 0x00, // $0096: RDPSG vector
}; };
memcpy( ram + 0x01, bios, sizeof bios ); memcpy( ram + 0x01, bios, sizeof bios );
memcpy( ram + 0x93, vectors, sizeof vectors ); memcpy( ram + 0x93, vectors, sizeof vectors );
// copy non-banked data into RAM // copy non-banked data into RAM
int load_addr = get_le16( header_.load_addr ); int load_addr = get_le16( header_.load_addr );
int orig_load_size = get_le16( header_.load_size ); int orig_load_size = get_le16( header_.load_size );
int load_size = min( orig_load_size, rom.file_size() ); int load_size = min( orig_load_size, rom.file_size() );
load_size = min( load_size, (int) mem_size - load_addr ); load_size = min( load_size, (int) mem_size - load_addr );
if ( load_size != orig_load_size ) if ( load_size != orig_load_size )
set_warning( "Excessive data size" ); set_warning( "Excessive data size" );
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size ); memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
rom.set_addr( -load_size - header_.extra_header ); rom.set_addr( -load_size - header_.extra_header );
// check available bank data // check available bank data
int const bank_size = this->bank_size(); int const bank_size = this->bank_size();
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size; int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
bank_count = header_.bank_mode & 0x7F; bank_count = header_.bank_mode & 0x7F;
if ( bank_count > max_banks ) if ( bank_count > max_banks )
{ {
bank_count = max_banks; bank_count = max_banks;
set_warning( "Bank data missing" ); set_warning( "Bank data missing" );
} }
//dprintf( "load_size : $%X\n", load_size ); //dprintf( "load_size : $%X\n", load_size );
//dprintf( "bank_size : $%X\n", bank_size ); //dprintf( "bank_size : $%X\n", bank_size );
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F ); //dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
ram [idle_addr] = 0xFF; ram [idle_addr] = 0xFF;
cpu.reset( unmapped_write, unmapped_read ); cpu.reset( unmapped_write, unmapped_read );
cpu.map_mem( 0, mem_size, ram, ram ); cpu.map_mem( 0, mem_size, ram, ram );
cpu.r.sp = 0xF380; cpu.r.sp = 0xF380;
cpu.r.b.a = track; cpu.r.b.a = track;
cpu.r.b.h = 0; cpu.r.b.h = 0;
next_play = play_period; next_play = play_period;
gain_updated = false; gain_updated = false;
jsr( header_.init_addr ); jsr( header_.init_addr );
return blargg_ok; return blargg_ok;
} }
void Kss_Core::set_bank( int logical, int physical ) void Kss_Core::set_bank( int logical, int physical )
{ {
int const bank_size = this->bank_size(); int const bank_size = this->bank_size();
int addr = 0x8000; int addr = 0x8000;
if ( logical && bank_size == 8 * 1024 ) if ( logical && bank_size == 8 * 1024 )
addr = 0xA000; addr = 0xA000;
physical -= header_.first_bank; physical -= header_.first_bank;
if ( (unsigned) physical >= (unsigned) bank_count ) if ( (unsigned) physical >= (unsigned) bank_count )
{ {
byte* data = ram + addr; byte* data = ram + addr;
cpu.map_mem( addr, bank_size, data, data ); cpu.map_mem( addr, bank_size, data, data );
} }
else else
{ {
int phys = physical * bank_size; int phys = physical * bank_size;
for ( int offset = 0; offset < bank_size; offset += cpu.page_size ) for ( int offset = 0; offset < bank_size; offset += cpu.page_size )
cpu.map_mem( addr + offset, cpu.page_size, cpu.map_mem( addr + offset, cpu.page_size,
unmapped_write, rom.at_addr( phys + offset ) ); unmapped_write, rom.at_addr( phys + offset ) );
} }
} }
void Kss_Core::cpu_out( time_t, addr_t addr, int data ) void Kss_Core::cpu_out( time_t, addr_t addr, int data )
{ {
dprintf( "OUT $%04X,$%02X\n", addr, data ); dprintf( "OUT $%04X,$%02X\n", addr, data );
} }
int Kss_Core::cpu_in( time_t, addr_t addr ) int Kss_Core::cpu_in( time_t, addr_t addr )
{ {
dprintf( "IN $%04X\n", addr ); dprintf( "IN $%04X\n", addr );
return 0xFF; return 0xFF;
} }
blargg_err_t Kss_Core::end_frame( time_t end ) blargg_err_t Kss_Core::end_frame( time_t end )
{ {
while ( cpu.time() < end ) while ( cpu.time() < end )
{ {
time_t next = min( end, next_play ); time_t next = min( end, next_play );
run_cpu( next ); run_cpu( next );
if ( cpu.r.pc == idle_addr ) if ( cpu.r.pc == idle_addr )
cpu.set_time( next ); cpu.set_time( next );
if ( cpu.time() >= next_play ) if ( cpu.time() >= next_play )
{ {
next_play += play_period; next_play += play_period;
if ( cpu.r.pc == idle_addr ) if ( cpu.r.pc == idle_addr )
{ {
if ( !gain_updated ) if ( !gain_updated )
{ {
gain_updated = true; gain_updated = true;
update_gain(); update_gain();
} }
jsr( header_.play_addr ); jsr( header_.play_addr );
} }
} }
} }
next_play -= end; next_play -= end;
check( next_play >= 0 ); check( next_play >= 0 );
cpu.adjust_time( -end ); cpu.adjust_time( -end );
return blargg_ok; return blargg_ok;
} }

View file

@ -1,100 +1,100 @@
// MSX computer KSS music file emulator // MSX computer KSS music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef KSS_CORE_H #ifndef KSS_CORE_H
#define KSS_CORE_H #define KSS_CORE_H
#include "Gme_Loader.h" #include "Gme_Loader.h"
#include "Rom_Data.h" #include "Rom_Data.h"
#include "Z80_Cpu.h" #include "Z80_Cpu.h"
class Kss_Core : public Gme_Loader { class Kss_Core : public Gme_Loader {
public: public:
// KSS file header // KSS file header
struct header_t struct header_t
{ {
enum { size = 0x20 }; enum { size = 0x20 };
enum { base_size = 0x10 }; enum { base_size = 0x10 };
enum { ext_size = size - base_size }; enum { ext_size = size - base_size };
byte tag [4]; byte tag [4];
byte load_addr [2]; byte load_addr [2];
byte load_size [2]; byte load_size [2];
byte init_addr [2]; byte init_addr [2];
byte play_addr [2]; byte play_addr [2];
byte first_bank; byte first_bank;
byte bank_mode; byte bank_mode;
byte extra_header; byte extra_header;
byte device_flags; byte device_flags;
// KSSX extended data, if extra_header==0x10 // KSSX extended data, if extra_header==0x10
byte data_size [4]; byte data_size [4];
byte unused [4]; byte unused [4];
byte first_track [2]; byte first_track [2];
byte last_track [2]; // if no extended data, we set this to 0xFF byte last_track [2]; // if no extended data, we set this to 0xFF
byte psg_vol; byte psg_vol;
byte scc_vol; byte scc_vol;
byte msx_music_vol; byte msx_music_vol;
byte msx_audio_vol; byte msx_audio_vol;
}; };
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return header_; } header_t const& header() const { return header_; }
// ROM data // ROM data
Rom_Data const& rom_() const { return rom; } Rom_Data const& rom_() const { return rom; }
typedef int time_t; typedef int time_t;
void set_play_period( time_t p ) { play_period = p; } void set_play_period( time_t p ) { play_period = p; }
blargg_err_t start_track( int ); blargg_err_t start_track( int );
blargg_err_t end_frame( time_t ); blargg_err_t end_frame( time_t );
protected: protected:
typedef Z80_Cpu Kss_Cpu; typedef Z80_Cpu Kss_Cpu;
Kss_Cpu cpu; Kss_Cpu cpu;
void set_bank( int logical, int physical ); void set_bank( int logical, int physical );
typedef int addr_t; typedef int addr_t;
virtual void cpu_write( addr_t, int ) = 0; virtual void cpu_write( addr_t, int ) = 0;
virtual int cpu_in( time_t, addr_t ); virtual int cpu_in( time_t, addr_t );
virtual void cpu_out( time_t, addr_t, int ); virtual void cpu_out( time_t, addr_t, int );
// Called after one frame of emulation // Called after one frame of emulation
virtual void update_gain() = 0; virtual void update_gain() = 0;
// Implementation // Implementation
public: public:
Kss_Core(); Kss_Core();
virtual ~Kss_Core(); virtual ~Kss_Core();
protected: protected:
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual void unload(); virtual void unload();
private: private:
enum { idle_addr = 0xFFFF }; enum { idle_addr = 0xFFFF };
Rom_Data rom; Rom_Data rom;
header_t header_; header_t header_;
bool gain_updated; bool gain_updated;
int bank_count; int bank_count;
time_t play_period; time_t play_period;
time_t next_play; time_t next_play;
// large items // large items
enum { mem_size = 0x10000 }; enum { mem_size = 0x10000 };
byte ram [mem_size + Kss_Cpu::cpu_padding]; byte ram [mem_size + Kss_Cpu::cpu_padding];
byte unmapped_read [0x100]; // TODO: why isn't this page_size? byte unmapped_read [0x100]; // TODO: why isn't this page_size?
// because CPU can't read beyond this in last page? or because it will spill into unmapped_write? // because CPU can't read beyond this in last page? or because it will spill into unmapped_write?
byte unmapped_write [Kss_Cpu::page_size]; byte unmapped_write [Kss_Cpu::page_size];
int bank_size() const { return (16 * 1024) >> (header_.bank_mode >> 7 & 1); } int bank_size() const { return (16 * 1024) >> (header_.bank_mode >> 7 & 1); }
bool run_cpu( time_t end ); bool run_cpu( time_t end );
void jsr( byte const (&addr) [2] ); void jsr( byte const (&addr) [2] );
}; };
#endif #endif

View file

@ -1,493 +1,493 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Kss_Emu.h" #include "Kss_Emu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you /* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#define IF_PTR( ptr ) if ( ptr ) (ptr) #define IF_PTR( ptr ) if ( ptr ) (ptr)
int const clock_rate = 3579545; int const clock_rate = 3579545;
#define FOR_EACH_APU( macro )\ #define FOR_EACH_APU( macro )\
{\ {\
macro( sms.psg );\ macro( sms.psg );\
macro( sms.fm );\ macro( sms.fm );\
macro( msx.psg );\ macro( msx.psg );\
macro( msx.scc );\ macro( msx.scc );\
macro( msx.music );\ macro( msx.music );\
macro( msx.audio );\ macro( msx.audio );\
} }
Kss_Emu::Kss_Emu() : Kss_Emu::Kss_Emu() :
core( this ) core( this )
{ {
#define ACTION( apu ) { core.apu = NULL; } #define ACTION( apu ) { core.apu = NULL; }
FOR_EACH_APU( ACTION ); FOR_EACH_APU( ACTION );
#undef ACTION #undef ACTION
set_type( gme_kss_type ); set_type( gme_kss_type );
} }
Kss_Emu::~Kss_Emu() Kss_Emu::~Kss_Emu()
{ {
unload(); unload();
} }
inline void Kss_Emu::Core::unload() inline void Kss_Emu::Core::unload()
{ {
#define ACTION( ptr ) { delete (ptr); (ptr) = 0; } #define ACTION( ptr ) { delete (ptr); (ptr) = 0; }
FOR_EACH_APU( ACTION ); FOR_EACH_APU( ACTION );
#undef ACTION #undef ACTION
} }
void Kss_Emu::unload() void Kss_Emu::unload()
{ {
core.unload(); core.unload();
Classic_Emu::unload(); Classic_Emu::unload();
} }
// Track info // Track info
static void copy_kss_fields( Kss_Core::header_t const& h, track_info_t* out ) static void copy_kss_fields( Kss_Core::header_t const& h, track_info_t* out )
{ {
const char* system = "MSX"; const char* system = "MSX";
if ( h.device_flags & 0x02 ) if ( h.device_flags & 0x02 )
{ {
system = "Sega Master System"; system = "Sega Master System";
if ( h.device_flags & 0x04 ) if ( h.device_flags & 0x04 )
system = "Game Gear"; system = "Game Gear";
if ( h.device_flags & 0x01 ) if ( h.device_flags & 0x01 )
system = "Sega Mark III"; system = "Sega Mark III";
} }
else else
{ {
if ( h.device_flags & 0x09 ) if ( h.device_flags & 0x09 )
system = "MSX + FM Sound"; system = "MSX + FM Sound";
} }
Gme_File::copy_field_( out->system, system ); Gme_File::copy_field_( out->system, system );
} }
static void hash_kss_file( Kss_Core::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) static void hash_kss_file( Kss_Core::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
{ {
out.hash_( &h.load_addr[0], sizeof(h.load_addr) ); out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
out.hash_( &h.load_size[0], sizeof(h.load_size) ); out.hash_( &h.load_size[0], sizeof(h.load_size) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) ); out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) ); out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
out.hash_( &h.first_bank, sizeof(h.first_bank) ); out.hash_( &h.first_bank, sizeof(h.first_bank) );
out.hash_( &h.bank_mode, sizeof(h.bank_mode) ); out.hash_( &h.bank_mode, sizeof(h.bank_mode) );
out.hash_( &h.extra_header, sizeof(h.extra_header) ); out.hash_( &h.extra_header, sizeof(h.extra_header) );
out.hash_( &h.device_flags, sizeof(h.device_flags) ); out.hash_( &h.device_flags, sizeof(h.device_flags) );
out.hash_( data, data_size ); out.hash_( data, data_size );
} }
blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const
{ {
copy_kss_fields( header(), out ); copy_kss_fields( header(), out );
// TODO: remove // TODO: remove
//if ( msx.music ) strcpy( out->system, "msxmusic" ); //if ( msx.music ) strcpy( out->system, "msxmusic" );
//if ( msx.audio ) strcpy( out->system, "msxaudio" ); //if ( msx.audio ) strcpy( out->system, "msxaudio" );
//if ( sms.fm ) strcpy( out->system, "fmunit" ); //if ( sms.fm ) strcpy( out->system, "fmunit" );
return blargg_ok; return blargg_ok;
} }
static blargg_err_t check_kss_header( void const* header ) static blargg_err_t check_kss_header( void const* header )
{ {
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
return blargg_err_file_type; return blargg_err_file_type;
return blargg_ok; return blargg_ok;
} }
struct Kss_File : Gme_Info_ struct Kss_File : Gme_Info_
{ {
Kss_Emu::header_t const* header_; Kss_Emu::header_t const* header_;
Kss_File() { set_type( gme_kss_type ); } Kss_File() { set_type( gme_kss_type ); }
blargg_err_t load_mem_( byte const begin [], int size ) blargg_err_t load_mem_( byte const begin [], int size )
{ {
header_ = ( Kss_Emu::header_t const* ) begin; header_ = ( Kss_Emu::header_t const* ) begin;
if ( header_->tag [3] == 'X' && header_->extra_header == 0x10 ) if ( header_->tag [3] == 'X' && header_->extra_header == 0x10 )
set_track_count( get_le16( header_->last_track ) + 1 ); set_track_count( get_le16( header_->last_track ) + 1 );
return check_kss_header( header_ ); return check_kss_header( header_ );
} }
blargg_err_t track_info_( track_info_t* out, int ) const blargg_err_t track_info_( track_info_t* out, int ) const
{ {
copy_kss_fields( *header_, out ); copy_kss_fields( *header_, out );
return blargg_ok; return blargg_ok;
} }
blargg_err_t hash_( Hash_Function& out ) const blargg_err_t hash_( Hash_Function& out ) const
{ {
hash_kss_file( *header_, file_begin() + Kss_Core::header_t::base_size, file_end() - file_begin() - Kss_Core::header_t::base_size, out ); hash_kss_file( *header_, file_begin() + Kss_Core::header_t::base_size, file_end() - file_begin() - Kss_Core::header_t::base_size, out );
return blargg_ok; return blargg_ok;
} }
}; };
static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; } static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; }
static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; } static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; }
gme_type_t_ const gme_kss_type [1] = {{ gme_type_t_ const gme_kss_type [1] = {{
"MSX", "MSX",
256, 256,
&new_kss_emu, &new_kss_emu,
&new_kss_file, &new_kss_file,
"KSS", "KSS",
0x03 0x03
}}; }};
// Setup // Setup
void Kss_Emu::Core::update_gain_() void Kss_Emu::Core::update_gain_()
{ {
double g = emu.gain(); double g = emu.gain();
if ( msx.music || msx.audio || sms.fm ) if ( msx.music || msx.audio || sms.fm )
{ {
g *= 0.3; g *= 0.3;
} }
else else
{ {
g *= 1.2; g *= 1.2;
if ( scc_accessed ) if ( scc_accessed )
g *= 1.4; g *= 1.4;
} }
#define ACTION( apu ) IF_PTR( apu )->volume( g ) #define ACTION( apu ) IF_PTR( apu )->volume( g )
FOR_EACH_APU( ACTION ); FOR_EACH_APU( ACTION );
#undef ACTION #undef ACTION
} }
static blargg_err_t new_opl_apu( Opl_Apu::type_t type, Opl_Apu** out ) static blargg_err_t new_opl_apu( Opl_Apu::type_t type, Opl_Apu** out )
{ {
check( !*out ); check( !*out );
CHECK_ALLOC( *out = BLARGG_NEW( Opl_Apu ) ); CHECK_ALLOC( *out = BLARGG_NEW( Opl_Apu ) );
blip_time_t const period = 72; blip_time_t const period = 72;
int const rate = clock_rate / period; int const rate = clock_rate / period;
return (*out)->init( rate * period, rate, period, type ); return (*out)->init( rate * period, rate, period, type );
} }
blargg_err_t Kss_Emu::load_( Data_Reader& in ) blargg_err_t Kss_Emu::load_( Data_Reader& in )
{ {
RETURN_ERR( core.load( in ) ); RETURN_ERR( core.load( in ) );
set_warning( core.warning() ); set_warning( core.warning() );
set_track_count( get_le16( header().last_track ) + 1 ); set_track_count( get_le16( header().last_track ) + 1 );
core.scc_enabled = false; core.scc_enabled = false;
if ( header().device_flags & 0x02 ) // Sega Master System if ( header().device_flags & 0x02 ) // Sega Master System
{ {
int const osc_count = Sms_Apu::osc_count + Opl_Apu::osc_count; int const osc_count = Sms_Apu::osc_count + Opl_Apu::osc_count;
static const char* const names [osc_count] = { static const char* const names [osc_count] = {
"Square 1", "Square 2", "Square 3", "Noise", "FM" "Square 1", "Square 2", "Square 3", "Noise", "FM"
}; };
set_voice_names( names ); set_voice_names( names );
static int const types [osc_count] = { static int const types [osc_count] = {
wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0 wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0
}; };
set_voice_types( types ); set_voice_types( types );
// sms.psg // sms.psg
set_voice_count( Sms_Apu::osc_count ); set_voice_count( Sms_Apu::osc_count );
check( !core.sms.psg ); check( !core.sms.psg );
CHECK_ALLOC( core.sms.psg = BLARGG_NEW Sms_Apu ); CHECK_ALLOC( core.sms.psg = BLARGG_NEW Sms_Apu );
// sms.fm // sms.fm
if ( header().device_flags & 0x01 ) if ( header().device_flags & 0x01 )
{ {
set_voice_count( osc_count ); set_voice_count( osc_count );
RETURN_ERR( new_opl_apu( Opl_Apu::type_smsfmunit, &core.sms.fm ) ); RETURN_ERR( new_opl_apu( Opl_Apu::type_smsfmunit, &core.sms.fm ) );
} }
} }
else // MSX else // MSX
{ {
int const osc_count = Ay_Apu::osc_count + Opl_Apu::osc_count; int const osc_count = Ay_Apu::osc_count + Opl_Apu::osc_count;
static const char* const names [osc_count] = { static const char* const names [osc_count] = {
"Square 1", "Square 2", "Square 3", "FM" "Square 1", "Square 2", "Square 3", "FM"
}; };
set_voice_names( names ); set_voice_names( names );
static int const types [osc_count] = { static int const types [osc_count] = {
wave_type+1, wave_type+3, wave_type+2, wave_type+0 wave_type+1, wave_type+3, wave_type+2, wave_type+0
}; };
set_voice_types( types ); set_voice_types( types );
// msx.psg // msx.psg
set_voice_count( Ay_Apu::osc_count ); set_voice_count( Ay_Apu::osc_count );
check( !core.msx.psg ); check( !core.msx.psg );
CHECK_ALLOC( core.msx.psg = BLARGG_NEW Ay_Apu ); CHECK_ALLOC( core.msx.psg = BLARGG_NEW Ay_Apu );
if ( header().device_flags & 0x10 ) if ( header().device_flags & 0x10 )
set_warning( "MSX stereo not supported" ); set_warning( "MSX stereo not supported" );
// msx.music // msx.music
if ( header().device_flags & 0x01 ) if ( header().device_flags & 0x01 )
{ {
set_voice_count( osc_count ); set_voice_count( osc_count );
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxmusic, &core.msx.music ) ); RETURN_ERR( new_opl_apu( Opl_Apu::type_msxmusic, &core.msx.music ) );
} }
// msx.audio // msx.audio
if ( header().device_flags & 0x08 ) if ( header().device_flags & 0x08 )
{ {
set_voice_count( osc_count ); set_voice_count( osc_count );
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxaudio, &core.msx.audio ) ); RETURN_ERR( new_opl_apu( Opl_Apu::type_msxaudio, &core.msx.audio ) );
} }
if ( !(header().device_flags & 0x80) ) if ( !(header().device_flags & 0x80) )
{ {
if ( !(header().device_flags & 0x84) ) if ( !(header().device_flags & 0x84) )
core.scc_enabled = core.scc_enabled_true; core.scc_enabled = core.scc_enabled_true;
// msx.scc // msx.scc
check( !core.msx.scc ); check( !core.msx.scc );
CHECK_ALLOC( core.msx.scc = BLARGG_NEW Scc_Apu ); CHECK_ALLOC( core.msx.scc = BLARGG_NEW Scc_Apu );
int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count; int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count;
static const char* const names [osc_count] = { static const char* const names [osc_count] = {
"Square 1", "Square 2", "Square 3", "Square 1", "Square 2", "Square 3",
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5" "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5"
}; };
set_voice_names( names ); set_voice_names( names );
static int const types [osc_count] = { static int const types [osc_count] = {
wave_type+1, wave_type+3, wave_type+2, wave_type+1, wave_type+3, wave_type+2,
wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7, wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7,
}; };
set_voice_types( types ); set_voice_types( types );
set_voice_count( osc_count ); set_voice_count( osc_count );
} }
} }
set_silence_lookahead( 6 ); set_silence_lookahead( 6 );
if ( core.sms.fm || core.msx.music || core.msx.audio ) if ( core.sms.fm || core.msx.music || core.msx.audio )
{ {
if ( !Opl_Apu::supported() ) if ( !Opl_Apu::supported() )
set_warning( "FM sound not supported" ); set_warning( "FM sound not supported" );
else else
set_silence_lookahead( 3 ); // Opl_Apu is really slow set_silence_lookahead( 3 ); // Opl_Apu is really slow
} }
return setup_buffer( ::clock_rate ); return setup_buffer( ::clock_rate );
} }
void Kss_Emu::update_eq( blip_eq_t const& eq ) void Kss_Emu::update_eq( blip_eq_t const& eq )
{ {
#define ACTION( apu ) IF_PTR( core.apu )->treble_eq( eq ) #define ACTION( apu ) IF_PTR( core.apu )->treble_eq( eq )
FOR_EACH_APU( ACTION ); FOR_EACH_APU( ACTION );
#undef ACTION #undef ACTION
} }
void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{ {
if ( core.sms.psg ) // Sega Master System if ( core.sms.psg ) // Sega Master System
{ {
i -= core.sms.psg->osc_count; i -= core.sms.psg->osc_count;
if ( i < 0 ) if ( i < 0 )
{ {
core.sms.psg->set_output( i + core.sms.psg->osc_count, center, left, right ); core.sms.psg->set_output( i + core.sms.psg->osc_count, center, left, right );
return; return;
} }
if ( core.sms.fm && i < core.sms.fm->osc_count ) if ( core.sms.fm && i < core.sms.fm->osc_count )
core.sms.fm->set_output( i, center, NULL, NULL ); core.sms.fm->set_output( i, center, NULL, NULL );
} }
else if ( core.msx.psg ) // MSX else if ( core.msx.psg ) // MSX
{ {
i -= core.msx.psg->osc_count; i -= core.msx.psg->osc_count;
if ( i < 0 ) if ( i < 0 )
{ {
core.msx.psg->set_output( i + core.msx.psg->osc_count, center ); core.msx.psg->set_output( i + core.msx.psg->osc_count, center );
return; return;
} }
if ( core.msx.scc && i < core.msx.scc->osc_count ) core.msx.scc ->set_output( i, center ); if ( core.msx.scc && i < core.msx.scc->osc_count ) core.msx.scc ->set_output( i, center );
if ( core.msx.music && i < core.msx.music->osc_count ) core.msx.music->set_output( i, center, NULL, NULL ); if ( core.msx.music && i < core.msx.music->osc_count ) core.msx.music->set_output( i, center, NULL, NULL );
if ( core.msx.audio && i < core.msx.audio->osc_count ) core.msx.audio->set_output( i, center, NULL, NULL ); if ( core.msx.audio && i < core.msx.audio->osc_count ) core.msx.audio->set_output( i, center, NULL, NULL );
} }
} }
void Kss_Emu::set_tempo_( double t ) void Kss_Emu::set_tempo_( double t )
{ {
int period = (header().device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60); int period = (header().device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60);
core.set_play_period( (Kss_Core::time_t) (period / t) ); core.set_play_period( (Kss_Core::time_t) (period / t) );
} }
blargg_err_t Kss_Emu::start_track_( int track ) blargg_err_t Kss_Emu::start_track_( int track )
{ {
RETURN_ERR( Classic_Emu::start_track_( track ) ); RETURN_ERR( Classic_Emu::start_track_( track ) );
#define ACTION( apu ) IF_PTR( core.apu )->reset() #define ACTION( apu ) IF_PTR( core.apu )->reset()
FOR_EACH_APU( ACTION ); FOR_EACH_APU( ACTION );
#undef ACTION #undef ACTION
core.scc_accessed = false; core.scc_accessed = false;
core.update_gain_(); core.update_gain_();
return core.start_track( track ); return core.start_track( track );
} }
void Kss_Emu::Core::cpu_write_( addr_t addr, int data ) void Kss_Emu::Core::cpu_write_( addr_t addr, int data )
{ {
// TODO: SCC+ support // TODO: SCC+ support
data &= 0xFF; data &= 0xFF;
switch ( addr ) switch ( addr )
{ {
case 0x9000: case 0x9000:
set_bank( 0, data ); set_bank( 0, data );
return; return;
case 0xB000: case 0xB000:
set_bank( 1, data ); set_bank( 1, data );
return; return;
case 0xBFFE: // selects between mapping areas (we just always enable both) case 0xBFFE: // selects between mapping areas (we just always enable both)
if ( data == 0 || data == 0x20 ) if ( data == 0 || data == 0x20 )
return; return;
} }
int scc_addr = (addr & 0xDFFF) - 0x9800; int scc_addr = (addr & 0xDFFF) - 0x9800;
if ( (unsigned) scc_addr < 0xB0 && msx.scc ) if ( (unsigned) scc_addr < 0xB0 && msx.scc )
{ {
scc_accessed = true; scc_accessed = true;
//if ( (unsigned) (scc_addr - 0x90) < 0x10 ) //if ( (unsigned) (scc_addr - 0x90) < 0x10 )
// scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F // scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F
if ( scc_addr < Scc_Apu::reg_count ) if ( scc_addr < Scc_Apu::reg_count )
msx.scc->write( cpu.time(), addr, data ); msx.scc->write( cpu.time(), addr, data );
return; return;
} }
dprintf( "LD ($%04X),$%02X\n", addr, data ); dprintf( "LD ($%04X),$%02X\n", addr, data );
} }
void Kss_Emu::Core::cpu_write( addr_t addr, int data ) void Kss_Emu::Core::cpu_write( addr_t addr, int data )
{ {
*cpu.write( addr ) = data; *cpu.write( addr ) = data;
if ( (addr & scc_enabled) == 0x8000 ) if ( (addr & scc_enabled) == 0x8000 )
cpu_write_( addr, data ); cpu_write_( addr, data );
} }
void Kss_Emu::Core::cpu_out( time_t time, addr_t addr, int data ) void Kss_Emu::Core::cpu_out( time_t time, addr_t addr, int data )
{ {
data &= 0xFF; data &= 0xFF;
switch ( addr & 0xFF ) switch ( addr & 0xFF )
{ {
case 0xA0: case 0xA0:
if ( msx.psg ) if ( msx.psg )
msx.psg->write_addr( data ); msx.psg->write_addr( data );
return; return;
case 0xA1: case 0xA1:
if ( msx.psg ) if ( msx.psg )
msx.psg->write_data( time, data ); msx.psg->write_data( time, data );
return; return;
case 0x06: case 0x06:
if ( sms.psg && (header().device_flags & 0x04) ) if ( sms.psg && (header().device_flags & 0x04) )
{ {
sms.psg->write_ggstereo( time, data ); sms.psg->write_ggstereo( time, data );
return; return;
} }
break; break;
case 0x7E: case 0x7E:
case 0x7F: case 0x7F:
if ( sms.psg ) if ( sms.psg )
{ {
sms.psg->write_data( time, data ); sms.psg->write_data( time, data );
return; return;
} }
break; break;
#define OPL_WRITE_HANDLER( base, opl )\ #define OPL_WRITE_HANDLER( base, opl )\
case base : if ( opl ) { opl->write_addr( data ); return; } break;\ case base : if ( opl ) { opl->write_addr( data ); return; } break;\
case base+1: if ( opl ) { opl->write_data( time, data ); return; } break; case base+1: if ( opl ) { opl->write_data( time, data ); return; } break;
OPL_WRITE_HANDLER( 0x7C, msx.music ) OPL_WRITE_HANDLER( 0x7C, msx.music )
OPL_WRITE_HANDLER( 0xC0, msx.audio ) OPL_WRITE_HANDLER( 0xC0, msx.audio )
OPL_WRITE_HANDLER( 0xF0, sms.fm ) OPL_WRITE_HANDLER( 0xF0, sms.fm )
case 0xFE: case 0xFE:
set_bank( 0, data ); set_bank( 0, data );
return; return;
#ifndef NDEBUG #ifndef NDEBUG
case 0xA8: // PPI case 0xA8: // PPI
return; return;
#endif #endif
} }
Kss_Core::cpu_out( time, addr, data ); Kss_Core::cpu_out( time, addr, data );
} }
int Kss_Emu::Core::cpu_in( time_t time, addr_t addr ) int Kss_Emu::Core::cpu_in( time_t time, addr_t addr )
{ {
switch ( addr & 0xFF ) switch ( addr & 0xFF )
{ {
case 0xC0: case 0xC0:
case 0xC1: case 0xC1:
if ( msx.audio ) if ( msx.audio )
return msx.audio->read( time, addr & 1 ); return msx.audio->read( time, addr & 1 );
break; break;
case 0xA2: case 0xA2:
if ( msx.psg ) if ( msx.psg )
return msx.psg->read(); return msx.psg->read();
break; break;
#ifndef NDEBUG #ifndef NDEBUG
case 0xA8: // PPI case 0xA8: // PPI
return 0; return 0;
#endif #endif
} }
return Kss_Core::cpu_in( time, addr ); return Kss_Core::cpu_in( time, addr );
} }
void Kss_Emu::Core::update_gain() void Kss_Emu::Core::update_gain()
{ {
if ( scc_accessed ) if ( scc_accessed )
{ {
dprintf( "SCC accessed\n" ); dprintf( "SCC accessed\n" );
update_gain_(); update_gain_();
} }
} }
blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int ) blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int )
{ {
RETURN_ERR( core.end_frame( duration ) ); RETURN_ERR( core.end_frame( duration ) );
#define ACTION( apu ) IF_PTR( core.apu )->end_frame( duration ) #define ACTION( apu ) IF_PTR( core.apu )->end_frame( duration )
FOR_EACH_APU( ACTION ); FOR_EACH_APU( ACTION );
#undef ACTION #undef ACTION
return blargg_ok; return blargg_ok;
} }
blargg_err_t Kss_Emu::hash_( Hash_Function& out ) const blargg_err_t Kss_Emu::hash_( Hash_Function& out ) const
{ {
hash_kss_file( header(), core.rom_().begin(), core.rom_().file_size(), out ); hash_kss_file( header(), core.rom_().begin(), core.rom_().file_size(), out );
return blargg_ok; return blargg_ok;
} }

View file

@ -1,79 +1,79 @@
// MSX computer KSS music file emulator // MSX computer KSS music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef KSS_EMU_H #ifndef KSS_EMU_H
#define KSS_EMU_H #define KSS_EMU_H
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Kss_Core.h" #include "Kss_Core.h"
#include "Kss_Scc_Apu.h" #include "Kss_Scc_Apu.h"
#include "Sms_Apu.h" #include "Sms_Apu.h"
#include "Ay_Apu.h" #include "Ay_Apu.h"
#include "Opl_Apu.h" #include "Opl_Apu.h"
class Kss_Emu : public Classic_Emu { class Kss_Emu : public Classic_Emu {
public: public:
// KSS file header (see Kss_Core.h) // KSS file header (see Kss_Core.h)
typedef Kss_Core::header_t header_t; typedef Kss_Core::header_t header_t;
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return core.header(); } header_t const& header() const { return core.header(); }
blargg_err_t hash_( Hash_Function& ) const; blargg_err_t hash_( Hash_Function& ) const;
static gme_type_t static_type() { return gme_kss_type; } static gme_type_t static_type() { return gme_kss_type; }
// Implementation // Implementation
public: public:
Kss_Emu(); Kss_Emu();
~Kss_Emu(); ~Kss_Emu();
protected: protected:
virtual blargg_err_t track_info_( track_info_t*, int track ) const; virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int ); virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int ); virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double ); virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& ); virtual void update_eq( blip_eq_t const& );
virtual void unload(); virtual void unload();
private: private:
struct Core; struct Core;
friend struct Core; friend struct Core;
struct Core : Kss_Core { struct Core : Kss_Core {
Kss_Emu& emu; Kss_Emu& emu;
// detection of tunes that use SCC so they can be made louder // detection of tunes that use SCC so they can be made louder
bool scc_accessed; bool scc_accessed;
enum { scc_enabled_true = 0xC000 }; enum { scc_enabled_true = 0xC000 };
unsigned scc_enabled; // 0 or 0xC000 unsigned scc_enabled; // 0 or 0xC000
int ay_latch; int ay_latch;
struct { struct {
Sms_Apu* psg; Sms_Apu* psg;
Opl_Apu* fm; Opl_Apu* fm;
} sms; } sms;
struct { struct {
Ay_Apu* psg; Ay_Apu* psg;
Scc_Apu* scc; Scc_Apu* scc;
Opl_Apu* music; Opl_Apu* music;
Opl_Apu* audio; Opl_Apu* audio;
} msx; } msx;
Core( Kss_Emu* e ) : emu( *e ) { } Core( Kss_Emu* e ) : emu( *e ) { }
virtual void cpu_write( addr_t, int ); virtual void cpu_write( addr_t, int );
virtual int cpu_in( time_t, addr_t ); virtual int cpu_in( time_t, addr_t );
virtual void cpu_out( time_t, addr_t, int ); virtual void cpu_out( time_t, addr_t, int );
virtual void update_gain(); virtual void update_gain();
void cpu_write_( addr_t addr, int data ); void cpu_write_( addr_t addr, int data );
void update_gain_(); void update_gain_();
void unload(); void unload();
} core; } core;
}; };
#endif #endif

View file

@ -1,124 +1,124 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Kss_Scc_Apu.h" #include "Kss_Scc_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
// Tones above this frequency are treated as disabled tone at half volume. // Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division). // Power of two is more efficient (avoids division).
int const inaudible_freq = 16384; int const inaudible_freq = 16384;
int const wave_size = 0x20; int const wave_size = 0x20;
void Scc_Apu::set_output( Blip_Buffer* buf ) void Scc_Apu::set_output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
set_output( i, buf ); set_output( i, buf );
} }
void Scc_Apu::volume( double v ) void Scc_Apu::volume( double v )
{ {
synth.volume( 0.43 / osc_count / amp_range * v ); synth.volume( 0.43 / osc_count / amp_range * v );
} }
void Scc_Apu::reset() void Scc_Apu::reset()
{ {
last_time = 0; last_time = 0;
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
memset( &oscs [i], 0, offsetof (osc_t,output) ); memset( &oscs [i], 0, offsetof (osc_t,output) );
memset( regs, 0, sizeof regs ); memset( regs, 0, sizeof regs );
} }
Scc_Apu::Scc_Apu() Scc_Apu::Scc_Apu()
{ {
set_output( NULL ); set_output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
void Scc_Apu::run_until( blip_time_t end_time ) void Scc_Apu::run_until( blip_time_t end_time )
{ {
for ( int index = 0; index < osc_count; index++ ) for ( int index = 0; index < osc_count; index++ )
{ {
osc_t& osc = oscs [index]; osc_t& osc = oscs [index];
Blip_Buffer* const output = osc.output; Blip_Buffer* const output = osc.output;
if ( !output ) if ( !output )
continue; continue;
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 + blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 +
regs [0xA0 + index * 2] + 1; regs [0xA0 + index * 2] + 1;
int volume = 0; int volume = 0;
if ( regs [0xAF] & (1 << index) ) if ( regs [0xAF] & (1 << index) )
{ {
blip_time_t inaudible_period = (unsigned) (output->clock_rate() + blip_time_t inaudible_period = (unsigned) (output->clock_rate() +
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16); inaudible_freq * 32) / (unsigned) (inaudible_freq * 16);
if ( period > inaudible_period ) if ( period > inaudible_period )
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15); volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15);
} }
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size; BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size;
/*if ( index == osc_count - 1 ) /*if ( index == osc_count - 1 )
wave -= wave_size; // last two oscs share same wave RAM*/ wave -= wave_size; // last two oscs share same wave RAM*/
{ {
int delta = wave [osc.phase] * volume - osc.last_amp; int delta = wave [osc.phase] * volume - osc.last_amp;
if ( delta ) if ( delta )
{ {
osc.last_amp += delta; osc.last_amp += delta;
output->set_modified(); output->set_modified();
synth.offset( last_time, delta, output ); synth.offset( last_time, delta, output );
} }
} }
blip_time_t time = last_time + osc.delay; blip_time_t time = last_time + osc.delay;
if ( time < end_time ) if ( time < end_time )
{ {
int phase = osc.phase; int phase = osc.phase;
if ( !volume ) if ( !volume )
{ {
// maintain phase // maintain phase
int count = (end_time - time + period - 1) / period; int count = (end_time - time + period - 1) / period;
phase += count; // will be masked below phase += count; // will be masked below
time += count * period; time += count * period;
} }
else else
{ {
int last_wave = wave [phase]; int last_wave = wave [phase];
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
do do
{ {
int delta = wave [phase] - last_wave; int delta = wave [phase] - last_wave;
phase = (phase + 1) & (wave_size - 1); phase = (phase + 1) & (wave_size - 1);
if ( delta ) if ( delta )
{ {
last_wave += delta; last_wave += delta;
synth.offset_inline( time, delta * volume, output ); synth.offset_inline( time, delta * volume, output );
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
osc.last_amp = last_wave * volume; osc.last_amp = last_wave * volume;
output->set_modified(); output->set_modified();
phase--; // undo pre-advance phase--; // undo pre-advance
} }
osc.phase = phase & (wave_size - 1); osc.phase = phase & (wave_size - 1);
} }
osc.delay = time - end_time; osc.delay = time - end_time;
} }
last_time = end_time; last_time = end_time;
} }

View file

@ -1,111 +1,111 @@
// Konami SCC sound chip emulator // Konami SCC sound chip emulator
// $package // $package
#ifndef KSS_SCC_APU_H #ifndef KSS_SCC_APU_H
#define KSS_SCC_APU_H #define KSS_SCC_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Scc_Apu { class Scc_Apu {
public: public:
// Basics // Basics
// Sets buffer to generate sound into, or 0 to mute. // Sets buffer to generate sound into, or 0 to mute.
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
// Emulates to time t, then writes data to reg // Emulates to time t, then writes data to reg
enum { reg_count = 0xB0 }; // 0 <= reg < reg_count enum { reg_count = 0xB0 }; // 0 <= reg < reg_count
void write( blip_time_t t, int reg, int data ); void write( blip_time_t t, int reg, int data );
// Emulates to time t, then subtracts t from the current time. // Emulates to time t, then subtracts t from the current time.
// OK if previous write call had time slightly after t. // OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); void end_frame( blip_time_t t );
// More features // More features
// Resets sound chip // Resets sound chip
void reset(); void reset();
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
enum { osc_count = 5 }; enum { osc_count = 5 };
void set_output( int chan, Blip_Buffer* ); void set_output( int chan, Blip_Buffer* );
// Set overall volume, where 1.0 is normal // Set overall volume, where 1.0 is normal
void volume( double ); void volume( double );
// Set treble equalization // Set treble equalization
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
private: private:
// noncopyable // noncopyable
Scc_Apu( const Scc_Apu& ); Scc_Apu( const Scc_Apu& );
Scc_Apu& operator = ( const Scc_Apu& ); Scc_Apu& operator = ( const Scc_Apu& );
// Implementation // Implementation
public: public:
Scc_Apu(); Scc_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
private: private:
enum { amp_range = 0x8000 }; enum { amp_range = 0x8000 };
struct osc_t struct osc_t
{ {
int delay; int delay;
int phase; int phase;
int last_amp; int last_amp;
Blip_Buffer* output; Blip_Buffer* output;
}; };
osc_t oscs [osc_count]; osc_t oscs [osc_count];
blip_time_t last_time; blip_time_t last_time;
unsigned char regs [reg_count]; unsigned char regs [reg_count];
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
void run_until( blip_time_t ); void run_until( blip_time_t );
}; };
inline void Scc_Apu::set_output( int index, Blip_Buffer* b ) inline void Scc_Apu::set_output( int index, Blip_Buffer* b )
{ {
assert( (unsigned) index < osc_count ); assert( (unsigned) index < osc_count );
oscs [index].output = b; oscs [index].output = b;
} }
inline void Scc_Apu::write( blip_time_t time, int addr, int data ) inline void Scc_Apu::write( blip_time_t time, int addr, int data )
{ {
//assert( (unsigned) addr < reg_count ); //assert( (unsigned) addr < reg_count );
assert( ( addr >= 0x9800 && addr <= 0x988F ) || ( addr >= 0xB800 && addr <= 0xB8AF ) ); assert( ( addr >= 0x9800 && addr <= 0x988F ) || ( addr >= 0xB800 && addr <= 0xB8AF ) );
run_until( time ); run_until( time );
addr -= 0x9800; addr -= 0x9800;
if ( ( unsigned ) addr < 0x90 ) if ( ( unsigned ) addr < 0x90 )
{ {
if ( ( unsigned ) addr < 0x60 ) if ( ( unsigned ) addr < 0x60 )
regs [addr] = data; regs [addr] = data;
else if ( ( unsigned ) addr < 0x80 ) else if ( ( unsigned ) addr < 0x80 )
{ {
regs [addr] = regs[addr + 0x20] = data; regs [addr] = regs[addr + 0x20] = data;
} }
else if ( ( unsigned ) addr < 0x90 ) else if ( ( unsigned ) addr < 0x90 )
{ {
regs [addr + 0x20] = data; regs [addr + 0x20] = data;
} }
} }
else else
{ {
addr -= 0xB800 - 0x9800; addr -= 0xB800 - 0x9800;
if ( ( unsigned ) addr < 0xB0 ) if ( ( unsigned ) addr < 0xB0 )
regs [addr] = data; regs [addr] = data;
} }
} }
inline void Scc_Apu::end_frame( blip_time_t end_time ) inline void Scc_Apu::end_frame( blip_time_t end_time )
{ {
if ( end_time > last_time ) if ( end_time > last_time )
run_until( end_time ); run_until( end_time );
last_time -= end_time; last_time -= end_time;
assert( last_time >= 0 ); assert( last_time >= 0 );
} }
#endif #endif

View file

@ -1,219 +1,219 @@
// Multi-channel sound buffer interface, and basic mono and stereo buffers // Multi-channel sound buffer interface, and basic mono and stereo buffers
// Blip_Buffer $vers // Blip_Buffer $vers
#ifndef MULTI_BUFFER_H #ifndef MULTI_BUFFER_H
#define MULTI_BUFFER_H #define MULTI_BUFFER_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
// Interface to one or more Blip_Buffers mapped to one or more channels // Interface to one or more Blip_Buffers mapped to one or more channels
// consisting of left, center, and right buffers. // consisting of left, center, and right buffers.
class Multi_Buffer { class Multi_Buffer {
public: public:
// 1=mono, 2=stereo // 1=mono, 2=stereo
Multi_Buffer( int samples_per_frame ); Multi_Buffer( int samples_per_frame );
virtual ~Multi_Buffer() { } virtual ~Multi_Buffer() { }
// Sets the number of channels available and optionally their types // Sets the number of channels available and optionally their types
// (type information used by Effects_Buffer) // (type information used by Effects_Buffer)
enum { type_index_mask = 0xFF }; enum { type_index_mask = 0xFF };
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
virtual blargg_err_t set_channel_count( int, int const types [] = NULL ); virtual blargg_err_t set_channel_count( int, int const types [] = NULL );
int channel_count() const { return channel_count_; } int channel_count() const { return channel_count_; }
// Gets indexed channel, from 0 to channel_count()-1 // Gets indexed channel, from 0 to channel_count()-1
struct channel_t { struct channel_t {
Blip_Buffer* center; Blip_Buffer* center;
Blip_Buffer* left; Blip_Buffer* left;
Blip_Buffer* right; Blip_Buffer* right;
}; };
virtual channel_t channel( int index ) BLARGG_PURE( ; ) virtual channel_t channel( int index ) BLARGG_PURE( ; )
// Number of samples per output frame (1 = mono, 2 = stereo) // Number of samples per output frame (1 = mono, 2 = stereo)
int samples_per_frame() const; int samples_per_frame() const;
// Count of changes to channel configuration. Incremented whenever // Count of changes to channel configuration. Incremented whenever
// a change is made to any of the Blip_Buffers for any channel. // a change is made to any of the Blip_Buffers for any channel.
unsigned channels_changed_count() { return channels_changed_count_; } unsigned channels_changed_count() { return channels_changed_count_; }
// See Blip_Buffer.h // See Blip_Buffer.h
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ) BLARGG_PURE( ; ) virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ) BLARGG_PURE( ; )
int sample_rate() const; int sample_rate() const;
int length() const; int length() const;
virtual void clock_rate( int ) BLARGG_PURE( ; ) virtual void clock_rate( int ) BLARGG_PURE( ; )
virtual void bass_freq( int ) BLARGG_PURE( ; ) virtual void bass_freq( int ) BLARGG_PURE( ; )
virtual void clear() BLARGG_PURE( ; ) virtual void clear() BLARGG_PURE( ; )
virtual void end_frame( blip_time_t ) BLARGG_PURE( ; ) virtual void end_frame( blip_time_t ) BLARGG_PURE( ; )
virtual int read_samples( blip_sample_t [], int ) BLARGG_PURE( ; ) virtual int read_samples( blip_sample_t [], int ) BLARGG_PURE( ; )
virtual int samples_avail() const BLARGG_PURE( ; ) virtual int samples_avail() const BLARGG_PURE( ; )
private: private:
// noncopyable // noncopyable
Multi_Buffer( const Multi_Buffer& ); Multi_Buffer( const Multi_Buffer& );
Multi_Buffer& operator = ( const Multi_Buffer& ); Multi_Buffer& operator = ( const Multi_Buffer& );
// Implementation // Implementation
public: public:
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
void disable_immediate_removal() { immediate_removal_ = false; } void disable_immediate_removal() { immediate_removal_ = false; }
protected: protected:
bool immediate_removal() const { return immediate_removal_; } bool immediate_removal() const { return immediate_removal_; }
int const* channel_types() const { return channel_types_; } int const* channel_types() const { return channel_types_; }
void channels_changed() { channels_changed_count_++; } void channels_changed() { channels_changed_count_++; }
private: private:
unsigned channels_changed_count_; unsigned channels_changed_count_;
int sample_rate_; int sample_rate_;
int length_; int length_;
int channel_count_; int channel_count_;
int const samples_per_frame_; int const samples_per_frame_;
int const* channel_types_; int const* channel_types_;
bool immediate_removal_; bool immediate_removal_;
}; };
// Uses a single buffer and outputs mono samples. // Uses a single buffer and outputs mono samples.
class Mono_Buffer : public Multi_Buffer { class Mono_Buffer : public Multi_Buffer {
public: public:
// Buffer used for all channels // Buffer used for all channels
Blip_Buffer* center() { return &buf; } Blip_Buffer* center() { return &buf; }
// Implementation // Implementation
public: public:
Mono_Buffer(); Mono_Buffer();
~Mono_Buffer(); ~Mono_Buffer();
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ); virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length );
virtual void clock_rate( int rate ) { buf.clock_rate( rate ); } virtual void clock_rate( int rate ) { buf.clock_rate( rate ); }
virtual void bass_freq( int freq ) { buf.bass_freq( freq ); } virtual void bass_freq( int freq ) { buf.bass_freq( freq ); }
virtual void clear() { buf.clear(); } virtual void clear() { buf.clear(); }
virtual int samples_avail() const { return buf.samples_avail(); } virtual int samples_avail() const { return buf.samples_avail(); }
virtual int read_samples( blip_sample_t p [], int s ) { return buf.read_samples( p, s ); } virtual int read_samples( blip_sample_t p [], int s ) { return buf.read_samples( p, s ); }
virtual channel_t channel( int ) { return chan; } virtual channel_t channel( int ) { return chan; }
virtual void end_frame( blip_time_t t ) { buf.end_frame( t ); } virtual void end_frame( blip_time_t t ) { buf.end_frame( t ); }
private: private:
Blip_Buffer buf; Blip_Buffer buf;
channel_t chan; channel_t chan;
}; };
class Tracked_Blip_Buffer : public Blip_Buffer { class Tracked_Blip_Buffer : public Blip_Buffer {
public: public:
// Non-zero if buffer still has non-silent samples in it. Requires that you call // Non-zero if buffer still has non-silent samples in it. Requires that you call
// set_modified() appropriately. // set_modified() appropriately.
unsigned non_silent() const; unsigned non_silent() const;
// remove_samples( samples_avail() ) // remove_samples( samples_avail() )
void remove_all_samples(); void remove_all_samples();
// Implementation // Implementation
public: public:
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
int read_samples( blip_sample_t [], int ); int read_samples( blip_sample_t [], int );
void remove_silence( int ); void remove_silence( int );
void remove_samples( int ); void remove_samples( int );
Tracked_Blip_Buffer(); Tracked_Blip_Buffer();
void clear(); void clear();
void end_frame( blip_time_t ); void end_frame( blip_time_t );
private: private:
int last_non_silence; int last_non_silence;
delta_t unsettled() const { return integrator() >> delta_bits; } delta_t unsettled() const { return integrator() >> delta_bits; }
void remove_( int ); void remove_( int );
}; };
class Stereo_Mixer { class Stereo_Mixer {
public: public:
Tracked_Blip_Buffer* bufs [3]; Tracked_Blip_Buffer* bufs [3];
int samples_read; int samples_read;
Stereo_Mixer() : samples_read( 0 ) { } Stereo_Mixer() : samples_read( 0 ) { }
void read_pairs( blip_sample_t out [], int count ); void read_pairs( blip_sample_t out [], int count );
private: private:
void mix_mono ( blip_sample_t out [], int pair_count ); void mix_mono ( blip_sample_t out [], int pair_count );
void mix_stereo( blip_sample_t out [], int pair_count ); void mix_stereo( blip_sample_t out [], int pair_count );
}; };
// Uses three buffers (one for center) and outputs stereo sample pairs. // Uses three buffers (one for center) and outputs stereo sample pairs.
class Stereo_Buffer : public Multi_Buffer { class Stereo_Buffer : public Multi_Buffer {
public: public:
// Buffers used for all channels // Buffers used for all channels
Blip_Buffer* center() { return &bufs [2]; } Blip_Buffer* center() { return &bufs [2]; }
Blip_Buffer* left() { return &bufs [0]; } Blip_Buffer* left() { return &bufs [0]; }
Blip_Buffer* right() { return &bufs [1]; } Blip_Buffer* right() { return &bufs [1]; }
// Implementation // Implementation
public: public:
Stereo_Buffer(); Stereo_Buffer();
~Stereo_Buffer(); ~Stereo_Buffer();
virtual blargg_err_t set_sample_rate( int, int msec = blip_default_length ); virtual blargg_err_t set_sample_rate( int, int msec = blip_default_length );
virtual void clock_rate( int ); virtual void clock_rate( int );
virtual void bass_freq( int ); virtual void bass_freq( int );
virtual void clear(); virtual void clear();
virtual channel_t channel( int ) { return chan; } virtual channel_t channel( int ) { return chan; }
virtual void end_frame( blip_time_t ); virtual void end_frame( blip_time_t );
virtual int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; } virtual int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
virtual int read_samples( blip_sample_t [], int ); virtual int read_samples( blip_sample_t [], int );
private: private:
enum { bufs_size = 3 }; enum { bufs_size = 3 };
typedef Tracked_Blip_Buffer buf_t; typedef Tracked_Blip_Buffer buf_t;
buf_t bufs [bufs_size]; buf_t bufs [bufs_size];
Stereo_Mixer mixer; Stereo_Mixer mixer;
channel_t chan; channel_t chan;
int samples_avail_; int samples_avail_;
}; };
// Silent_Buffer generates no samples, useful where no sound is wanted // Silent_Buffer generates no samples, useful where no sound is wanted
class Silent_Buffer : public Multi_Buffer { class Silent_Buffer : public Multi_Buffer {
channel_t chan; channel_t chan;
public: public:
Silent_Buffer(); Silent_Buffer();
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ); virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length );
virtual void clock_rate( int ) { } virtual void clock_rate( int ) { }
virtual void bass_freq( int ) { } virtual void bass_freq( int ) { }
virtual void clear() { } virtual void clear() { }
virtual channel_t channel( int ) { return chan; } virtual channel_t channel( int ) { return chan; }
virtual void end_frame( blip_time_t ) { } virtual void end_frame( blip_time_t ) { }
virtual int samples_avail() const { return 0; } virtual int samples_avail() const { return 0; }
virtual int read_samples( blip_sample_t [], int ) { return 0; } virtual int read_samples( blip_sample_t [], int ) { return 0; }
}; };
inline blargg_err_t Multi_Buffer::set_sample_rate( int rate, int msec ) inline blargg_err_t Multi_Buffer::set_sample_rate( int rate, int msec )
{ {
sample_rate_ = rate; sample_rate_ = rate;
length_ = msec; length_ = msec;
return blargg_ok; return blargg_ok;
} }
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
inline int Multi_Buffer::sample_rate() const { return sample_rate_; } inline int Multi_Buffer::sample_rate() const { return sample_rate_; }
inline int Multi_Buffer::length() const { return length_; } inline int Multi_Buffer::length() const { return length_; }
inline void Multi_Buffer::clock_rate( int ) { } inline void Multi_Buffer::clock_rate( int ) { }
inline void Multi_Buffer::bass_freq( int ) { } inline void Multi_Buffer::bass_freq( int ) { }
inline void Multi_Buffer::clear() { } inline void Multi_Buffer::clear() { }
inline void Multi_Buffer::end_frame( blip_time_t ) { } inline void Multi_Buffer::end_frame( blip_time_t ) { }
inline int Multi_Buffer::read_samples( blip_sample_t [], int ) { return 0; } inline int Multi_Buffer::read_samples( blip_sample_t [], int ) { return 0; }
inline int Multi_Buffer::samples_avail() const { return 0; } inline int Multi_Buffer::samples_avail() const { return 0; }
inline blargg_err_t Multi_Buffer::set_channel_count( int n, int const types [] ) inline blargg_err_t Multi_Buffer::set_channel_count( int n, int const types [] )
{ {
channel_count_ = n; channel_count_ = n;
channel_types_ = types; channel_types_ = types;
return blargg_ok; return blargg_ok;
} }
inline blargg_err_t Silent_Buffer::set_sample_rate( int rate, int msec ) inline blargg_err_t Silent_Buffer::set_sample_rate( int rate, int msec )
{ {
return Multi_Buffer::set_sample_rate( rate, msec ); return Multi_Buffer::set_sample_rate( rate, msec );
} }
#endif #endif

View file

@ -220,7 +220,7 @@ void Music_Emu::set_fade( int start_msec, int length_msec )
fade_set = true; fade_set = true;
this->length_msec = start_msec; this->length_msec = start_msec;
this->fade_msec = length_msec; this->fade_msec = length_msec;
track_filter.set_fade( msec_to_samples( start_msec ), track_filter.set_fade( start_msec < 0 ? Track_Filter::indefinite_count : msec_to_samples( start_msec ),
length_msec * sample_rate() / (1000 / stereo) ); length_msec * sample_rate() / (1000 / stereo) );
} }

View file

@ -1,394 +1,394 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Apu.h" #include "Nes_Apu.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const amp_range = 15; int const amp_range = 15;
Nes_Apu::Nes_Apu() : Nes_Apu::Nes_Apu() :
square1( &square_synth ), square1( &square_synth ),
square2( &square_synth ) square2( &square_synth )
{ {
tempo_ = 1.0; tempo_ = 1.0;
dmc.apu = this; dmc.apu = this;
oscs [0] = &square1; oscs [0] = &square1;
oscs [1] = &square2; oscs [1] = &square2;
oscs [2] = &triangle; oscs [2] = &triangle;
oscs [3] = &noise; oscs [3] = &noise;
oscs [4] = &dmc; oscs [4] = &dmc;
set_output( NULL ); set_output( NULL );
dmc.nonlinear = false; dmc.nonlinear = false;
volume( 1.0 ); volume( 1.0 );
reset( false ); reset( false );
} }
void Nes_Apu::treble_eq( const blip_eq_t& eq ) void Nes_Apu::treble_eq( const blip_eq_t& eq )
{ {
square_synth .treble_eq( eq ); square_synth .treble_eq( eq );
triangle.synth.treble_eq( eq ); triangle.synth.treble_eq( eq );
noise .synth.treble_eq( eq ); noise .synth.treble_eq( eq );
dmc .synth.treble_eq( eq ); dmc .synth.treble_eq( eq );
} }
void Nes_Apu::enable_nonlinear_( double sq, double tnd ) void Nes_Apu::enable_nonlinear_( double sq, double tnd )
{ {
dmc.nonlinear = true; dmc.nonlinear = true;
square_synth.volume( sq ); square_synth.volume( sq );
triangle.synth.volume( tnd * 2.752 ); triangle.synth.volume( tnd * 2.752 );
noise .synth.volume( tnd * 1.849 ); noise .synth.volume( tnd * 1.849 );
dmc .synth.volume( tnd ); dmc .synth.volume( tnd );
square1 .last_amp = 0; square1 .last_amp = 0;
square2 .last_amp = 0; square2 .last_amp = 0;
triangle.last_amp = 0; triangle.last_amp = 0;
noise .last_amp = 0; noise .last_amp = 0;
dmc .last_amp = 0; dmc .last_amp = 0;
} }
void Nes_Apu::volume( double v ) void Nes_Apu::volume( double v )
{ {
if ( !dmc.nonlinear ) if ( !dmc.nonlinear )
{ {
v *= 1.0 / 1.11; // TODO: merge into values below v *= 1.0 / 1.11; // TODO: merge into values below
square_synth .volume( 0.125 / amp_range * v ); // was 0.1128 1.108 square_synth .volume( 0.125 / amp_range * v ); // was 0.1128 1.108
triangle.synth.volume( 0.150 / amp_range * v ); // was 0.12765 1.175 triangle.synth.volume( 0.150 / amp_range * v ); // was 0.12765 1.175
noise .synth.volume( 0.095 / amp_range * v ); // was 0.0741 1.282 noise .synth.volume( 0.095 / amp_range * v ); // was 0.0741 1.282
dmc .synth.volume( 0.450 / 2048 * v ); // was 0.42545 1.058 dmc .synth.volume( 0.450 / 2048 * v ); // was 0.42545 1.058
} }
} }
void Nes_Apu::set_output( Blip_Buffer* buffer ) void Nes_Apu::set_output( Blip_Buffer* buffer )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
set_output( i, buffer ); set_output( i, buffer );
} }
void Nes_Apu::set_tempo( double t ) void Nes_Apu::set_tempo( double t )
{ {
tempo_ = t; tempo_ = t;
frame_period = (dmc.pal_mode ? 8314 : 7458); frame_period = (dmc.pal_mode ? 8314 : 7458);
if ( t != 1.0 ) if ( t != 1.0 )
frame_period = (int) (frame_period / t) & ~1; // must be even frame_period = (int) (frame_period / t) & ~1; // must be even
} }
void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
{ {
dmc.pal_mode = pal_mode; dmc.pal_mode = pal_mode;
set_tempo( tempo_ ); set_tempo( tempo_ );
square1.reset(); square1.reset();
square2.reset(); square2.reset();
triangle.reset(); triangle.reset();
noise.reset(); noise.reset();
dmc.reset(); dmc.reset();
last_time = 0; last_time = 0;
last_dmc_time = 0; last_dmc_time = 0;
osc_enables = 0; osc_enables = 0;
irq_flag = false; irq_flag = false;
enable_w4011 = true; enable_w4011 = true;
earliest_irq_ = no_irq; earliest_irq_ = no_irq;
frame_delay = 1; frame_delay = 1;
write_register( 0, 0x4017, 0x00 ); write_register( 0, 0x4017, 0x00 );
write_register( 0, 0x4015, 0x00 ); write_register( 0, 0x4015, 0x00 );
for ( int addr = io_addr; addr <= 0x4013; addr++ ) for ( int addr = io_addr; addr <= 0x4013; addr++ )
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
dmc.dac = initial_dmc_dac; dmc.dac = initial_dmc_dac;
if ( !dmc.nonlinear ) if ( !dmc.nonlinear )
triangle.last_amp = 15; triangle.last_amp = 15;
if ( !dmc.nonlinear ) // TODO: remove? if ( !dmc.nonlinear ) // TODO: remove?
dmc.last_amp = initial_dmc_dac; // prevent output transition dmc.last_amp = initial_dmc_dac; // prevent output transition
} }
void Nes_Apu::irq_changed() void Nes_Apu::irq_changed()
{ {
blip_time_t new_irq = dmc.next_irq; blip_time_t new_irq = dmc.next_irq;
if ( dmc.irq_flag | irq_flag ) { if ( dmc.irq_flag | irq_flag ) {
new_irq = 0; new_irq = 0;
} }
else if ( new_irq > next_irq ) { else if ( new_irq > next_irq ) {
new_irq = next_irq; new_irq = next_irq;
} }
if ( new_irq != earliest_irq_ ) { if ( new_irq != earliest_irq_ ) {
earliest_irq_ = new_irq; earliest_irq_ = new_irq;
if ( irq_notifier.f ) if ( irq_notifier.f )
irq_notifier.f( irq_notifier.data ); irq_notifier.f( irq_notifier.data );
} }
} }
// frames // frames
void Nes_Apu::run_until( blip_time_t end_time ) void Nes_Apu::run_until( blip_time_t end_time )
{ {
require( end_time >= last_dmc_time ); require( end_time >= last_dmc_time );
if ( end_time > next_dmc_read_time() ) if ( end_time > next_dmc_read_time() )
{ {
blip_time_t start = last_dmc_time; blip_time_t start = last_dmc_time;
last_dmc_time = end_time; last_dmc_time = end_time;
dmc.run( start, end_time ); dmc.run( start, end_time );
} }
} }
void Nes_Apu::run_until_( blip_time_t end_time ) void Nes_Apu::run_until_( blip_time_t end_time )
{ {
require( end_time >= last_time ); require( end_time >= last_time );
if ( end_time == last_time ) if ( end_time == last_time )
return; return;
if ( last_dmc_time < end_time ) if ( last_dmc_time < end_time )
{ {
blip_time_t start = last_dmc_time; blip_time_t start = last_dmc_time;
last_dmc_time = end_time; last_dmc_time = end_time;
dmc.run( start, end_time ); dmc.run( start, end_time );
} }
while ( true ) while ( true )
{ {
// earlier of next frame time or end time // earlier of next frame time or end time
blip_time_t time = last_time + frame_delay; blip_time_t time = last_time + frame_delay;
if ( time > end_time ) if ( time > end_time )
time = end_time; time = end_time;
frame_delay -= time - last_time; frame_delay -= time - last_time;
// run oscs to present // run oscs to present
square1.run( last_time, time ); square1.run( last_time, time );
square2.run( last_time, time ); square2.run( last_time, time );
triangle.run( last_time, time ); triangle.run( last_time, time );
noise.run( last_time, time ); noise.run( last_time, time );
last_time = time; last_time = time;
if ( time == end_time ) if ( time == end_time )
break; // no more frames to run break; // no more frames to run
// take frame-specific actions // take frame-specific actions
frame_delay = frame_period; frame_delay = frame_period;
switch ( frame++ ) switch ( frame++ )
{ {
case 0: case 0:
if ( !(frame_mode & 0xC0) ) { if ( !(frame_mode & 0xC0) ) {
next_irq = time + frame_period * 4 + 2; next_irq = time + frame_period * 4 + 2;
irq_flag = true; irq_flag = true;
} }
// fall through // fall through
case 2: case 2:
// clock length and sweep on frames 0 and 2 // clock length and sweep on frames 0 and 2
square1.clock_length( 0x20 ); square1.clock_length( 0x20 );
square2.clock_length( 0x20 ); square2.clock_length( 0x20 );
noise.clock_length( 0x20 ); noise.clock_length( 0x20 );
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
square1.clock_sweep( -1 ); square1.clock_sweep( -1 );
square2.clock_sweep( 0 ); square2.clock_sweep( 0 );
// frame 2 is slightly shorter in mode 1 // frame 2 is slightly shorter in mode 1
if ( dmc.pal_mode && frame == 3 ) if ( dmc.pal_mode && frame == 3 )
frame_delay -= 2; frame_delay -= 2;
break; break;
case 1: case 1:
// frame 1 is slightly shorter in mode 0 // frame 1 is slightly shorter in mode 0
if ( !dmc.pal_mode ) if ( !dmc.pal_mode )
frame_delay -= 2; frame_delay -= 2;
break; break;
case 3: case 3:
frame = 0; frame = 0;
// frame 3 is almost twice as long in mode 1 // frame 3 is almost twice as long in mode 1
if ( frame_mode & 0x80 ) if ( frame_mode & 0x80 )
frame_delay += frame_period - (dmc.pal_mode ? 2 : 6); frame_delay += frame_period - (dmc.pal_mode ? 2 : 6);
break; break;
} }
// clock envelopes and linear counter every frame // clock envelopes and linear counter every frame
triangle.clock_linear_counter(); triangle.clock_linear_counter();
square1.clock_envelope(); square1.clock_envelope();
square2.clock_envelope(); square2.clock_envelope();
noise.clock_envelope(); noise.clock_envelope();
} }
} }
template<class T> template<class T>
inline void zero_apu_osc( T* osc, blip_time_t time ) inline void zero_apu_osc( T* osc, blip_time_t time )
{ {
Blip_Buffer* output = osc->output; Blip_Buffer* output = osc->output;
int last_amp = osc->last_amp; int last_amp = osc->last_amp;
osc->last_amp = 0; osc->last_amp = 0;
if ( output && last_amp ) if ( output && last_amp )
osc->synth.offset( time, -last_amp, output ); osc->synth.offset( time, -last_amp, output );
} }
void Nes_Apu::end_frame( blip_time_t end_time ) void Nes_Apu::end_frame( blip_time_t end_time )
{ {
if ( end_time > last_time ) if ( end_time > last_time )
run_until_( end_time ); run_until_( end_time );
if ( dmc.nonlinear ) if ( dmc.nonlinear )
{ {
zero_apu_osc( &square1, last_time ); zero_apu_osc( &square1, last_time );
zero_apu_osc( &square2, last_time ); zero_apu_osc( &square2, last_time );
zero_apu_osc( &triangle, last_time ); zero_apu_osc( &triangle, last_time );
zero_apu_osc( &noise, last_time ); zero_apu_osc( &noise, last_time );
zero_apu_osc( &dmc, last_time ); zero_apu_osc( &dmc, last_time );
} }
// make times relative to new frame // make times relative to new frame
last_time -= end_time; last_time -= end_time;
require( last_time >= 0 ); require( last_time >= 0 );
last_dmc_time -= end_time; last_dmc_time -= end_time;
require( last_dmc_time >= 0 ); require( last_dmc_time >= 0 );
if ( next_irq != no_irq ) { if ( next_irq != no_irq ) {
next_irq -= end_time; next_irq -= end_time;
check( next_irq >= 0 ); check( next_irq >= 0 );
} }
if ( dmc.next_irq != no_irq ) { if ( dmc.next_irq != no_irq ) {
dmc.next_irq -= end_time; dmc.next_irq -= end_time;
check( dmc.next_irq >= 0 ); check( dmc.next_irq >= 0 );
} }
if ( earliest_irq_ != no_irq ) { if ( earliest_irq_ != no_irq ) {
earliest_irq_ -= end_time; earliest_irq_ -= end_time;
if ( earliest_irq_ < 0 ) if ( earliest_irq_ < 0 )
earliest_irq_ = 0; earliest_irq_ = 0;
} }
} }
// registers // registers
static const unsigned char length_table [0x20] = { static const unsigned char length_table [0x20] = {
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
}; };
void Nes_Apu::write_register( blip_time_t time, int addr, int data ) void Nes_Apu::write_register( blip_time_t time, int addr, int data )
{ {
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
require( (unsigned) data <= 0xFF ); require( (unsigned) data <= 0xFF );
// Ignore addresses outside range // Ignore addresses outside range
if ( unsigned (addr - io_addr) >= io_size ) if ( unsigned (addr - io_addr) >= io_size )
return; return;
run_until_( time ); run_until_( time );
if ( addr < 0x4014 ) if ( addr < 0x4014 )
{ {
// Write to channel // Write to channel
int osc_index = (addr - io_addr) >> 2; int osc_index = (addr - io_addr) >> 2;
Nes_Osc* osc = oscs [osc_index]; Nes_Osc* osc = oscs [osc_index];
int reg = addr & 3; int reg = addr & 3;
osc->regs [reg] = data; osc->regs [reg] = data;
osc->reg_written [reg] = true; osc->reg_written [reg] = true;
if ( osc_index == 4 ) if ( osc_index == 4 )
{ {
// handle DMC specially // handle DMC specially
if ( enable_w4011 || reg != 1 ) if ( enable_w4011 || reg != 1 )
dmc.write_register( reg, data ); dmc.write_register( reg, data );
} }
else if ( reg == 3 ) else if ( reg == 3 )
{ {
// load length counter // load length counter
if ( (osc_enables >> osc_index) & 1 ) if ( (osc_enables >> osc_index) & 1 )
osc->length_counter = length_table [(data >> 3) & 0x1F]; osc->length_counter = length_table [(data >> 3) & 0x1F];
// reset square phase // reset square phase
if ( osc_index < 2 ) if ( osc_index < 2 )
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
} }
} }
else if ( addr == 0x4015 ) else if ( addr == 0x4015 )
{ {
// Channel enables // Channel enables
for ( int i = osc_count; i--; ) for ( int i = osc_count; i--; )
if ( !((data >> i) & 1) ) if ( !((data >> i) & 1) )
oscs [i]->length_counter = 0; oscs [i]->length_counter = 0;
bool recalc_irq = dmc.irq_flag; bool recalc_irq = dmc.irq_flag;
dmc.irq_flag = false; dmc.irq_flag = false;
int old_enables = osc_enables; int old_enables = osc_enables;
osc_enables = data; osc_enables = data;
if ( !(data & 0x10) ) { if ( !(data & 0x10) ) {
dmc.next_irq = no_irq; dmc.next_irq = no_irq;
recalc_irq = true; recalc_irq = true;
} }
else if ( !(old_enables & 0x10) ) { else if ( !(old_enables & 0x10) ) {
dmc.start(); // dmc just enabled dmc.start(); // dmc just enabled
} }
if ( recalc_irq ) if ( recalc_irq )
irq_changed(); irq_changed();
} }
else if ( addr == 0x4017 ) else if ( addr == 0x4017 )
{ {
// Frame mode // Frame mode
frame_mode = data; frame_mode = data;
bool irq_enabled = !(data & 0x40); bool irq_enabled = !(data & 0x40);
irq_flag &= irq_enabled; irq_flag &= irq_enabled;
next_irq = no_irq; next_irq = no_irq;
// mode 1 // mode 1
frame_delay = (frame_delay & 1); frame_delay = (frame_delay & 1);
frame = 0; frame = 0;
if ( !(data & 0x80) ) if ( !(data & 0x80) )
{ {
// mode 0 // mode 0
frame = 1; frame = 1;
frame_delay += frame_period; frame_delay += frame_period;
if ( irq_enabled ) if ( irq_enabled )
next_irq = time + frame_delay + frame_period * 3 + 1; next_irq = time + frame_delay + frame_period * 3 + 1;
} }
irq_changed(); irq_changed();
} }
} }
int Nes_Apu::read_status( blip_time_t time ) int Nes_Apu::read_status( blip_time_t time )
{ {
run_until_( time - 1 ); run_until_( time - 1 );
int result = (dmc.irq_flag << 7) | (irq_flag << 6); int result = (dmc.irq_flag << 7) | (irq_flag << 6);
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
if ( oscs [i]->length_counter ) if ( oscs [i]->length_counter )
result |= 1 << i; result |= 1 << i;
run_until_( time ); run_until_( time );
if ( irq_flag ) if ( irq_flag )
{ {
result |= 0x40; result |= 0x40;
irq_flag = false; irq_flag = false;
irq_changed(); irq_changed();
} }
//dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result ); //dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
return result; return result;
} }

View file

@ -1,184 +1,184 @@
// NES 2A03 APU sound chip emulator // NES 2A03 APU sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu $vers
#ifndef NES_APU_H #ifndef NES_APU_H
#define NES_APU_H #define NES_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Nes_Oscs.h" #include "Nes_Oscs.h"
struct apu_state_t; struct apu_state_t;
class Nes_Buffer; class Nes_Buffer;
class Nes_Apu { class Nes_Apu {
public: public:
// Basics // Basics
typedef int nes_time_t; // NES CPU clock cycle count typedef int nes_time_t; // NES CPU clock cycle count
// Sets memory reader callback used by DMC oscillator to fetch samples. // Sets memory reader callback used by DMC oscillator to fetch samples.
// When callback is invoked, 'user_data' is passed unchanged as the // When callback is invoked, 'user_data' is passed unchanged as the
// first parameter. // first parameter.
//void dmc_reader( int (*callback)( void* user_data, int addr ), void* user_data = NULL ); //void dmc_reader( int (*callback)( void* user_data, int addr ), void* user_data = NULL );
// Sets buffer to generate sound into, or 0 to mute output (reduces // Sets buffer to generate sound into, or 0 to mute output (reduces
// emulation accuracy). // emulation accuracy).
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
// All time values are the number of CPU clock cycles relative to the // All time values are the number of CPU clock cycles relative to the
// beginning of the current time frame. Before resetting the CPU clock // beginning of the current time frame. Before resetting the CPU clock
// count, call end_frame( last_cpu_time ). // count, call end_frame( last_cpu_time ).
// Writes to register (0x4000-0x4013, and 0x4015 and 0x4017) // Writes to register (0x4000-0x4013, and 0x4015 and 0x4017)
enum { io_addr = 0x4000 }; enum { io_addr = 0x4000 };
enum { io_size = 0x18 }; enum { io_size = 0x18 };
void write_register( nes_time_t, int addr, int data ); void write_register( nes_time_t, int addr, int data );
// Reads from status register (0x4015) // Reads from status register (0x4015)
enum { status_addr = 0x4015 }; enum { status_addr = 0x4015 };
int read_status( nes_time_t ); int read_status( nes_time_t );
// Runs all oscillators up to specified time, ends current time frame, then // Runs all oscillators up to specified time, ends current time frame, then
// starts a new time frame at time 0. Time frames have no effect on emulation // starts a new time frame at time 0. Time frames have no effect on emulation
// and each can be whatever length is convenient. // and each can be whatever length is convenient.
void end_frame( nes_time_t ); void end_frame( nes_time_t );
// Optional // Optional
// Resets internal frame counter, registers, and all oscillators. // Resets internal frame counter, registers, and all oscillators.
// Uses PAL timing if pal_timing is true, otherwise use NTSC timing. // Uses PAL timing if pal_timing is true, otherwise use NTSC timing.
// Sets the DMC oscillator's initial DAC value to initial_dmc_dac without // Sets the DMC oscillator's initial DAC value to initial_dmc_dac without
// any audible click. // any audible click.
void reset( bool pal_mode = false, int initial_dmc_dac = 0 ); void reset( bool pal_mode = false, int initial_dmc_dac = 0 );
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
// 0: Square 1, 1: Square 2, 2: Triangle, 3: Noise, 4: DMC // 0: Square 1, 1: Square 2, 2: Triangle, 3: Noise, 4: DMC
enum { osc_count = 5 }; enum { osc_count = 5 };
void set_output( int chan, Blip_Buffer* buf ); void set_output( int chan, Blip_Buffer* buf );
// Adjusts frame period // Adjusts frame period
void set_tempo( double ); void set_tempo( double );
// Saves/loads exact emulation state // Saves/loads exact emulation state
void save_state( apu_state_t* out ) const; void save_state( apu_state_t* out ) const;
void load_state( apu_state_t const& ); void load_state( apu_state_t const& );
// Sets overall volume (default is 1.0) // Sets overall volume (default is 1.0)
void volume( double ); void volume( double );
// Sets treble equalization (see notes.txt) // Sets treble equalization (see notes.txt)
void treble_eq( const blip_eq_t& ); void treble_eq( const blip_eq_t& );
// Sets IRQ time callback that is invoked when the time of earliest IRQ // Sets IRQ time callback that is invoked when the time of earliest IRQ
// may have changed, or NULL to disable. When callback is invoked, // may have changed, or NULL to disable. When callback is invoked,
// 'user_data' is passed unchanged as the first parameter. // 'user_data' is passed unchanged as the first parameter.
//void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); //void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
// Gets time that APU-generated IRQ will occur if no further register reads // Gets time that APU-generated IRQ will occur if no further register reads
// or writes occur. If IRQ is already pending, returns irq_waiting. If no // or writes occur. If IRQ is already pending, returns irq_waiting. If no
// IRQ will occur, returns no_irq. // IRQ will occur, returns no_irq.
enum { no_irq = INT_MAX/2 + 1 }; enum { no_irq = INT_MAX/2 + 1 };
enum { irq_waiting = 0 }; enum { irq_waiting = 0 };
nes_time_t earliest_irq( nes_time_t ) const; nes_time_t earliest_irq( nes_time_t ) const;
// Counts number of DMC reads that would occur if 'run_until( t )' were executed. // Counts number of DMC reads that would occur if 'run_until( t )' were executed.
// If last_read is not NULL, set *last_read to the earliest time that // If last_read is not NULL, set *last_read to the earliest time that
// 'count_dmc_reads( time )' would result in the same result. // 'count_dmc_reads( time )' would result in the same result.
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
// Time when next DMC memory read will occur // Time when next DMC memory read will occur
nes_time_t next_dmc_read_time() const; nes_time_t next_dmc_read_time() const;
// Runs DMC until specified time, so that any DMC memory reads can be // Runs DMC until specified time, so that any DMC memory reads can be
// accounted for (i.e. inserting CPU wait states). // accounted for (i.e. inserting CPU wait states).
void run_until( nes_time_t ); void run_until( nes_time_t );
// Implementation // Implementation
public: public:
Nes_Apu(); Nes_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
// Use set_output() in place of these // Use set_output() in place of these
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); ) BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); )
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ); ) BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ); )
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4000 }; ) BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4000 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4017 }; ) BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4017 }; )
blargg_callback<int (*)( void* user_data, int addr )> dmc_reader; blargg_callback<int (*)( void* user_data, int addr )> dmc_reader;
blargg_callback<void (*)( void* user_data )> irq_notifier; blargg_callback<void (*)( void* user_data )> irq_notifier;
void enable_nonlinear_( double sq, double tnd ); void enable_nonlinear_( double sq, double tnd );
static float tnd_total_() { return 196.015f; } static float tnd_total_() { return 196.015f; }
void enable_w4011_( bool enable = true ) { enable_w4011 = enable; } void enable_w4011_( bool enable = true ) { enable_w4011 = enable; }
private: private:
friend struct Nes_Dmc; friend struct Nes_Dmc;
// noncopyable // noncopyable
Nes_Apu( const Nes_Apu& ); Nes_Apu( const Nes_Apu& );
Nes_Apu& operator = ( const Nes_Apu& ); Nes_Apu& operator = ( const Nes_Apu& );
Nes_Osc* oscs [osc_count]; Nes_Osc* oscs [osc_count];
Nes_Square square1; Nes_Square square1;
Nes_Square square2; Nes_Square square2;
Nes_Noise noise; Nes_Noise noise;
Nes_Triangle triangle; Nes_Triangle triangle;
Nes_Dmc dmc; Nes_Dmc dmc;
double tempo_; double tempo_;
nes_time_t last_time; // has been run until this time in current frame nes_time_t last_time; // has been run until this time in current frame
nes_time_t last_dmc_time; nes_time_t last_dmc_time;
nes_time_t earliest_irq_; nes_time_t earliest_irq_;
nes_time_t next_irq; nes_time_t next_irq;
int frame_period; int frame_period;
int frame_delay; // cycles until frame counter runs next int frame_delay; // cycles until frame counter runs next
int frame; // current frame (0-3) int frame; // current frame (0-3)
int osc_enables; int osc_enables;
int frame_mode; int frame_mode;
bool irq_flag; bool irq_flag;
bool enable_w4011; bool enable_w4011;
Nes_Square::Synth square_synth; // shared by squares Nes_Square::Synth square_synth; // shared by squares
void irq_changed(); void irq_changed();
void state_restored(); void state_restored();
void run_until_( nes_time_t ); void run_until_( nes_time_t );
// TODO: remove // TODO: remove
friend class Nes_Core; friend class Nes_Core;
}; };
inline void Nes_Apu::set_output( int osc, Blip_Buffer* buf ) inline void Nes_Apu::set_output( int osc, Blip_Buffer* buf )
{ {
assert( (unsigned) osc < osc_count ); assert( (unsigned) osc < osc_count );
oscs [osc]->output = buf; oscs [osc]->output = buf;
} }
inline Nes_Apu::nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const inline Nes_Apu::nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
{ {
return earliest_irq_; return earliest_irq_;
} }
inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const
{ {
return dmc.count_reads( time, last_read ); return dmc.count_reads( time, last_read );
} }
inline Nes_Apu::nes_time_t Nes_Dmc::next_read_time() const inline Nes_Apu::nes_time_t Nes_Dmc::next_read_time() const
{ {
if ( length_counter == 0 ) if ( length_counter == 0 )
return Nes_Apu::no_irq; // not reading return Nes_Apu::no_irq; // not reading
return apu->last_dmc_time + delay + (bits_remain - 1) * period; return apu->last_dmc_time + delay + (bits_remain - 1) * period;
} }
inline Nes_Apu::nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } inline Nes_Apu::nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
BLARGG_DEPRECATED( typedef int nes_time_t; ) // use your own typedef BLARGG_DEPRECATED( typedef int nes_time_t; ) // use your own typedef
BLARGG_DEPRECATED( typedef unsigned nes_addr_t; ) // use your own typedef BLARGG_DEPRECATED( typedef unsigned nes_addr_t; ) // use your own typedef
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::output ( Blip_Buffer* c ) { set_output( c ); } ) BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::output ( Blip_Buffer* c ) { set_output( c ); } )
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::osc_output( int i, Blip_Buffer* c ) { set_output( i, c ); } ) BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::osc_output( int i, Blip_Buffer* c ) { set_output( i, c ); } )
#endif #endif

View file

@ -1,62 +1,62 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Nes_Cpu.h" #include "Nes_Cpu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
inline void Nes_Cpu::set_code_page( int i, void const* p ) inline void Nes_Cpu::set_code_page( int i, void const* p )
{ {
byte const* p2 = STATIC_CAST(byte const*,p) - NES_CPU_OFFSET( i * page_size ); byte const* p2 = STATIC_CAST(byte const*,p) - NES_CPU_OFFSET( i * page_size );
cpu_state->code_map [i] = p2; cpu_state->code_map [i] = p2;
cpu_state_.code_map [i] = p2; cpu_state_.code_map [i] = p2;
} }
void Nes_Cpu::map_code( addr_t start, int size, void const* data, int mirror_size ) void Nes_Cpu::map_code( addr_t start, int size, void const* data, int mirror_size )
{ {
// address range must begin and end on page boundaries // address range must begin and end on page boundaries
require( start % page_size == 0 ); require( start % page_size == 0 );
require( size % page_size == 0 ); require( size % page_size == 0 );
require( start + size <= 0x10000 ); require( start + size <= 0x10000 );
require( mirror_size % page_size == 0 ); require( mirror_size % page_size == 0 );
for ( int offset = 0; offset < size; offset += page_size ) for ( int offset = 0; offset < size; offset += page_size )
set_code_page( NES_CPU_PAGE( start + offset ), set_code_page( NES_CPU_PAGE( start + offset ),
STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) ); STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) );
} }
void Nes_Cpu::reset( void const* unmapped_page ) void Nes_Cpu::reset( void const* unmapped_page )
{ {
check( cpu_state == &cpu_state_ ); check( cpu_state == &cpu_state_ );
cpu_state = &cpu_state_; cpu_state = &cpu_state_;
r.flags = irq_inhibit_mask; r.flags = irq_inhibit_mask;
r.sp = 0xFF; r.sp = 0xFF;
r.pc = 0; r.pc = 0;
r.a = 0; r.a = 0;
r.x = 0; r.x = 0;
r.y = 0; r.y = 0;
cpu_state_.time = 0; cpu_state_.time = 0;
cpu_state_.base = 0; cpu_state_.base = 0;
irq_time_ = future_time; irq_time_ = future_time;
end_time_ = future_time; end_time_ = future_time;
error_count_ = 0; error_count_ = 0;
set_code_page( page_count, unmapped_page ); set_code_page( page_count, unmapped_page );
map_code( 0, 0x10000, unmapped_page, page_size ); map_code( 0, 0x10000, unmapped_page, page_size );
blargg_verify_byte_order(); blargg_verify_byte_order();
} }

View file

@ -1,131 +1,131 @@
// NES CPU emulator // NES CPU emulator
// $package // $package
#ifndef NES_CPU_H #ifndef NES_CPU_H
#define NES_CPU_H #define NES_CPU_H
#include "blargg_common.h" #include "blargg_common.h"
class Nes_Cpu { class Nes_Cpu {
public: public:
typedef BOOST::uint8_t byte; typedef BOOST::uint8_t byte;
typedef int time_t; typedef int time_t;
typedef int addr_t; typedef int addr_t;
enum { future_time = INT_MAX/2 + 1 }; enum { future_time = INT_MAX/2 + 1 };
// Clears registers and maps all pages to unmapped_page // Clears registers and maps all pages to unmapped_page
void reset( void const* unmapped_page = NULL ); void reset( void const* unmapped_page = NULL );
// Maps code memory (memory accessed via the program counter). Start and size // Maps code memory (memory accessed via the program counter). Start and size
// must be multiple of page_size. If mirror_size is non-zero, the first // must be multiple of page_size. If mirror_size is non-zero, the first
// mirror_size bytes are repeated over the range. mirror_size must be a // mirror_size bytes are repeated over the range. mirror_size must be a
// multiple of page_size. // multiple of page_size.
enum { page_bits = 11 }; enum { page_bits = 11 };
enum { page_size = 1 << page_bits }; enum { page_size = 1 << page_bits };
void map_code( addr_t start, int size, void const* code, int mirror_size = 0 ); void map_code( addr_t start, int size, void const* code, int mirror_size = 0 );
// Accesses emulated memory as CPU does // Accesses emulated memory as CPU does
byte const* get_code( addr_t ) const; byte const* get_code( addr_t ) const;
// NES 6502 registers. NOT kept updated during emulation. // NES 6502 registers. NOT kept updated during emulation.
struct registers_t { struct registers_t {
BOOST::uint16_t pc; BOOST::uint16_t pc;
byte a; byte a;
byte x; byte x;
byte y; byte y;
byte flags; byte flags;
byte sp; byte sp;
}; };
registers_t r; registers_t r;
// Time of beginning of next instruction to be executed // Time of beginning of next instruction to be executed
time_t time() const { return cpu_state->time + cpu_state->base; } time_t time() const { return cpu_state->time + cpu_state->base; }
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; } void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; }
void adjust_time( int delta ) { cpu_state->time += delta; } void adjust_time( int delta ) { cpu_state->time += delta; }
// Clocks past end (negative if before) // Clocks past end (negative if before)
int time_past_end() const { return cpu_state->time; } int time_past_end() const { return cpu_state->time; }
// Time of next IRQ // Time of next IRQ
time_t irq_time() const { return irq_time_; } time_t irq_time() const { return irq_time_; }
void set_irq_time( time_t ); void set_irq_time( time_t );
// Emulation stops once time >= end_time // Emulation stops once time >= end_time
time_t end_time() const { return end_time_; } time_t end_time() const { return end_time_; }
void set_end_time( time_t ); void set_end_time( time_t );
// Number of unimplemented instructions encountered and skipped // Number of unimplemented instructions encountered and skipped
void clear_error_count() { error_count_ = 0; } void clear_error_count() { error_count_ = 0; }
unsigned error_count() const { return error_count_; } unsigned error_count() const { return error_count_; }
void count_error() { error_count_++; } void count_error() { error_count_++; }
// Unmapped page should be filled with this // Unmapped page should be filled with this
enum { halt_opcode = 0x22 }; enum { halt_opcode = 0x22 };
enum { irq_inhibit_mask = 0x04 }; enum { irq_inhibit_mask = 0x04 };
// Can read this many bytes past end of a page // Can read this many bytes past end of a page
enum { cpu_padding = 8 }; enum { cpu_padding = 8 };
private: private:
// noncopyable // noncopyable
Nes_Cpu( const Nes_Cpu& ); Nes_Cpu( const Nes_Cpu& );
Nes_Cpu& operator = ( const Nes_Cpu& ); Nes_Cpu& operator = ( const Nes_Cpu& );
// Implementation // Implementation
public: public:
Nes_Cpu() { cpu_state = &cpu_state_; } Nes_Cpu() { cpu_state = &cpu_state_; }
enum { page_count = 0x10000 >> page_bits }; enum { page_count = 0x10000 >> page_bits };
struct cpu_state_t { struct cpu_state_t {
byte const* code_map [page_count + 1]; byte const* code_map [page_count + 1];
time_t base; time_t base;
int time; int time;
}; };
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
cpu_state_t cpu_state_; cpu_state_t cpu_state_;
time_t irq_time_; time_t irq_time_;
time_t end_time_; time_t end_time_;
unsigned error_count_; unsigned error_count_;
private: private:
void set_code_page( int, void const* ); void set_code_page( int, void const* );
inline void update_end_time( time_t end, time_t irq ); inline void update_end_time( time_t end, time_t irq );
}; };
#define NES_CPU_PAGE( addr ) ((unsigned) (addr) >> Nes_Cpu::page_bits) #define NES_CPU_PAGE( addr ) ((unsigned) (addr) >> Nes_Cpu::page_bits)
#if BLARGG_NONPORTABLE #if BLARGG_NONPORTABLE
#define NES_CPU_OFFSET( addr ) (addr) #define NES_CPU_OFFSET( addr ) (addr)
#else #else
#define NES_CPU_OFFSET( addr ) ((addr) & (Nes_Cpu::page_size - 1)) #define NES_CPU_OFFSET( addr ) ((addr) & (Nes_Cpu::page_size - 1))
#endif #endif
inline BOOST::uint8_t const* Nes_Cpu::get_code( addr_t addr ) const inline BOOST::uint8_t const* Nes_Cpu::get_code( addr_t addr ) const
{ {
return cpu_state_.code_map [NES_CPU_PAGE( addr )] + NES_CPU_OFFSET( addr ); return cpu_state_.code_map [NES_CPU_PAGE( addr )] + NES_CPU_OFFSET( addr );
} }
inline void Nes_Cpu::update_end_time( time_t end, time_t irq ) inline void Nes_Cpu::update_end_time( time_t end, time_t irq )
{ {
if ( end > irq && !(r.flags & irq_inhibit_mask) ) if ( end > irq && !(r.flags & irq_inhibit_mask) )
end = irq; end = irq;
cpu_state->time += cpu_state->base - end; cpu_state->time += cpu_state->base - end;
cpu_state->base = end; cpu_state->base = end;
} }
inline void Nes_Cpu::set_irq_time( time_t t ) inline void Nes_Cpu::set_irq_time( time_t t )
{ {
irq_time_ = t; irq_time_ = t;
update_end_time( end_time_, t ); update_end_time( end_time_, t );
} }
inline void Nes_Cpu::set_end_time( time_t t ) inline void Nes_Cpu::set_end_time( time_t t )
{ {
end_time_ = t; end_time_ = t;
update_end_time( t, irq_time_ ); update_end_time( t, irq_time_ );
} }
#endif #endif

View file

@ -1,280 +1,280 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Fds_Apu.h" #include "Nes_Fds_Apu.h"
/* Copyright (C) 2006 Shay Green. This module is free software; you /* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const fract_range = 65536; int const fract_range = 65536;
void Nes_Fds_Apu::reset() void Nes_Fds_Apu::reset()
{ {
memset( regs_, 0, sizeof regs_ ); memset( regs_, 0, sizeof regs_ );
memset( mod_wave, 0, sizeof mod_wave ); memset( mod_wave, 0, sizeof mod_wave );
last_time = 0; last_time = 0;
env_delay = 0; env_delay = 0;
sweep_delay = 0; sweep_delay = 0;
wave_pos = 0; wave_pos = 0;
last_amp = 0; last_amp = 0;
wave_fract = fract_range; wave_fract = fract_range;
mod_fract = fract_range; mod_fract = fract_range;
mod_pos = 0; mod_pos = 0;
mod_write_pos = 0; mod_write_pos = 0;
static byte const initial_regs [0x0B] = { static byte const initial_regs [0x0B] = {
0x80, // disable envelope 0x80, // disable envelope
0, 0, 0xC0, // disable wave and lfo 0, 0, 0xC0, // disable wave and lfo
0x80, // disable sweep 0x80, // disable sweep
0, 0, 0x80, // disable modulation 0, 0, 0x80, // disable modulation
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does? 0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
}; };
for ( int i = 0; i < (int) sizeof initial_regs; i++ ) for ( int i = 0; i < (int) sizeof initial_regs; i++ )
{ {
// two writes to set both gain and period for envelope registers // two writes to set both gain and period for envelope registers
write_( io_addr + wave_size + i, 0 ); write_( io_addr + wave_size + i, 0 );
write_( io_addr + wave_size + i, initial_regs [i] ); write_( io_addr + wave_size + i, initial_regs [i] );
} }
} }
void Nes_Fds_Apu::write_( unsigned addr, int data ) void Nes_Fds_Apu::write_( unsigned addr, int data )
{ {
unsigned reg = addr - io_addr; unsigned reg = addr - io_addr;
if ( reg < io_size ) if ( reg < io_size )
{ {
if ( reg < wave_size ) if ( reg < wave_size )
{ {
if ( regs (0x4089) & 0x80 ) if ( regs (0x4089) & 0x80 )
regs_ [reg] = data & wave_sample_max; regs_ [reg] = data & wave_sample_max;
} }
else else
{ {
regs_ [reg] = data; regs_ [reg] = data;
switch ( addr ) switch ( addr )
{ {
case 0x4080: case 0x4080:
if ( data & 0x80 ) if ( data & 0x80 )
env_gain = data & 0x3F; env_gain = data & 0x3F;
else else
env_speed = (data & 0x3F) + 1; env_speed = (data & 0x3F) + 1;
break; break;
case 0x4084: case 0x4084:
if ( data & 0x80 ) if ( data & 0x80 )
sweep_gain = data & 0x3F; sweep_gain = data & 0x3F;
else else
sweep_speed = (data & 0x3F) + 1; sweep_speed = (data & 0x3F) + 1;
break; break;
case 0x4085: case 0x4085:
mod_pos = mod_write_pos; mod_pos = mod_write_pos;
regs (0x4085) = data & 0x7F; regs (0x4085) = data & 0x7F;
break; break;
case 0x4088: case 0x4088:
if ( regs (0x4087) & 0x80 ) if ( regs (0x4087) & 0x80 )
{ {
int pos = mod_write_pos; int pos = mod_write_pos;
data &= 0x07; data &= 0x07;
mod_wave [pos ] = data; mod_wave [pos ] = data;
mod_wave [pos + 1] = data; mod_wave [pos + 1] = data;
mod_write_pos = (pos + 2) & (wave_size - 1); mod_write_pos = (pos + 2) & (wave_size - 1);
mod_pos = (mod_pos + 2) & (wave_size - 1); mod_pos = (mod_pos + 2) & (wave_size - 1);
} }
break; break;
} }
} }
} }
} }
void Nes_Fds_Apu::set_tempo( double t ) void Nes_Fds_Apu::set_tempo( double t )
{ {
lfo_tempo = lfo_base_tempo; lfo_tempo = lfo_base_tempo;
if ( t != 1.0 ) if ( t != 1.0 )
{ {
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5); lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
if ( lfo_tempo <= 0 ) if ( lfo_tempo <= 0 )
lfo_tempo = 1; lfo_tempo = 1;
} }
} }
void Nes_Fds_Apu::run_until( blip_time_t final_end_time ) void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
{ {
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082); int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
Blip_Buffer* const output_ = this->output_; Blip_Buffer* const output_ = this->output_;
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) ) if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
{ {
output_->set_modified(); output_->set_modified();
// master_volume // master_volume
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100 #define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
static unsigned char const master_volumes [4] = { static unsigned char const master_volumes [4] = {
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 ) MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
}; };
int const master_volume = master_volumes [regs (0x4089) & 0x03]; int const master_volume = master_volumes [regs (0x4089) & 0x03];
// lfo_period // lfo_period
blip_time_t lfo_period = regs (0x408A) * lfo_tempo; blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
if ( regs (0x4083) & 0x40 ) if ( regs (0x4083) & 0x40 )
lfo_period = 0; lfo_period = 0;
// sweep setup // sweep setup
blip_time_t sweep_time = last_time + sweep_delay; blip_time_t sweep_time = last_time + sweep_delay;
blip_time_t const sweep_period = lfo_period * sweep_speed; blip_time_t const sweep_period = lfo_period * sweep_speed;
if ( !sweep_period || regs (0x4084) & 0x80 ) if ( !sweep_period || regs (0x4084) & 0x80 )
sweep_time = final_end_time; sweep_time = final_end_time;
// envelope setup // envelope setup
blip_time_t env_time = last_time + env_delay; blip_time_t env_time = last_time + env_delay;
blip_time_t const env_period = lfo_period * env_speed; blip_time_t const env_period = lfo_period * env_speed;
if ( !env_period || regs (0x4080) & 0x80 ) if ( !env_period || regs (0x4080) & 0x80 )
env_time = final_end_time; env_time = final_end_time;
// modulation // modulation
int mod_freq = 0; int mod_freq = 0;
if ( !(regs (0x4087) & 0x80) ) if ( !(regs (0x4087) & 0x80) )
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086); mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
blip_time_t end_time = last_time; blip_time_t end_time = last_time;
do do
{ {
// sweep // sweep
if ( sweep_time <= end_time ) if ( sweep_time <= end_time )
{ {
sweep_time += sweep_period; sweep_time += sweep_period;
int mode = regs (0x4084) >> 5 & 2; int mode = regs (0x4084) >> 5 & 2;
int new_sweep_gain = sweep_gain + mode - 1; int new_sweep_gain = sweep_gain + mode - 1;
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode ) if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
sweep_gain = new_sweep_gain; sweep_gain = new_sweep_gain;
else else
regs (0x4084) |= 0x80; // optimization only regs (0x4084) |= 0x80; // optimization only
} }
// envelope // envelope
if ( env_time <= end_time ) if ( env_time <= end_time )
{ {
env_time += env_period; env_time += env_period;
int mode = regs (0x4080) >> 5 & 2; int mode = regs (0x4080) >> 5 & 2;
int new_env_gain = env_gain + mode - 1; int new_env_gain = env_gain + mode - 1;
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode ) if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
env_gain = new_env_gain; env_gain = new_env_gain;
else else
regs (0x4080) |= 0x80; // optimization only regs (0x4080) |= 0x80; // optimization only
} }
// new end_time // new end_time
blip_time_t const start_time = end_time; blip_time_t const start_time = end_time;
end_time = final_end_time; end_time = final_end_time;
if ( end_time > env_time ) end_time = env_time; if ( end_time > env_time ) end_time = env_time;
if ( end_time > sweep_time ) end_time = sweep_time; if ( end_time > sweep_time ) end_time = sweep_time;
// frequency modulation // frequency modulation
int freq = wave_freq; int freq = wave_freq;
if ( mod_freq ) if ( mod_freq )
{ {
// time of next modulation clock // time of next modulation clock
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq; blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
if ( end_time > mod_time ) if ( end_time > mod_time )
end_time = mod_time; end_time = mod_time;
// run modulator up to next clock and save old sweep_bias // run modulator up to next clock and save old sweep_bias
int sweep_bias = regs (0x4085); int sweep_bias = regs (0x4085);
mod_fract -= (end_time - start_time) * mod_freq; mod_fract -= (end_time - start_time) * mod_freq;
if ( mod_fract <= 0 ) if ( mod_fract <= 0 )
{ {
mod_fract += fract_range; mod_fract += fract_range;
check( (unsigned) mod_fract <= fract_range ); check( (unsigned) mod_fract <= fract_range );
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 }; static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
int mod = mod_wave [mod_pos]; int mod = mod_wave [mod_pos];
mod_pos = (mod_pos + 1) & (wave_size - 1); mod_pos = (mod_pos + 1) & (wave_size - 1);
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F; int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
if ( mod == 4 ) if ( mod == 4 )
new_sweep_bias = 0; new_sweep_bias = 0;
regs (0x4085) = new_sweep_bias; regs (0x4085) = new_sweep_bias;
} }
// apply frequency modulation // apply frequency modulation
sweep_bias = (sweep_bias ^ 0x40) - 0x40; sweep_bias = (sweep_bias ^ 0x40) - 0x40;
int factor = sweep_bias * sweep_gain; int factor = sweep_bias * sweep_gain;
int extra = factor & 0x0F; int extra = factor & 0x0F;
factor >>= 4; factor >>= 4;
if ( extra ) if ( extra )
{ {
factor--; factor--;
if ( sweep_bias >= 0 ) if ( sweep_bias >= 0 )
factor += 3; factor += 3;
} }
if ( factor > 193 ) factor -= 258; if ( factor > 193 ) factor -= 258;
if ( factor < -64 ) factor += 256; if ( factor < -64 ) factor += 256;
freq += (freq * factor) >> 6; freq += (freq * factor) >> 6;
if ( freq <= 0 ) if ( freq <= 0 )
continue; continue;
} }
// wave // wave
int wave_fract = this->wave_fract; int wave_fract = this->wave_fract;
blip_time_t delay = (wave_fract + freq - 1) / freq; blip_time_t delay = (wave_fract + freq - 1) / freq;
blip_time_t time = start_time + delay; blip_time_t time = start_time + delay;
if ( time <= end_time ) if ( time <= end_time )
{ {
// at least one wave clock within start_time...end_time // at least one wave clock within start_time...end_time
blip_time_t const min_delay = fract_range / freq; blip_time_t const min_delay = fract_range / freq;
int wave_pos = this->wave_pos; int wave_pos = this->wave_pos;
int volume = env_gain; int volume = env_gain;
if ( volume > vol_max ) if ( volume > vol_max )
volume = vol_max; volume = vol_max;
volume *= master_volume; volume *= master_volume;
int const min_fract = min_delay * freq; int const min_fract = min_delay * freq;
do do
{ {
// clock wave // clock wave
int amp = regs_ [wave_pos] * volume; int amp = regs_ [wave_pos] * volume;
wave_pos = (wave_pos + 1) & (wave_size - 1); wave_pos = (wave_pos + 1) & (wave_size - 1);
int delta = amp - last_amp; int delta = amp - last_amp;
if ( delta ) if ( delta )
{ {
last_amp = amp; last_amp = amp;
synth.offset_inline( time, delta, output_ ); synth.offset_inline( time, delta, output_ );
} }
wave_fract += fract_range - delay * freq; wave_fract += fract_range - delay * freq;
check( unsigned (fract_range - wave_fract) < freq ); check( unsigned (fract_range - wave_fract) < freq );
// delay until next clock // delay until next clock
delay = min_delay; delay = min_delay;
if ( wave_fract > min_fract ) if ( wave_fract > min_fract )
delay++; delay++;
check( delay && delay == (wave_fract + freq - 1) / freq ); check( delay && delay == (wave_fract + freq - 1) / freq );
time += delay; time += delay;
} }
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
this->wave_pos = wave_pos; this->wave_pos = wave_pos;
} }
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq; this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
check( this->wave_fract > 0 ); check( this->wave_fract > 0 );
} }
while ( end_time < final_end_time ); while ( end_time < final_end_time );
env_delay = env_time - final_end_time; check( env_delay >= 0 ); env_delay = env_time - final_end_time; check( env_delay >= 0 );
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 ); sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
} }
last_time = final_end_time; last_time = final_end_time;
} }

View file

@ -1,139 +1,139 @@
// NES FDS sound chip emulator // NES FDS sound chip emulator
// $package // $package
#ifndef NES_FDS_APU_H #ifndef NES_FDS_APU_H
#define NES_FDS_APU_H #define NES_FDS_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Nes_Fds_Apu { class Nes_Fds_Apu {
public: public:
// setup // setup
void set_tempo( double ); void set_tempo( double );
enum { osc_count = 1 }; enum { osc_count = 1 };
void set_output( Blip_Buffer* buf ); void set_output( Blip_Buffer* buf );
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
// emulation // emulation
void reset(); void reset();
enum { io_addr = 0x4040 }; enum { io_addr = 0x4040 };
enum { io_size = 0x53 }; enum { io_size = 0x53 };
void write( blip_time_t time, unsigned addr, int data ); void write( blip_time_t time, unsigned addr, int data );
int read( blip_time_t time, unsigned addr ); int read( blip_time_t time, unsigned addr );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
public: public:
Nes_Fds_Apu(); Nes_Fds_Apu();
void write_( unsigned addr, int data ); void write_( unsigned addr, int data );
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
void set_output( int index, Blip_Buffer* center, void set_output( int index, Blip_Buffer* center,
Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL ); Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL );
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; ) BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; ) BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; )
BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; ) BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; )
void osc_output( int, Blip_Buffer* ); void osc_output( int, Blip_Buffer* );
private: private:
enum { wave_size = 0x40 }; enum { wave_size = 0x40 };
enum { master_vol_max = 10 }; enum { master_vol_max = 10 };
enum { vol_max = 0x20 }; enum { vol_max = 0x20 };
enum { wave_sample_max = 0x3F }; enum { wave_sample_max = 0x3F };
unsigned char regs_ [io_size];// last written value to registers unsigned char regs_ [io_size];// last written value to registers
enum { lfo_base_tempo = 8 }; enum { lfo_base_tempo = 8 };
int lfo_tempo; // normally 8; adjusted by set_tempo() int lfo_tempo; // normally 8; adjusted by set_tempo()
int env_delay; int env_delay;
int env_speed; int env_speed;
int env_gain; int env_gain;
int sweep_delay; int sweep_delay;
int sweep_speed; int sweep_speed;
int sweep_gain; int sweep_gain;
int wave_pos; int wave_pos;
int last_amp; int last_amp;
blip_time_t wave_fract; blip_time_t wave_fract;
int mod_fract; int mod_fract;
int mod_pos; int mod_pos;
int mod_write_pos; int mod_write_pos;
unsigned char mod_wave [wave_size]; unsigned char mod_wave [wave_size];
// synthesis // synthesis
blip_time_t last_time; blip_time_t last_time;
Blip_Buffer* output_; Blip_Buffer* output_;
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
// allow access to registers by absolute address (i.e. 0x4080) // allow access to registers by absolute address (i.e. 0x4080)
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; } unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; }
void run_until( blip_time_t ); void run_until( blip_time_t );
}; };
inline void Nes_Fds_Apu::volume( double v ) inline void Nes_Fds_Apu::volume( double v )
{ {
synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v ); synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v );
} }
inline void Nes_Fds_Apu::set_output( Blip_Buffer* b ) inline void Nes_Fds_Apu::set_output( Blip_Buffer* b )
{ {
output_ = b; output_ = b;
} }
inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
output_ = buf; output_ = buf;
} }
inline void Nes_Fds_Apu::end_frame( blip_time_t end_time ) inline void Nes_Fds_Apu::end_frame( blip_time_t end_time )
{ {
if ( end_time > last_time ) if ( end_time > last_time )
run_until( end_time ); run_until( end_time );
last_time -= end_time; last_time -= end_time;
assert( last_time >= 0 ); assert( last_time >= 0 );
} }
inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data ) inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data )
{ {
run_until( time ); run_until( time );
write_( addr, data ); write_( addr, data );
} }
inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr ) inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
{ {
run_until( time ); run_until( time );
int result = 0xFF; int result = 0xFF;
switch ( addr ) switch ( addr )
{ {
case 0x4090: case 0x4090:
result = env_gain; result = env_gain;
break; break;
case 0x4092: case 0x4092:
result = sweep_gain; result = sweep_gain;
break; break;
default: default:
unsigned i = addr - io_addr; unsigned i = addr - io_addr;
if ( i < wave_size ) if ( i < wave_size )
result = regs_ [i]; result = regs_ [i];
} }
return result | 0x40; return result | 0x40;
} }
inline Nes_Fds_Apu::Nes_Fds_Apu() inline Nes_Fds_Apu::Nes_Fds_Apu()
{ {
lfo_tempo = lfo_base_tempo; lfo_tempo = lfo_base_tempo;
set_output( NULL ); set_output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
#endif #endif

View file

@ -1,121 +1,121 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Nes_Fme7_Apu.h" #include "Nes_Fme7_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
void Nes_Fme7_Apu::reset() void Nes_Fme7_Apu::reset()
{ {
last_time = 0; last_time = 0;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
oscs [i].last_amp = 0; oscs [i].last_amp = 0;
fme7_apu_state_t* state = this; fme7_apu_state_t* state = this;
memset( state, 0, sizeof *state ); memset( state, 0, sizeof *state );
} }
unsigned char const Nes_Fme7_Apu::amp_table [16] = unsigned char const Nes_Fme7_Apu::amp_table [16] =
{ {
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156),
ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624),
ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
#undef ENTRY #undef ENTRY
}; };
void Nes_Fme7_Apu::run_until( blip_time_t end_time ) void Nes_Fme7_Apu::run_until( blip_time_t end_time )
{ {
require( end_time >= last_time ); require( end_time >= last_time );
for ( int index = 0; index < osc_count; index++ ) for ( int index = 0; index < osc_count; index++ )
{ {
int mode = regs [7] >> index; int mode = regs [7] >> index;
int vol_mode = regs [010 + index]; int vol_mode = regs [010 + index];
int volume = amp_table [vol_mode & 0x0F]; int volume = amp_table [vol_mode & 0x0F];
Blip_Buffer* const osc_output = oscs [index].output; Blip_Buffer* const osc_output = oscs [index].output;
if ( !osc_output ) if ( !osc_output )
continue; continue;
// check for unsupported mode // check for unsupported mode
#ifndef NDEBUG #ifndef NDEBUG
if ( (mode & 011) <= 001 && vol_mode & 0x1F ) if ( (mode & 011) <= 001 && vol_mode & 0x1F )
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n", dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
mode, vol_mode & 0x1F ); mode, vol_mode & 0x1F );
#endif #endif
if ( (mode & 001) | (vol_mode & 0x10) ) if ( (mode & 001) | (vol_mode & 0x10) )
volume = 0; // noise and envelope aren't supported volume = 0; // noise and envelope aren't supported
// period // period
int const period_factor = 16; int const period_factor = 16;
unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor + unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor +
regs [index * 2] * period_factor; regs [index * 2] * period_factor;
if ( period < 50 ) // around 22 kHz if ( period < 50 ) // around 22 kHz
{ {
volume = 0; volume = 0;
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
period = period_factor; period = period_factor;
} }
// current amplitude // current amplitude
int amp = volume; int amp = volume;
if ( !phases [index] ) if ( !phases [index] )
amp = 0; amp = 0;
{ {
int delta = amp - oscs [index].last_amp; int delta = amp - oscs [index].last_amp;
if ( delta ) if ( delta )
{ {
oscs [index].last_amp = amp; oscs [index].last_amp = amp;
osc_output->set_modified(); osc_output->set_modified();
synth.offset( last_time, delta, osc_output ); synth.offset( last_time, delta, osc_output );
} }
} }
blip_time_t time = last_time + delays [index]; blip_time_t time = last_time + delays [index];
if ( time < end_time ) if ( time < end_time )
{ {
int delta = amp * 2 - volume; int delta = amp * 2 - volume;
osc_output->set_modified(); osc_output->set_modified();
if ( volume ) if ( volume )
{ {
do do
{ {
delta = -delta; delta = -delta;
synth.offset_inline( time, delta, osc_output ); synth.offset_inline( time, delta, osc_output );
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
oscs [index].last_amp = (delta + volume) >> 1; oscs [index].last_amp = (delta + volume) >> 1;
phases [index] = (delta > 0); phases [index] = (delta > 0);
} }
else else
{ {
// maintain phase when silent // maintain phase when silent
int count = (end_time - time + period - 1) / period; int count = (end_time - time + period - 1) / period;
phases [index] ^= count & 1; phases [index] ^= count & 1;
time += count * period; time += count * period;
} }
} }
delays [index] = time - end_time; delays [index] = time - end_time;
} }
last_time = end_time; last_time = end_time;
} }

View file

@ -1,131 +1,131 @@
// Sunsoft FME-7 sound emulator // Sunsoft FME-7 sound emulator
// $package // $package
#ifndef NES_FME7_APU_H #ifndef NES_FME7_APU_H
#define NES_FME7_APU_H #define NES_FME7_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
struct fme7_apu_state_t struct fme7_apu_state_t
{ {
enum { reg_count = 14 }; enum { reg_count = 14 };
BOOST::uint8_t regs [reg_count]; BOOST::uint8_t regs [reg_count];
BOOST::uint8_t phases [3]; // 0 or 1 BOOST::uint8_t phases [3]; // 0 or 1
BOOST::uint8_t latch; BOOST::uint8_t latch;
BOOST::uint16_t delays [3]; // a, b, c BOOST::uint16_t delays [3]; // a, b, c
}; };
class Nes_Fme7_Apu : private fme7_apu_state_t { class Nes_Fme7_Apu : private fme7_apu_state_t {
public: public:
// See Nes_Apu.h for reference // See Nes_Apu.h for reference
void reset(); void reset();
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& ); void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int index, Blip_Buffer* ); void set_output( int index, Blip_Buffer* );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
void save_state( fme7_apu_state_t* ) const; void save_state( fme7_apu_state_t* ) const;
void load_state( fme7_apu_state_t const& ); void load_state( fme7_apu_state_t const& );
// Mask and addresses of registers // Mask and addresses of registers
enum { addr_mask = 0xE000 }; enum { addr_mask = 0xE000 };
enum { data_addr = 0xE000 }; enum { data_addr = 0xE000 };
enum { latch_addr = 0xC000 }; enum { latch_addr = 0xC000 };
// (addr & addr_mask) == latch_addr // (addr & addr_mask) == latch_addr
void write_latch( int ); void write_latch( int );
// (addr & addr_mask) == data_addr // (addr & addr_mask) == data_addr
void write_data( blip_time_t, int data ); void write_data( blip_time_t, int data );
public: public:
Nes_Fme7_Apu(); Nes_Fme7_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
private: private:
// noncopyable // noncopyable
Nes_Fme7_Apu( const Nes_Fme7_Apu& ); Nes_Fme7_Apu( const Nes_Fme7_Apu& );
Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& );
static unsigned char const amp_table [16]; static unsigned char const amp_table [16];
struct { struct {
Blip_Buffer* output; Blip_Buffer* output;
int last_amp; int last_amp;
} oscs [osc_count]; } oscs [osc_count];
blip_time_t last_time; blip_time_t last_time;
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
Blip_Synth_Norm synth; Blip_Synth_Norm synth;
void run_until( blip_time_t ); void run_until( blip_time_t );
}; };
inline void Nes_Fme7_Apu::volume( double v ) inline void Nes_Fme7_Apu::volume( double v )
{ {
synth.volume( 0.38 / amp_range * v ); // to do: fine-tune synth.volume( 0.38 / amp_range * v ); // to do: fine-tune
} }
inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq ) inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq )
{ {
synth.treble_eq( eq ); synth.treble_eq( eq );
} }
inline void Nes_Fme7_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Fme7_Apu::set_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;
} }
inline void Nes_Fme7_Apu::set_output( Blip_Buffer* buf ) inline void Nes_Fme7_Apu::set_output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
set_output( i, buf ); set_output( i, buf );
} }
inline Nes_Fme7_Apu::Nes_Fme7_Apu() inline Nes_Fme7_Apu::Nes_Fme7_Apu()
{ {
set_output( NULL ); set_output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; } inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; }
inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data ) inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data )
{ {
if ( (unsigned) latch >= reg_count ) if ( (unsigned) latch >= reg_count )
{ {
#ifdef dprintf #ifdef dprintf
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
#endif #endif
return; return;
} }
run_until( time ); run_until( time );
regs [latch] = data; regs [latch] = data;
} }
inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) inline void Nes_Fme7_Apu::end_frame( blip_time_t time )
{ {
if ( time > last_time ) if ( time > last_time )
run_until( time ); run_until( time );
assert( last_time >= time ); assert( last_time >= time );
last_time -= time; last_time -= time;
} }
inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const
{ {
*out = *this; *out = *this;
} }
inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in )
{ {
reset(); reset();
fme7_apu_state_t* state = this; fme7_apu_state_t* state = this;
*state = in; *state = in;
} }
#endif #endif

View file

@ -1,70 +1,70 @@
// NES MMC5 sound chip emulator // NES MMC5 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu $vers
#ifndef NES_MMC5_APU_H #ifndef NES_MMC5_APU_H
#define NES_MMC5_APU_H #define NES_MMC5_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Nes_Apu.h" #include "Nes_Apu.h"
class Nes_Mmc5_Apu : public Nes_Apu { class Nes_Mmc5_Apu : public Nes_Apu {
public: public:
enum { regs_addr = 0x5000 }; enum { regs_addr = 0x5000 };
enum { regs_size = 0x16 }; enum { regs_size = 0x16 };
enum { osc_count = 3 }; enum { osc_count = 3 };
void write_register( blip_time_t, unsigned addr, int data ); void write_register( blip_time_t, unsigned addr, int data );
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
void set_output( int index, Blip_Buffer* ); void set_output( int index, Blip_Buffer* );
enum { exram_size = 1024 }; enum { exram_size = 1024 };
unsigned char exram [exram_size]; unsigned char exram [exram_size];
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; ) BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; ) BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; )
}; };
inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b ) inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b )
{ {
// in: square 1, square 2, PCM // in: square 1, square 2, PCM
// out: square 1, square 2, skipped, skipped, PCM // out: square 1, square 2, skipped, skipped, PCM
if ( i > 1 ) if ( i > 1 )
i += 2; i += 2;
Nes_Apu::set_output( i, b ); Nes_Apu::set_output( i, b );
} }
inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b ) inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b )
{ {
set_output( 0, b ); set_output( 0, b );
set_output( 1, b ); set_output( 1, b );
set_output( 2, b ); set_output( 2, b );
} }
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data ) inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )
{ {
switch ( addr ) switch ( addr )
{ {
case 0x5015: // channel enables case 0x5015: // channel enables
data &= 0x03; // enable the square waves only data &= 0x03; // enable the square waves only
// fall through // fall through
case 0x5000: // Square 1 case 0x5000: // Square 1
case 0x5002: case 0x5002:
case 0x5003: case 0x5003:
case 0x5004: // Square 2 case 0x5004: // Square 2
case 0x5006: case 0x5006:
case 0x5007: case 0x5007:
case 0x5011: // DAC case 0x5011: // DAC
Nes_Apu::write_register( time, addr - 0x1000, data ); Nes_Apu::write_register( time, addr - 0x1000, data );
break; break;
case 0x5010: // some things write to this for some reason case 0x5010: // some things write to this for some reason
break; break;
#ifdef BLARGG_DEBUG_H #ifdef BLARGG_DEBUG_H
default: default:
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data ); dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
#endif #endif
} }
} }
#endif #endif

View file

@ -1,102 +1,102 @@
// Namco 106 sound chip emulator // Namco 106 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu $vers
#ifndef NES_NAMCO_APU_H #ifndef NES_NAMCO_APU_H
#define NES_NAMCO_APU_H #define NES_NAMCO_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
struct namco_state_t; struct namco_state_t;
class Nes_Namco_Apu { class Nes_Namco_Apu {
public: public:
// See Nes_Apu.h for reference. // See Nes_Apu.h for reference.
void volume( double ); void volume( double );
void treble_eq( const blip_eq_t& ); void treble_eq( const blip_eq_t& );
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
enum { osc_count = 8 }; enum { osc_count = 8 };
void set_output( int index, Blip_Buffer* ); void set_output( int index, Blip_Buffer* );
void reset(); void reset();
void end_frame( blip_time_t ); void end_frame( blip_time_t );
// Read/write data register is at 0x4800 // Read/write data register is at 0x4800
enum { data_reg_addr = 0x4800 }; enum { data_reg_addr = 0x4800 };
void write_data( blip_time_t, int ); void write_data( blip_time_t, int );
int read_data(); int read_data();
// Write-only address register is at 0xF800 // Write-only address register is at 0xF800
enum { addr_reg_addr = 0xF800 }; enum { addr_reg_addr = 0xF800 };
void write_addr( int ); void write_addr( int );
// to do: implement save/restore // to do: implement save/restore
void save_state( namco_state_t* out ) const; void save_state( namco_state_t* out ) const;
void load_state( namco_state_t const& ); void load_state( namco_state_t const& );
public: public:
Nes_Namco_Apu(); Nes_Namco_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
private: private:
// noncopyable // noncopyable
Nes_Namco_Apu( const Nes_Namco_Apu& ); Nes_Namco_Apu( const Nes_Namco_Apu& );
Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& );
struct Namco_Osc { struct Namco_Osc {
int delay; int delay;
Blip_Buffer* output; Blip_Buffer* output;
short last_amp; short last_amp;
short wave_pos; short wave_pos;
}; };
Namco_Osc oscs [osc_count]; Namco_Osc oscs [osc_count];
blip_time_t last_time; blip_time_t last_time;
int addr_reg; int addr_reg;
enum { reg_count = 0x80 }; enum { reg_count = 0x80 };
BOOST::uint8_t reg [reg_count]; BOOST::uint8_t reg [reg_count];
Blip_Synth_Norm synth; Blip_Synth_Norm synth;
BOOST::uint8_t& access(); BOOST::uint8_t& access();
void run_until( blip_time_t ); void run_until( blip_time_t );
}; };
/* /*
struct namco_state_t struct namco_state_t
{ {
BOOST::uint8_t regs [0x80]; BOOST::uint8_t regs [0x80];
BOOST::uint8_t addr; BOOST::uint8_t addr;
BOOST::uint8_t unused; BOOST::uint8_t unused;
BOOST::uint8_t positions [8]; BOOST::uint8_t positions [8];
BOOST::uint32_t delays [8]; BOOST::uint32_t delays [8];
}; };
*/ */
inline BOOST::uint8_t& Nes_Namco_Apu::access() inline BOOST::uint8_t& Nes_Namco_Apu::access()
{ {
int addr = addr_reg & 0x7F; int addr = addr_reg & 0x7F;
if ( addr_reg & 0x80 ) if ( addr_reg & 0x80 )
addr_reg = (addr + 1) | 0x80; addr_reg = (addr + 1) | 0x80;
return reg [addr]; return reg [addr];
} }
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count / 15 * v ); } inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count / 15 * v ); }
inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; } inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; }
inline int Nes_Namco_Apu::read_data() { return access(); } inline int Nes_Namco_Apu::read_data() { return access(); }
inline void Nes_Namco_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Namco_Apu::set_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;
} }
inline void Nes_Namco_Apu::write_data( blip_time_t time, int data ) inline void Nes_Namco_Apu::write_data( blip_time_t time, int data )
{ {
run_until( time ); run_until( time );
access() = data; access() = data;
} }
#endif #endif

File diff suppressed because it is too large Load diff

View file

@ -1,147 +1,147 @@
// Private oscillators used by Nes_Apu // Private oscillators used by Nes_Apu
// Nes_Snd_Emu $vers // Nes_Snd_Emu $vers
#ifndef NES_OSCS_H #ifndef NES_OSCS_H
#define NES_OSCS_H #define NES_OSCS_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Nes_Apu; class Nes_Apu;
struct Nes_Osc struct Nes_Osc
{ {
typedef int nes_time_t; typedef int nes_time_t;
unsigned char regs [4]; unsigned char regs [4];
bool reg_written [4]; bool reg_written [4];
Blip_Buffer* output; Blip_Buffer* output;
int length_counter;// length counter (0 if unused by oscillator) int length_counter;// length counter (0 if unused by oscillator)
int delay; // delay until next (potential) transition int delay; // delay until next (potential) transition
int last_amp; // last amplitude oscillator was outputting int last_amp; // last amplitude oscillator was outputting
void clock_length( int halt_mask ); void clock_length( int halt_mask );
int period() const { int period() const {
return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF); return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF);
} }
void reset() { void reset() {
delay = 0; delay = 0;
last_amp = 0; last_amp = 0;
} }
int update_amp( int amp ) { int update_amp( int amp ) {
int delta = amp - last_amp; int delta = amp - last_amp;
last_amp = amp; last_amp = amp;
return delta; return delta;
} }
}; };
struct Nes_Envelope : Nes_Osc struct Nes_Envelope : Nes_Osc
{ {
int envelope; int envelope;
int env_delay; int env_delay;
void clock_envelope(); void clock_envelope();
int volume() const; int volume() const;
void reset() { void reset() {
envelope = 0; envelope = 0;
env_delay = 0; env_delay = 0;
Nes_Osc::reset(); Nes_Osc::reset();
} }
}; };
// Nes_Square // Nes_Square
struct Nes_Square : Nes_Envelope struct Nes_Square : Nes_Envelope
{ {
enum { negate_flag = 0x08 }; enum { negate_flag = 0x08 };
enum { shift_mask = 0x07 }; enum { shift_mask = 0x07 };
enum { phase_range = 8 }; enum { phase_range = 8 };
int phase; int phase;
int sweep_delay; int sweep_delay;
typedef Blip_Synth_Norm Synth; typedef Blip_Synth_Norm Synth;
Synth const& synth; // shared between squares Synth const& synth; // shared between squares
Nes_Square( Synth const* s ) : synth( *s ) { } Nes_Square( Synth const* s ) : synth( *s ) { }
void clock_sweep( int adjust ); void clock_sweep( int adjust );
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );
void reset() { void reset() {
sweep_delay = 0; sweep_delay = 0;
Nes_Envelope::reset(); Nes_Envelope::reset();
} }
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period ); nes_time_t timer_period );
}; };
// Nes_Triangle // Nes_Triangle
struct Nes_Triangle : Nes_Osc struct Nes_Triangle : Nes_Osc
{ {
enum { phase_range = 16 }; enum { phase_range = 16 };
int phase; int phase;
int linear_counter; int linear_counter;
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
int calc_amp() const; int calc_amp() const;
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );
void clock_linear_counter(); void clock_linear_counter();
void reset() { void reset() {
linear_counter = 0; linear_counter = 0;
phase = 1; phase = 1;
Nes_Osc::reset(); Nes_Osc::reset();
} }
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period ); nes_time_t timer_period );
}; };
// Nes_Noise // Nes_Noise
struct Nes_Noise : Nes_Envelope struct Nes_Noise : Nes_Envelope
{ {
int noise; int noise;
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );
void reset() { void reset() {
noise = 1 << 14; noise = 1 << 14;
Nes_Envelope::reset(); Nes_Envelope::reset();
} }
}; };
// Nes_Dmc // Nes_Dmc
struct Nes_Dmc : Nes_Osc struct Nes_Dmc : Nes_Osc
{ {
int address; // address of next byte to read int address; // address of next byte to read
int period; int period;
//int length_counter; // bytes remaining to play (already defined in Nes_Osc) //int length_counter; // bytes remaining to play (already defined in Nes_Osc)
int buf; int buf;
int bits_remain; int bits_remain;
int bits; int bits;
bool buf_full; bool buf_full;
bool silence; bool silence;
enum { loop_flag = 0x40 }; enum { loop_flag = 0x40 };
int dac; int dac;
nes_time_t next_irq; nes_time_t next_irq;
bool irq_enabled; bool irq_enabled;
bool irq_flag; bool irq_flag;
bool pal_mode; bool pal_mode;
bool nonlinear; bool nonlinear;
Nes_Apu* apu; Nes_Apu* apu;
Blip_Synth_Fast synth; Blip_Synth_Fast synth;
int update_amp_nonlinear( int dac_in ); int update_amp_nonlinear( int dac_in );
void start(); void start();
void write_register( int, int ); void write_register( int, int );
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );
void recalc_irq(); void recalc_irq();
void fill_buffer(); void fill_buffer();
void reload_sample(); void reload_sample();
void reset(); void reset();
int count_reads( nes_time_t, nes_time_t* ) const; int count_reads( nes_time_t, nes_time_t* ) const;
nes_time_t next_read_time() const; nes_time_t next_read_time() const;
}; };
#endif #endif

View file

@ -1,216 +1,216 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Vrc6_Apu.h" #include "Nes_Vrc6_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf ) void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
set_output( i, buf ); set_output( i, buf );
} }
void Nes_Vrc6_Apu::reset() void Nes_Vrc6_Apu::reset()
{ {
last_time = 0; last_time = 0;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
{ {
Vrc6_Osc& osc = oscs [i]; Vrc6_Osc& osc = oscs [i];
for ( int j = 0; j < reg_count; j++ ) for ( int j = 0; j < reg_count; j++ )
osc.regs [j] = 0; osc.regs [j] = 0;
osc.delay = 0; osc.delay = 0;
osc.last_amp = 0; osc.last_amp = 0;
osc.phase = 1; osc.phase = 1;
osc.amp = 0; osc.amp = 0;
} }
} }
Nes_Vrc6_Apu::Nes_Vrc6_Apu() Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{ {
set_output( NULL ); set_output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
void Nes_Vrc6_Apu::run_until( blip_time_t time ) void Nes_Vrc6_Apu::run_until( blip_time_t time )
{ {
require( time >= last_time ); require( time >= last_time );
run_square( oscs [0], time ); run_square( oscs [0], time );
run_square( oscs [1], time ); run_square( oscs [1], time );
run_saw( time ); run_saw( time );
last_time = time; last_time = time;
} }
void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data ) void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data )
{ {
require( (unsigned) osc_index < osc_count ); require( (unsigned) osc_index < osc_count );
require( (unsigned) reg < reg_count ); require( (unsigned) reg < reg_count );
run_until( time ); run_until( time );
oscs [osc_index].regs [reg] = data; oscs [osc_index].regs [reg] = data;
} }
void Nes_Vrc6_Apu::end_frame( blip_time_t time ) void Nes_Vrc6_Apu::end_frame( blip_time_t time )
{ {
if ( time > last_time ) if ( time > last_time )
run_until( time ); run_until( time );
assert( last_time >= time ); assert( last_time >= time );
last_time -= time; last_time -= time;
} }
void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
{ {
assert( sizeof (vrc6_apu_state_t) == 20 ); assert( sizeof (vrc6_apu_state_t) == 20 );
out->saw_amp = oscs [2].amp; out->saw_amp = oscs [2].amp;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
{ {
Vrc6_Osc const& osc = oscs [i]; Vrc6_Osc const& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ ) for ( int r = 0; r < reg_count; r++ )
out->regs [i] [r] = osc.regs [r]; out->regs [i] [r] = osc.regs [r];
out->delays [i] = osc.delay; out->delays [i] = osc.delay;
out->phases [i] = osc.phase; out->phases [i] = osc.phase;
} }
} }
void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in ) void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in )
{ {
reset(); reset();
oscs [2].amp = in.saw_amp; oscs [2].amp = in.saw_amp;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
{ {
Vrc6_Osc& osc = oscs [i]; Vrc6_Osc& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ ) for ( int r = 0; r < reg_count; r++ )
osc.regs [r] = in.regs [i] [r]; osc.regs [r] = in.regs [i] [r];
osc.delay = in.delays [i]; osc.delay = in.delays [i];
osc.phase = in.phases [i]; osc.phase = in.phases [i];
} }
if ( !oscs [2].phase ) if ( !oscs [2].phase )
oscs [2].phase = 1; oscs [2].phase = 1;
} }
void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time ) void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
{ {
Blip_Buffer* output = osc.output; Blip_Buffer* output = osc.output;
if ( !output ) if ( !output )
return; return;
int volume = osc.regs [0] & 15; int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) ) if ( !(osc.regs [2] & 0x80) )
volume = 0; volume = 0;
int gate = osc.regs [0] & 0x80; int gate = osc.regs [0] & 0x80;
int duty = ((osc.regs [0] >> 4) & 7) + 1; int duty = ((osc.regs [0] >> 4) & 7) + 1;
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
blip_time_t time = last_time; blip_time_t time = last_time;
if ( delta ) if ( delta )
{ {
osc.last_amp += delta; osc.last_amp += delta;
output->set_modified(); output->set_modified();
square_synth.offset( time, delta, output ); square_synth.offset( time, delta, output );
} }
time += osc.delay; time += osc.delay;
osc.delay = 0; osc.delay = 0;
int period = osc.period(); int period = osc.period();
if ( volume && !gate && period > 4 ) if ( volume && !gate && period > 4 )
{ {
if ( time < end_time ) if ( time < end_time )
{ {
int phase = osc.phase; int phase = osc.phase;
output->set_modified(); output->set_modified();
do do
{ {
phase++; phase++;
if ( phase == 16 ) if ( phase == 16 )
{ {
phase = 0; phase = 0;
osc.last_amp = volume; osc.last_amp = volume;
square_synth.offset( time, volume, output ); square_synth.offset( time, volume, output );
} }
if ( phase == duty ) if ( phase == duty )
{ {
osc.last_amp = 0; osc.last_amp = 0;
square_synth.offset( time, -volume, output ); square_synth.offset( time, -volume, output );
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
osc.phase = phase; osc.phase = phase;
} }
osc.delay = time - end_time; osc.delay = time - end_time;
} }
} }
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time ) void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
{ {
Vrc6_Osc& osc = oscs [2]; Vrc6_Osc& osc = oscs [2];
Blip_Buffer* output = osc.output; Blip_Buffer* output = osc.output;
if ( !output ) if ( !output )
return; return;
output->set_modified(); output->set_modified();
int amp = osc.amp; int amp = osc.amp;
int amp_step = osc.regs [0] & 0x3F; int amp_step = osc.regs [0] & 0x3F;
blip_time_t time = last_time; blip_time_t time = last_time;
int last_amp = osc.last_amp; int last_amp = osc.last_amp;
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
{ {
osc.delay = 0; osc.delay = 0;
int delta = (amp >> 3) - last_amp; int delta = (amp >> 3) - last_amp;
last_amp = amp >> 3; last_amp = amp >> 3;
saw_synth.offset( time, delta, output ); saw_synth.offset( time, delta, output );
} }
else else
{ {
time += osc.delay; time += osc.delay;
if ( time < end_time ) if ( time < end_time )
{ {
int period = osc.period() * 2; int period = osc.period() * 2;
int phase = osc.phase; int phase = osc.phase;
do do
{ {
if ( --phase == 0 ) if ( --phase == 0 )
{ {
phase = 7; phase = 7;
amp = 0; amp = 0;
} }
int delta = (amp >> 3) - last_amp; int delta = (amp >> 3) - last_amp;
if ( delta ) if ( delta )
{ {
last_amp = amp >> 3; last_amp = amp >> 3;
saw_synth.offset( time, delta, output ); saw_synth.offset( time, delta, output );
} }
time += period; time += period;
amp = (amp + amp_step) & 0xFF; amp = (amp + amp_step) & 0xFF;
} }
while ( time < end_time ); while ( time < end_time );
osc.phase = phase; osc.phase = phase;
osc.amp = amp; osc.amp = amp;
} }
osc.delay = time - end_time; osc.delay = time - end_time;
} }
osc.last_amp = last_amp; osc.last_amp = last_amp;
} }

View file

@ -1,95 +1,95 @@
// Konami VRC6 sound chip emulator // Konami VRC6 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu $vers
#ifndef NES_VRC6_APU_H #ifndef NES_VRC6_APU_H
#define NES_VRC6_APU_H #define NES_VRC6_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
struct vrc6_apu_state_t; struct vrc6_apu_state_t;
class Nes_Vrc6_Apu { class Nes_Vrc6_Apu {
public: public:
// See Nes_Apu.h for reference // See Nes_Apu.h for reference
void reset(); void reset();
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& ); void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int index, Blip_Buffer* ); void set_output( int index, Blip_Buffer* );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
void save_state( vrc6_apu_state_t* ) const; void save_state( vrc6_apu_state_t* ) const;
void load_state( vrc6_apu_state_t const& ); void load_state( vrc6_apu_state_t const& );
// Oscillator 0 write-only registers are at $9000-$9002 // Oscillator 0 write-only registers are at $9000-$9002
// Oscillator 1 write-only registers are at $A000-$A002 // Oscillator 1 write-only registers are at $A000-$A002
// Oscillator 2 write-only registers are at $B000-$B002 // Oscillator 2 write-only registers are at $B000-$B002
enum { reg_count = 3 }; enum { reg_count = 3 };
enum { base_addr = 0x9000 }; enum { base_addr = 0x9000 };
enum { addr_step = 0x1000 }; enum { addr_step = 0x1000 };
void write_osc( blip_time_t, int osc, int reg, int data ); void write_osc( blip_time_t, int osc, int reg, int data );
public: public:
Nes_Vrc6_Apu(); Nes_Vrc6_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
private: private:
// noncopyable // noncopyable
Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); Nes_Vrc6_Apu( const Nes_Vrc6_Apu& );
Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& ); Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& );
struct Vrc6_Osc struct Vrc6_Osc
{ {
BOOST::uint8_t regs [3]; BOOST::uint8_t regs [3];
Blip_Buffer* output; Blip_Buffer* output;
int delay; int delay;
int last_amp; int last_amp;
int phase; int phase;
int amp; // only used by saw int amp; // only used by saw
int period() const int period() const
{ {
return (regs [2] & 0x0F) * 0x100 + regs [1] + 1; return (regs [2] & 0x0F) * 0x100 + regs [1] + 1;
} }
}; };
Vrc6_Osc oscs [osc_count]; Vrc6_Osc oscs [osc_count];
blip_time_t last_time; blip_time_t last_time;
Blip_Synth_Fast saw_synth; Blip_Synth_Fast saw_synth;
Blip_Synth_Norm square_synth; Blip_Synth_Norm square_synth;
void run_until( blip_time_t ); void run_until( blip_time_t );
void run_square( Vrc6_Osc& osc, blip_time_t ); void run_square( Vrc6_Osc& osc, blip_time_t );
void run_saw( blip_time_t ); void run_saw( blip_time_t );
}; };
struct vrc6_apu_state_t struct vrc6_apu_state_t
{ {
BOOST::uint8_t regs [3] [3]; BOOST::uint8_t regs [3] [3];
BOOST::uint8_t saw_amp; BOOST::uint8_t saw_amp;
BOOST::uint16_t delays [3]; BOOST::uint16_t delays [3];
BOOST::uint8_t phases [3]; BOOST::uint8_t phases [3];
BOOST::uint8_t unused; BOOST::uint8_t unused;
}; };
inline void Nes_Vrc6_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Vrc6_Apu::set_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;
} }
inline void Nes_Vrc6_Apu::volume( double v ) inline void Nes_Vrc6_Apu::volume( double v )
{ {
double const factor = 0.0967 * 2; double const factor = 0.0967 * 2;
saw_synth.volume( factor / 31 * v ); saw_synth.volume( factor / 31 * v );
square_synth.volume( factor * 0.5 / 15 * v ); square_synth.volume( factor * 0.5 / 15 * v );
} }
inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq )
{ {
saw_synth.treble_eq( eq ); saw_synth.treble_eq( eq );
square_synth.treble_eq( eq ); square_synth.treble_eq( eq );
} }
#endif #endif

View file

@ -1,10 +1,18 @@
#include "Nes_Vrc7_Apu.h" #include "Nes_Vrc7_Apu.h"
#include "ym2413.h" extern "C" {
#include "../vgmplay/chips/emu2413.h"
}
#include <string.h> #include <string.h>
#include "blargg_source.h" #include "blargg_source.h"
static unsigned char vrc7_inst[(16 + 3) * 8] =
{
#include "../vgmplay/chips/vrc7tone.h"
};
int const period = 36; // NES CPU clocks per FM clock int const period = 36; // NES CPU clocks per FM clock
Nes_Vrc7_Apu::Nes_Vrc7_Apu() Nes_Vrc7_Apu::Nes_Vrc7_Apu()
@ -14,8 +22,10 @@ Nes_Vrc7_Apu::Nes_Vrc7_Apu()
blargg_err_t Nes_Vrc7_Apu::init() blargg_err_t Nes_Vrc7_Apu::init()
{ {
CHECK_ALLOC( opll = ym2413_init( 3579545, 3579545 / 72, 1 ) ); CHECK_ALLOC( opll = OPLL_new( 3579545, 3579545 / 72 ) );
OPLL_SetChipMode((OPLL *) opll, 1);
OPLL_setPatch((OPLL *) opll, vrc7_inst);
set_output( 0 ); set_output( 0 );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
@ -25,7 +35,7 @@ blargg_err_t Nes_Vrc7_Apu::init()
Nes_Vrc7_Apu::~Nes_Vrc7_Apu() Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
{ {
if ( opll ) if ( opll )
ym2413_shutdown( opll ); OPLL_delete( (OPLL *) opll );
} }
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf ) void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
@ -46,7 +56,7 @@ void Nes_Vrc7_Apu::output_changed()
break; break;
} }
} }
if ( mono.output ) if ( mono.output )
{ {
for ( int i = osc_count; --i; ) for ( int i = osc_count; --i; )
@ -62,7 +72,7 @@ void Nes_Vrc7_Apu::reset()
addr = 0; addr = 0;
next_time = 0; next_time = 0;
mono.last_amp = 0; mono.last_amp = 0;
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
Vrc7_Osc& osc = oscs [i]; Vrc7_Osc& osc = oscs [i];
@ -71,7 +81,7 @@ void Nes_Vrc7_Apu::reset()
osc.regs [j] = 0; osc.regs [j] = 0;
} }
ym2413_reset_chip( opll ); OPLL_reset( (OPLL *) opll );
} }
void Nes_Vrc7_Apu::write_reg( int data ) void Nes_Vrc7_Apu::write_reg( int data )
@ -85,21 +95,23 @@ void Nes_Vrc7_Apu::write_data( blip_time_t time, int data )
int chan = addr & 15; int chan = addr & 15;
if ( (unsigned) type < 3 && chan < osc_count ) if ( (unsigned) type < 3 && chan < osc_count )
oscs [chan].regs [type] = data; oscs [chan].regs [type] = data;
if ( addr < 0x08 )
inst [addr] = data;
if ( time > next_time ) if ( time > next_time )
run_until( time ); run_until( time );
ym2413_write( opll, 0, addr ); OPLL_writeIO( (OPLL *) opll, 0, addr );
ym2413_write( opll, 1, data ); OPLL_writeIO( (OPLL *) opll, 1, data );
} }
void Nes_Vrc7_Apu::end_frame( blip_time_t time ) void Nes_Vrc7_Apu::end_frame( blip_time_t time )
{ {
if ( time > next_time ) if ( time > next_time )
run_until( time ); run_until( time );
next_time -= time; next_time -= time;
assert( next_time >= 0 ); assert( next_time >= 0 );
for ( int i = osc_count; --i >= 0; ) for ( int i = osc_count; --i >= 0; )
{ {
Blip_Buffer* output = oscs [i].output; Blip_Buffer* output = oscs [i].output;
@ -117,13 +129,13 @@ void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const
for ( int j = 0; j < 3; ++j ) for ( int j = 0; j < 3; ++j )
out->regs [i] [j] = oscs [i].regs [j]; out->regs [i] [j] = oscs [i].regs [j];
} }
memcpy( out->inst, ym2413_get_inst0( opll ), 8 ); memcpy( out->inst, inst, 8 );
} }
void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in ) void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
{ {
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 ); assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
reset(); reset();
next_time = in.delay; next_time = in.delay;
write_reg( in.latch ); write_reg( in.latch );
@ -134,18 +146,19 @@ void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
oscs [i].regs [j] = in.regs [i] [j]; oscs [i].regs [j] = in.regs [i] [j];
} }
memcpy( inst, in.inst, 8 );
for ( i = 0; i < 8; ++i ) for ( i = 0; i < 8; ++i )
{ {
ym2413_write( opll, 0, i ); OPLL_writeIO( (OPLL *) opll, 0, i );
ym2413_write( opll, 1, in.inst [i] ); OPLL_writeIO( (OPLL *) opll, 1, in.inst [i] );
} }
for ( i = 0; i < 3; ++i ) for ( i = 0; i < 3; ++i )
{ {
for ( int j = 0; j < 6; ++j ) for ( int j = 0; j < 6; ++j )
{ {
ym2413_write( opll, 0, 0x10 + i * 0x10 + j ); OPLL_writeIO( (OPLL *) opll, 0, 0x10 + i * 0x10 + j );
ym2413_write( opll, 1, oscs [j].regs [i] ); OPLL_writeIO( (OPLL *) opll, 1, oscs [j].regs [i] );
} }
} }
} }
@ -157,16 +170,15 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
blip_time_t time = next_time; blip_time_t time = next_time;
void* opll = this->opll; // cache void* opll = this->opll; // cache
Blip_Buffer* const mono_output = mono.output; Blip_Buffer* const mono_output = mono.output;
e_int32 buffer [2];
e_int32* buffers[2] = {&buffer[0], &buffer[1]};
if ( mono_output ) if ( mono_output )
{ {
// optimal case // optimal case
do do
{ {
ym2413_advance_lfo( opll ); OPLL_calc_stereo( (OPLL *) opll, buffers, 1, -1 );
int amp = 0; int amp = buffer [0] + buffer [1];
for ( int i = 0; i < osc_count; i++ )
amp += ym2413_calcch( opll, i );
ym2413_advance( opll );
int delta = amp - mono.last_amp; int delta = amp - mono.last_amp;
if ( delta ) if ( delta )
{ {
@ -182,13 +194,14 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
mono.last_amp = 0; mono.last_amp = 0;
do do
{ {
ym2413_advance_lfo( opll ); OPLL_advance( (OPLL *) opll );
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
{ {
Vrc7_Osc& osc = oscs [i]; Vrc7_Osc& osc = oscs [i];
if ( osc.output ) if ( osc.output )
{ {
int amp = ym2413_calcch( opll, i ); OPLL_calc_stereo( (OPLL *) opll, buffers, 1, i );
int amp = buffer [0] + buffer [1];
int delta = amp - osc.last_amp; int delta = amp - osc.last_amp;
if ( delta ) if ( delta )
{ {
@ -197,7 +210,6 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
} }
} }
} }
ym2413_advance( opll );
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );

View file

@ -1,80 +1,82 @@
// Konami VRC7 sound chip emulator // Konami VRC7 sound chip emulator
#ifndef NES_VRC7_APU_H #ifndef NES_VRC7_APU_H
#define NES_VRC7_APU_H #define NES_VRC7_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
struct vrc7_snapshot_t; struct vrc7_snapshot_t;
class Nes_Vrc7_Apu { class Nes_Vrc7_Apu {
public: public:
blargg_err_t init(); blargg_err_t init();
// See Nes_Apu.h for reference // See Nes_Apu.h for reference
void reset(); void reset();
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& ); void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
enum { osc_count = 6 }; enum { osc_count = 6 };
void set_output( int index, Blip_Buffer* ); void set_output( int index, Blip_Buffer* );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
void save_snapshot( vrc7_snapshot_t* ) const; void save_snapshot( vrc7_snapshot_t* ) const;
void load_snapshot( vrc7_snapshot_t const& ); void load_snapshot( vrc7_snapshot_t const& );
void write_reg( int reg ); void write_reg( int reg );
void write_data( blip_time_t, int data ); void write_data( blip_time_t, int data );
public: public:
Nes_Vrc7_Apu(); Nes_Vrc7_Apu();
~Nes_Vrc7_Apu(); ~Nes_Vrc7_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
private: private:
// noncopyable // noncopyable
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& ); Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& ); Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
struct Vrc7_Osc struct Vrc7_Osc
{ {
BOOST::uint8_t regs [3]; BOOST::uint8_t regs [3];
Blip_Buffer* output; Blip_Buffer* output;
int last_amp; int last_amp;
}; };
Vrc7_Osc oscs [osc_count]; Vrc7_Osc oscs [osc_count];
void* opll; BOOST::uint8_t kon;
int addr; BOOST::uint8_t inst [8];
blip_time_t next_time; void* opll;
struct { int addr;
Blip_Buffer* output; blip_time_t next_time;
int last_amp; struct {
} mono; Blip_Buffer* output;
int last_amp;
Blip_Synth_Fast synth; } mono;
void run_until( blip_time_t ); Blip_Synth_Fast synth;
void output_changed();
}; void run_until( blip_time_t );
void output_changed();
struct vrc7_snapshot_t };
{
BOOST::uint8_t latch; struct vrc7_snapshot_t
BOOST::uint8_t inst [8]; {
BOOST::uint8_t regs [6] [3]; BOOST::uint8_t latch;
BOOST::uint8_t delay; BOOST::uint8_t inst [8];
}; BOOST::uint8_t regs [6] [3];
BOOST::uint8_t delay;
inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf ) };
{
assert( (unsigned) i < osc_count ); inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf )
oscs [i].output = buf; {
output_changed(); assert( (unsigned) i < osc_count );
} oscs [i].output = buf;
output_changed();
// DB2LIN_AMP_BITS == 11, * 2 }
inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
// DB2LIN_AMP_BITS == 11, * 2
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
#endif inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
#endif

View file

@ -1,302 +1,302 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Nsf_Core.h" #include "Nsf_Core.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
#include "Nes_Namco_Apu.h" #include "Nes_Namco_Apu.h"
#include "Nes_Vrc6_Apu.h" #include "Nes_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h" #include "Nes_Fme7_Apu.h"
#include "Nes_Fds_Apu.h" #include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h" #include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h" #include "Nes_Vrc7_Apu.h"
#endif #endif
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Nsf_Core::Nsf_Core() Nsf_Core::Nsf_Core()
{ {
fds = NULL; fds = NULL;
fme7 = NULL; fme7 = NULL;
mmc5 = NULL; mmc5 = NULL;
namco = NULL; namco = NULL;
vrc6 = NULL; vrc6 = NULL;
vrc7 = NULL; vrc7 = NULL;
} }
Nsf_Core::~Nsf_Core() Nsf_Core::~Nsf_Core()
{ {
unload(); unload();
} }
void Nsf_Core::unload() void Nsf_Core::unload()
{ {
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
delete fds; delete fds;
fds = NULL; fds = NULL;
delete fme7; delete fme7;
fme7 = NULL; fme7 = NULL;
delete namco; delete namco;
namco = NULL; namco = NULL;
delete mmc5; delete mmc5;
mmc5 = NULL; mmc5 = NULL;
delete vrc6; delete vrc6;
vrc6 = NULL; vrc6 = NULL;
delete vrc7; delete vrc7;
vrc7 = NULL; vrc7 = NULL;
#endif #endif
Nsf_Impl::unload(); Nsf_Impl::unload();
} }
void Nsf_Core::set_tempo( double t ) void Nsf_Core::set_tempo( double t )
{ {
set_play_period( (int) (header().play_period() / t) ); set_play_period( (int) (header().play_period() / t) );
nes_apu()->set_tempo( t ); nes_apu()->set_tempo( t );
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( fds ) if ( fds )
fds->set_tempo( t ); fds->set_tempo( t );
#endif #endif
} }
blargg_err_t Nsf_Core::post_load() blargg_err_t Nsf_Core::post_load()
{ {
int chip_flags = header().chip_flags; int chip_flags = header().chip_flags;
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( chip_flags & header_t::fds_mask ) if ( chip_flags & header_t::fds_mask )
CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu ); CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
if ( chip_flags & header_t::fme7_mask ) if ( chip_flags & header_t::fme7_mask )
CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu ); CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
if ( chip_flags & header_t::mmc5_mask ) if ( chip_flags & header_t::mmc5_mask )
CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu ); CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
if ( chip_flags & header_t::namco_mask ) if ( chip_flags & header_t::namco_mask )
CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu ); CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
if ( chip_flags & header_t::vrc6_mask ) if ( chip_flags & header_t::vrc6_mask )
CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu ); CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
if ( chip_flags & header_t::vrc7_mask ) if ( chip_flags & header_t::vrc7_mask )
{ {
#if NSF_EMU_NO_VRC7 #if NSF_EMU_NO_VRC7
chip_flags = ~chips_mask; // give warning rather than error chip_flags = ~chips_mask; // give warning rather than error
#else #else
CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu ); CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
RETURN_ERR( vrc7->init() ); RETURN_ERR( vrc7->init() );
#endif #endif
} }
#endif #endif
set_tempo( 1.0 ); set_tempo( 1.0 );
if ( chip_flags & ~chips_mask ) if ( chip_flags & ~chips_mask )
set_warning( "Uses unsupported audio expansion hardware" ); set_warning( "Uses unsupported audio expansion hardware" );
return Nsf_Impl::post_load(); return Nsf_Impl::post_load();
} }
int Nsf_Core::cpu_read( addr_t addr ) int Nsf_Core::cpu_read( addr_t addr )
{ {
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
{ {
if ( addr == Nes_Namco_Apu::data_reg_addr && namco ) if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
return namco->read_data(); return namco->read_data();
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds ) if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
return fds->read( time(), addr ); return fds->read( time(), addr );
int i = addr - 0x5C00; int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size && mmc5 ) if ( (unsigned) i < mmc5->exram_size && mmc5 )
return mmc5->exram [i]; return mmc5->exram [i];
int m = addr - 0x5205; int m = addr - 0x5205;
if ( (unsigned) m < 2 && mmc5 ) if ( (unsigned) m < 2 && mmc5 )
return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF; return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
} }
#endif #endif
return Nsf_Impl::cpu_read( addr ); return Nsf_Impl::cpu_read( addr );
} }
int Nsf_Core::unmapped_read( addr_t addr ) int Nsf_Core::unmapped_read( addr_t addr )
{ {
switch ( addr ) switch ( addr )
{ {
case 0x2002: case 0x2002:
case 0x4016: case 0x4016:
case 0x4017: case 0x4017:
return addr >> 8; return addr >> 8;
} }
return Nsf_Impl::unmapped_read( addr ); return Nsf_Impl::unmapped_read( addr );
} }
void Nsf_Core::cpu_write( addr_t addr, int data ) void Nsf_Core::cpu_write( addr_t addr, int data )
{ {
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
{ {
if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds ) if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
{ {
fds->write( time(), addr, data ); fds->write( time(), addr, data );
return; return;
} }
if ( namco ) if ( namco )
{ {
if ( addr == namco->addr_reg_addr ) if ( addr == namco->addr_reg_addr )
{ {
namco->write_addr( data ); namco->write_addr( data );
return; return;
} }
if ( addr == namco->data_reg_addr ) if ( addr == namco->data_reg_addr )
{ {
namco->write_data( time(), data ); namco->write_data( time(), data );
return; return;
} }
} }
if ( vrc6 ) if ( vrc6 )
{ {
int reg = addr & (vrc6->addr_step - 1); int reg = addr & (vrc6->addr_step - 1);
int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step; int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count ) if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
{ {
vrc6->write_osc( time(), osc, reg, data ); vrc6->write_osc( time(), osc, reg, data );
return; return;
} }
} }
if ( addr >= fme7->latch_addr && fme7 ) if ( addr >= fme7->latch_addr && fme7 )
{ {
switch ( addr & fme7->addr_mask ) switch ( addr & fme7->addr_mask )
{ {
case Nes_Fme7_Apu::latch_addr: case Nes_Fme7_Apu::latch_addr:
fme7->write_latch( data ); fme7->write_latch( data );
return; return;
case Nes_Fme7_Apu::data_addr: case Nes_Fme7_Apu::data_addr:
fme7->write_data( time(), data ); fme7->write_data( time(), data );
return; return;
} }
} }
if ( mmc5 ) if ( mmc5 )
{ {
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size ) if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
{ {
mmc5->write_register( time(), addr, data ); mmc5->write_register( time(), addr, data );
return; return;
} }
int m = addr - 0x5205; int m = addr - 0x5205;
if ( (unsigned) m < 2 ) if ( (unsigned) m < 2 )
{ {
mmc5_mul [m] = data; mmc5_mul [m] = data;
return; return;
} }
int i = addr - 0x5C00; int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size ) if ( (unsigned) i < mmc5->exram_size )
{ {
mmc5->exram [i] = data; mmc5->exram [i] = data;
return; return;
} }
} }
if ( vrc7 ) if ( vrc7 )
{ {
if ( addr == 0x9010 ) if ( addr == 0x9010 )
{ {
vrc7->write_reg( data ); vrc7->write_reg( data );
return; return;
} }
if ( (unsigned) (addr - 0x9028) <= 0x08 ) if ( (unsigned) (addr - 0x9028) <= 0x08 )
{ {
vrc7->write_data( time(), data ); vrc7->write_data( time(), data );
return; return;
} }
} }
} }
#endif #endif
return Nsf_Impl::cpu_write( addr, data ); return Nsf_Impl::cpu_write( addr, data );
} }
void Nsf_Core::unmapped_write( addr_t addr, int data ) void Nsf_Core::unmapped_write( addr_t addr, int data )
{ {
switch ( addr ) switch ( addr )
{ {
case 0x8000: // some write to $8000 and $8001 repeatedly case 0x8000: // some write to $8000 and $8001 repeatedly
case 0x8001: case 0x8001:
case 0x4800: // probably namco sound mistakenly turned on in MCK case 0x4800: // probably namco sound mistakenly turned on in MCK
case 0xF800: case 0xF800:
case 0xFFF8: // memory mapper? case 0xFFF8: // memory mapper?
return; return;
} }
if ( mmc5 && addr == 0x5115 ) return; if ( mmc5 && addr == 0x5115 ) return;
// FDS memory // FDS memory
if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return; if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;
Nsf_Impl::unmapped_write( addr, data ); Nsf_Impl::unmapped_write( addr, data );
} }
blargg_err_t Nsf_Core::start_track( int track ) blargg_err_t Nsf_Core::start_track( int track )
{ {
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( mmc5 ) if ( mmc5 )
{ {
mmc5_mul [0] = 0; mmc5_mul [0] = 0;
mmc5_mul [1] = 0; mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size ); memset( mmc5->exram, 0, mmc5->exram_size );
} }
#endif #endif
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( fds ) fds ->reset(); if ( fds ) fds ->reset();
if ( fme7 ) fme7 ->reset(); if ( fme7 ) fme7 ->reset();
if ( mmc5 ) mmc5 ->reset(); if ( mmc5 ) mmc5 ->reset();
if ( namco ) namco->reset(); if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset(); if ( vrc6 ) vrc6 ->reset();
if ( vrc7 ) vrc7 ->reset(); if ( vrc7 ) vrc7 ->reset();
#endif #endif
return Nsf_Impl::start_track( track ); return Nsf_Impl::start_track( track );
} }
void Nsf_Core::end_frame( time_t end ) void Nsf_Core::end_frame( time_t end )
{ {
Nsf_Impl::end_frame( end ); Nsf_Impl::end_frame( end );
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( fds ) fds ->end_frame( end ); if ( fds ) fds ->end_frame( end );
if ( fme7 ) fme7 ->end_frame( end ); if ( fme7 ) fme7 ->end_frame( end );
if ( mmc5 ) mmc5 ->end_frame( end ); if ( mmc5 ) mmc5 ->end_frame( end );
if ( namco ) namco->end_frame( end ); if ( namco ) namco->end_frame( end );
if ( vrc6 ) vrc6 ->end_frame( end ); if ( vrc6 ) vrc6 ->end_frame( end );
if ( vrc7 ) vrc7 ->end_frame( end ); if ( vrc7 ) vrc7 ->end_frame( end );
#endif #endif
} }

View file

@ -1,68 +1,68 @@
// Loads NSF file and emulates CPU and sound chips // Loads NSF file and emulates CPU and sound chips
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef NSF_CORE_H #ifndef NSF_CORE_H
#define NSF_CORE_H #define NSF_CORE_H
#include "Nsf_Impl.h" #include "Nsf_Impl.h"
class Nes_Namco_Apu; class Nes_Namco_Apu;
class Nes_Vrc6_Apu; class Nes_Vrc6_Apu;
class Nes_Fme7_Apu; class Nes_Fme7_Apu;
class Nes_Mmc5_Apu; class Nes_Mmc5_Apu;
class Nes_Vrc7_Apu; class Nes_Vrc7_Apu;
class Nes_Fds_Apu; class Nes_Fds_Apu;
class Nsf_Core : public Nsf_Impl { class Nsf_Core : public Nsf_Impl {
public: public:
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing. // Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
// Loading a file resets tempo to 1.0. // Loading a file resets tempo to 1.0.
void set_tempo( double ); void set_tempo( double );
// Pointer to sound chip, or NULL if not used by current file. // Pointer to sound chip, or NULL if not used by current file.
// Must be assigned to a Blip_Buffer to get any sound. // Must be assigned to a Blip_Buffer to get any sound.
Nes_Fds_Apu * fds_apu () { return fds; } Nes_Fds_Apu * fds_apu () { return fds; }
Nes_Fme7_Apu * fme7_apu () { return fme7; } Nes_Fme7_Apu * fme7_apu () { return fme7; }
Nes_Mmc5_Apu * mmc5_apu () { return mmc5; } Nes_Mmc5_Apu * mmc5_apu () { return mmc5; }
Nes_Namco_Apu* namco_apu() { return namco; } Nes_Namco_Apu* namco_apu() { return namco; }
Nes_Vrc6_Apu * vrc6_apu () { return vrc6; } Nes_Vrc6_Apu * vrc6_apu () { return vrc6; }
Nes_Vrc7_Apu * vrc7_apu () { return vrc7; } Nes_Vrc7_Apu * vrc7_apu () { return vrc7; }
// Mask for which chips are supported // Mask for which chips are supported
#if NSF_EMU_APU_ONLY #if NSF_EMU_APU_ONLY
enum { chips_mask = 0 }; enum { chips_mask = 0 };
#else #else
enum { chips_mask = header_t::all_mask }; enum { chips_mask = header_t::all_mask };
#endif #endif
protected: protected:
virtual int unmapped_read( addr_t ); virtual int unmapped_read( addr_t );
virtual void unmapped_write( addr_t, int data ); virtual void unmapped_write( addr_t, int data );
// Implementation // Implementation
public: public:
Nsf_Core(); Nsf_Core();
~Nsf_Core(); ~Nsf_Core();
virtual void unload(); virtual void unload();
virtual blargg_err_t start_track( int ); virtual blargg_err_t start_track( int );
virtual void end_frame( time_t ); virtual void end_frame( time_t );
protected: protected:
virtual blargg_err_t post_load(); virtual blargg_err_t post_load();
virtual int cpu_read( addr_t ); virtual int cpu_read( addr_t );
virtual void cpu_write( addr_t, int ); virtual void cpu_write( addr_t, int );
private: private:
byte mmc5_mul [2]; byte mmc5_mul [2];
Nes_Fds_Apu* fds; Nes_Fds_Apu* fds;
Nes_Fme7_Apu* fme7; Nes_Fme7_Apu* fme7;
Nes_Mmc5_Apu* mmc5; Nes_Mmc5_Apu* mmc5;
Nes_Namco_Apu* namco; Nes_Namco_Apu* namco;
Nes_Vrc6_Apu* vrc6; Nes_Vrc6_Apu* vrc6;
Nes_Vrc7_Apu* vrc7; Nes_Vrc7_Apu* vrc7;
}; };
#endif #endif

View file

@ -1,116 +1,116 @@
// Normal CPU for NSF emulator // Normal CPU for NSF emulator
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Nsf_Impl.h" #include "Nsf_Impl.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#ifdef BLARGG_DEBUG_H #ifdef BLARGG_DEBUG_H
//#define CPU_LOG_START 1000000 //#define CPU_LOG_START 1000000
//#include "nes_cpu_log.h" //#include "nes_cpu_log.h"
#undef LOG_MEM #undef LOG_MEM
#endif #endif
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#ifndef LOG_MEM #ifndef LOG_MEM
#define LOG_MEM( addr, str, data ) data #define LOG_MEM( addr, str, data ) data
#endif #endif
int Nsf_Impl::read_mem( addr_t addr ) int Nsf_Impl::read_mem( addr_t addr )
{ {
int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around
if ( addr & 0xE000 ) if ( addr & 0xE000 )
{ {
result = *cpu.get_code( addr ); result = *cpu.get_code( addr );
if ( addr < sram_addr ) if ( addr < sram_addr )
{ {
if ( addr == apu.status_addr ) if ( addr == apu.status_addr )
result = apu.read_status( time() ); result = apu.read_status( time() );
else else
result = cpu_read( addr ); result = cpu_read( addr );
} }
} }
return LOG_MEM( addr, ">", result ); return LOG_MEM( addr, ">", result );
} }
void Nsf_Impl::write_mem( addr_t addr, int data ) void Nsf_Impl::write_mem( addr_t addr, int data )
{ {
(void) LOG_MEM( addr, "<", data ); (void) LOG_MEM( addr, "<", data );
int offset = addr - sram_addr; int offset = addr - sram_addr;
if ( (unsigned) offset < sram_size ) if ( (unsigned) offset < sram_size )
{ {
sram() [offset] = data; sram() [offset] = data;
} }
else else
{ {
// after sram because CPU handles most low_ram accesses internally already // after sram because CPU handles most low_ram accesses internally already
int temp = addr & (low_ram_size-1); // also handles wrap-around int temp = addr & (low_ram_size-1); // also handles wrap-around
if ( !(addr & 0xE000) ) if ( !(addr & 0xE000) )
{ {
low_ram [temp] = data; low_ram [temp] = data;
} }
else else
{ {
int bank = addr - banks_addr; int bank = addr - banks_addr;
if ( (unsigned) bank < bank_count ) if ( (unsigned) bank < bank_count )
{ {
write_bank( bank, data ); write_bank( bank, data );
} }
else if ( (unsigned) (addr - apu.io_addr) < apu.io_size ) else if ( (unsigned) (addr - apu.io_addr) < apu.io_size )
{ {
apu.write_register( time(), addr, data ); apu.write_register( time(), addr, data );
} }
else else
{ {
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
// 0x8000-0xDFFF is writable // 0x8000-0xDFFF is writable
int i = addr - 0x8000; int i = addr - 0x8000;
if ( (unsigned) i < fdsram_size && fds_enabled() ) if ( (unsigned) i < fdsram_size && fds_enabled() )
fdsram() [i] = data; fdsram() [i] = data;
else else
#endif #endif
cpu_write( addr, data ); cpu_write( addr, data );
} }
} }
} }
} }
#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] )) #define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] ))
#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data )) #define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data ))
#define CAN_WRITE_FAST( addr ) (addr < low_ram_size) #define CAN_WRITE_FAST( addr ) (addr < low_ram_size)
#define WRITE_FAST WRITE_LOW #define WRITE_FAST WRITE_LOW
// addr < 0x2000 || addr >= 0x8000 // addr < 0x2000 || addr >= 0x8000
#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000) #define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000)
#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) )) #define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) ))
#define READ_MEM( addr ) read_mem( addr ) #define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data ) #define WRITE_MEM( addr, data ) write_mem( addr, data )
#define CPU cpu #define CPU cpu
#define CPU_BEGIN \ #define CPU_BEGIN \
bool Nsf_Impl::run_cpu_until( time_t end )\ bool Nsf_Impl::run_cpu_until( time_t end )\
{\ {\
cpu.set_end_time( end );\ cpu.set_end_time( end );\
if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\ if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\
{ {
#include "Nes_Cpu_run.h" #include "Nes_Cpu_run.h"
} }
return cpu.time_past_end() < 0; return cpu.time_past_end() < 0;
} }

View file

@ -21,14 +21,14 @@ FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80, 0,0,0,0,0,0,0,0 }; Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80, 0,0,0,0,0,0,0,0 };
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 }; Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 };
Nsf_Emu::Nsf_Emu() Nsf_Emu::Nsf_Emu()
{ {
set_type( gme_nsf_type ); set_type( gme_nsf_type );
set_silence_lookahead( 6 ); set_silence_lookahead( 6 );
set_gain( 1.4 ); set_gain( 1.4 );

View file

@ -1,55 +1,55 @@
// Nintendo NES/Famicom NSF music file emulator // Nintendo NES/Famicom NSF music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef NSF_EMU_H #ifndef NSF_EMU_H
#define NSF_EMU_H #define NSF_EMU_H
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Nsf_Core.h" #include "Nsf_Core.h"
void hash_nsf_file( Nsf_Core::header_t const& h, unsigned char const* data, int data_size, Music_Emu::Hash_Function& out ); void hash_nsf_file( Nsf_Core::header_t const& h, unsigned char const* data, int data_size, Music_Emu::Hash_Function& out );
class Nsf_Emu : public Classic_Emu { class Nsf_Emu : public Classic_Emu {
public: public:
// Equalizer profiles for US NES and Japanese Famicom // Equalizer profiles for US NES and Japanese Famicom
static equalizer_t const nes_eq; static equalizer_t const nes_eq;
static equalizer_t const famicom_eq; static equalizer_t const famicom_eq;
// NSF file header (see Nsf_Impl.h) // NSF file header (see Nsf_Impl.h)
typedef Nsf_Core::header_t header_t; typedef Nsf_Core::header_t header_t;
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return core_.header(); } header_t const& header() const { return core_.header(); }
blargg_err_t hash_( Hash_Function& ) const; blargg_err_t hash_( Hash_Function& ) const;
static gme_type_t static_type() { return gme_nsf_type; } static gme_type_t static_type() { return gme_nsf_type; }
Nsf_Core& core() { return core_; } Nsf_Core& core() { return core_; }
public: public:
Nsf_Emu(); Nsf_Emu();
~Nsf_Emu(); ~Nsf_Emu();
virtual void unload(); virtual void unload();
protected: protected:
virtual blargg_err_t track_info_( track_info_t*, int track ) const; virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int ); virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int ); virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double ); virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& ); virtual void update_eq( blip_eq_t const& );
private: private:
enum { max_voices = 32 }; enum { max_voices = 32 };
const char* voice_names_ [32]; const char* voice_names_ [32];
int voice_types_ [32]; int voice_types_ [32];
int voice_count_; int voice_count_;
Nsf_Core core_; Nsf_Core core_;
blargg_err_t init_sound(); blargg_err_t init_sound();
void append_voices( const char* const names [], int const types [], int count ); void append_voices( const char* const names [], int const types [], int count );
}; };
#endif #endif

View file

@ -1,328 +1,328 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Nsf_Impl.h" #include "Nsf_Impl.h"
#include "blargg_endian.h" #include "blargg_endian.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
// number of frames until play interrupts init // number of frames until play interrupts init
int const initial_play_delay = 7; // KikiKaikai needed this to work int const initial_play_delay = 7; // KikiKaikai needed this to work
int const bank_size = 0x1000; int const bank_size = 0x1000;
int const rom_addr = 0x8000; int const rom_addr = 0x8000;
int Nsf_Impl::read_code( addr_t addr ) const int Nsf_Impl::read_code( addr_t addr ) const
{ {
return *cpu.get_code( addr ); return *cpu.get_code( addr );
} }
int Nsf_Impl::pcm_read( void* self, int addr ) int Nsf_Impl::pcm_read( void* self, int addr )
{ {
return STATIC_CAST(Nsf_Impl*,self)->read_code( addr ); return STATIC_CAST(Nsf_Impl*,self)->read_code( addr );
} }
Nsf_Impl::Nsf_Impl() : rom( bank_size ), enable_w4011( true ) Nsf_Impl::Nsf_Impl() : rom( bank_size ), enable_w4011( true )
{ {
apu.dmc_reader( pcm_read, this ); apu.dmc_reader( pcm_read, this );
assert( offsetof (header_t,unused [4]) == header_t::size ); assert( offsetof (header_t,unused [4]) == header_t::size );
} }
void Nsf_Impl::unload() void Nsf_Impl::unload()
{ {
rom.clear(); rom.clear();
high_ram.clear(); high_ram.clear();
Gme_Loader::unload(); Gme_Loader::unload();
} }
Nsf_Impl::~Nsf_Impl() { unload(); } Nsf_Impl::~Nsf_Impl() { unload(); }
bool nsf_header_t::valid_tag() const bool nsf_header_t::valid_tag() const
{ {
return 0 == memcmp( tag, "NESM\x1A", 5 ); return 0 == memcmp( tag, "NESM\x1A", 5 );
} }
double nsf_header_t::clock_rate() const double nsf_header_t::clock_rate() const
{ {
return pal_only() ? 1662607.125 : 1789772.727272727; return pal_only() ? 1662607.125 : 1789772.727272727;
} }
int nsf_header_t::play_period() const int nsf_header_t::play_period() const
{ {
// NTSC // NTSC
int clocks = 29780; int clocks = 29780;
int value = 0x411A; int value = 0x411A;
byte const* rate_ptr = ntsc_speed; byte const* rate_ptr = ntsc_speed;
// PAL // PAL
if ( pal_only() ) if ( pal_only() )
{ {
clocks = 33247; clocks = 33247;
value = 0x4E20; value = 0x4E20;
rate_ptr = pal_speed; rate_ptr = pal_speed;
} }
// Default rate // Default rate
int rate = get_le16( rate_ptr ); int rate = get_le16( rate_ptr );
if ( rate == 0 ) if ( rate == 0 )
rate = value; rate = value;
// Custom rate // Custom rate
if ( rate != value ) if ( rate != value )
clocks = (int) (rate * clock_rate() * (1.0/1000000.0)); clocks = (int) (rate * clock_rate() * (1.0/1000000.0));
return clocks; return clocks;
} }
// Gets address, given pointer to it in file header. If zero, returns rom_addr. // Gets address, given pointer to it in file header. If zero, returns rom_addr.
Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] ) Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] )
{ {
addr_t addr = get_le16( in ); addr_t addr = get_le16( in );
if ( addr == 0 ) if ( addr == 0 )
addr = rom_addr; addr = rom_addr;
return addr; return addr;
} }
blargg_err_t Nsf_Impl::load_( Data_Reader& in ) blargg_err_t Nsf_Impl::load_( Data_Reader& in )
{ {
// pad ROM data with 0 // pad ROM data with 0
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) ); RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
if ( !header_.valid_tag() ) if ( !header_.valid_tag() )
return blargg_err_file_type; return blargg_err_file_type;
RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) ); RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) );
addr_t load_addr = get_addr( header_.load_addr ); addr_t load_addr = get_addr( header_.load_addr );
if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) ) if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) )
set_warning( "Load address is too low" ); set_warning( "Load address is too low" );
rom.set_addr( load_addr % bank_size ); rom.set_addr( load_addr % bank_size );
if ( header_.vers != 1 ) if ( header_.vers != 1 )
set_warning( "Unknown file version" ); set_warning( "Unknown file version" );
set_play_period( header_.play_period() ); set_play_period( header_.play_period() );
return blargg_ok; return blargg_ok;
} }
void Nsf_Impl::write_bank( int bank, int data ) void Nsf_Impl::write_bank( int bank, int data )
{ {
// Find bank in ROM // Find bank in ROM
int offset = rom.mask_addr( data * bank_size ); int offset = rom.mask_addr( data * bank_size );
if ( offset >= rom.size() ) if ( offset >= rom.size() )
special_event( "invalid bank" ); special_event( "invalid bank" );
void const* rom_data = rom.at_addr( offset ); void const* rom_data = rom.at_addr( offset );
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
if ( bank < bank_count - fds_banks && fds_enabled() ) if ( bank < bank_count - fds_banks && fds_enabled() )
{ {
// TODO: FDS bank switching is kind of hacky, might need to // TODO: FDS bank switching is kind of hacky, might need to
// treat ROM as RAM so changes won't get lost when switching. // treat ROM as RAM so changes won't get lost when switching.
byte* out = sram(); byte* out = sram();
if ( bank >= fds_banks ) if ( bank >= fds_banks )
{ {
out = fdsram(); out = fdsram();
bank -= fds_banks; bank -= fds_banks;
} }
memcpy( &out [bank * bank_size], rom_data, bank_size ); memcpy( &out [bank * bank_size], rom_data, bank_size );
return; return;
} }
#endif #endif
if ( bank >= fds_banks ) if ( bank >= fds_banks )
cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data ); cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data );
} }
void Nsf_Impl::map_memory() void Nsf_Impl::map_memory()
{ {
// Map standard things // Map standard things
cpu.reset( unmapped_code() ); cpu.reset( unmapped_code() );
cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times
cpu.map_code( sram_addr, sram_size, sram() ); cpu.map_code( sram_addr, sram_size, sram() );
// Determine initial banks // Determine initial banks
byte banks [bank_count]; byte banks [bank_count];
static byte const zero_banks [sizeof header_.banks] = { 0 }; static byte const zero_banks [sizeof header_.banks] = { 0 };
if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) ) if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) )
{ {
banks [0] = header_.banks [6]; banks [0] = header_.banks [6];
banks [1] = header_.banks [7]; banks [1] = header_.banks [7];
memcpy( banks + fds_banks, header_.banks, sizeof header_.banks ); memcpy( banks + fds_banks, header_.banks, sizeof header_.banks );
} }
else else
{ {
// No initial banks, so assign them based on load_addr // No initial banks, so assign them based on load_addr
int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size; int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size;
unsigned total_banks = rom.size() / bank_size; unsigned total_banks = rom.size() / bank_size;
for ( int i = bank_count; --i >= 0; ) for ( int i = bank_count; --i >= 0; )
{ {
int bank = i - first_bank; int bank = i - first_bank;
if ( (unsigned) bank >= total_banks ) if ( (unsigned) bank >= total_banks )
bank = 0; bank = 0;
banks [i] = bank; banks [i] = bank;
} }
} }
// Map banks // Map banks
for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i ) for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i )
write_bank( i, banks [i] ); write_bank( i, banks [i] );
// Map FDS RAM // Map FDS RAM
if ( fds_enabled() ) if ( fds_enabled() )
cpu.map_code( rom_addr, fdsram_size, fdsram() ); cpu.map_code( rom_addr, fdsram_size, fdsram() );
} }
inline void Nsf_Impl::push_byte( int b ) inline void Nsf_Impl::push_byte( int b )
{ {
low_ram [0x100 + cpu.r.sp--] = b; low_ram [0x100 + cpu.r.sp--] = b;
} }
// Jumps to routine, given pointer to address in file header. Pushes idle_addr // Jumps to routine, given pointer to address in file header. Pushes idle_addr
// as return address, NOT old PC. // as return address, NOT old PC.
void Nsf_Impl::jsr_then_stop( byte const addr [] ) void Nsf_Impl::jsr_then_stop( byte const addr [] )
{ {
cpu.r.pc = get_addr( addr ); cpu.r.pc = get_addr( addr );
push_byte( (idle_addr - 1) >> 8 ); push_byte( (idle_addr - 1) >> 8 );
push_byte( (idle_addr - 1) ); push_byte( (idle_addr - 1) );
} }
blargg_err_t Nsf_Impl::start_track( int track ) blargg_err_t Nsf_Impl::start_track( int track )
{ {
int speed_flags = 0; int speed_flags = 0;
#if NSF_EMU_EXTRA_FLAGS #if NSF_EMU_EXTRA_FLAGS
speed_flags = header().speed_flags; speed_flags = header().speed_flags;
#endif #endif
apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 ); apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 );
apu.enable_w4011_( enable_w4011 ); apu.enable_w4011_( enable_w4011 );
apu.write_register( 0, 0x4015, 0x0F ); apu.write_register( 0, 0x4015, 0x0F );
apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 ); apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 );
// Clear memory // Clear memory
memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size ); memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size );
memset( low_ram, 0, low_ram_size ); memset( low_ram, 0, low_ram_size );
memset( sram(), 0, sram_size ); memset( sram(), 0, sram_size );
map_memory(); map_memory();
// Arrange time of first call to play routine // Arrange time of first call to play routine
play_extra = 0; play_extra = 0;
next_play = play_period; next_play = play_period;
play_delay = initial_play_delay; play_delay = initial_play_delay;
saved_state.pc = idle_addr; saved_state.pc = idle_addr;
// Setup for call to init routine // Setup for call to init routine
cpu.r.a = track; cpu.r.a = track;
cpu.r.x = header_.pal_only(); cpu.r.x = header_.pal_only();
cpu.r.sp = 0xFF; cpu.r.sp = 0xFF;
jsr_then_stop( header_.init_addr ); jsr_then_stop( header_.init_addr );
if ( cpu.r.pc < get_addr( header_.load_addr ) ) if ( cpu.r.pc < get_addr( header_.load_addr ) )
set_warning( "Init address < load address" ); set_warning( "Init address < load address" );
return blargg_ok; return blargg_ok;
} }
void Nsf_Impl::unmapped_write( addr_t addr, int data ) void Nsf_Impl::unmapped_write( addr_t addr, int data )
{ {
dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data ); dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data );
} }
int Nsf_Impl::unmapped_read( addr_t addr ) int Nsf_Impl::unmapped_read( addr_t addr )
{ {
dprintf( "Unmapped read $%04X\n", (int) addr ); dprintf( "Unmapped read $%04X\n", (int) addr );
return addr >> 8; return addr >> 8;
} }
void Nsf_Impl::special_event( const char str [] ) void Nsf_Impl::special_event( const char str [] )
{ {
dprintf( "%s\n", str ); dprintf( "%s\n", str );
} }
void Nsf_Impl::run_once( time_t end ) void Nsf_Impl::run_once( time_t end )
{ {
// Emulate until next play call if possible // Emulate until next play call if possible
if ( run_cpu_until( min( next_play, end ) ) ) if ( run_cpu_until( min( next_play, end ) ) )
{ {
// Halt instruction encountered // Halt instruction encountered
if ( cpu.r.pc != idle_addr ) if ( cpu.r.pc != idle_addr )
{ {
special_event( "illegal instruction" ); special_event( "illegal instruction" );
cpu.count_error(); cpu.count_error();
cpu.set_time( cpu.end_time() ); cpu.set_time( cpu.end_time() );
return; return;
} }
// Init/play routine returned // Init/play routine returned
play_delay = 1; // play can now be called regularly play_delay = 1; // play can now be called regularly
if ( saved_state.pc == idle_addr ) if ( saved_state.pc == idle_addr )
{ {
// nothing to run // nothing to run
time_t t = cpu.end_time(); time_t t = cpu.end_time();
if ( cpu.time() < t ) if ( cpu.time() < t )
cpu.set_time( t ); cpu.set_time( t );
} }
else else
{ {
// continue init routine that was interrupted by play routine // continue init routine that was interrupted by play routine
cpu.r = saved_state; cpu.r = saved_state;
saved_state.pc = idle_addr; saved_state.pc = idle_addr;
} }
} }
if ( time() >= next_play ) if ( time() >= next_play )
{ {
// Calculate time of next call to play routine // Calculate time of next call to play routine
play_extra ^= 1; // extra clock every other call play_extra ^= 1; // extra clock every other call
next_play += play_period + play_extra; next_play += play_period + play_extra;
// Call routine if ready // Call routine if ready
if ( play_delay && !--play_delay ) if ( play_delay && !--play_delay )
{ {
// Save state if init routine is still running // Save state if init routine is still running
if ( cpu.r.pc != idle_addr ) if ( cpu.r.pc != idle_addr )
{ {
check( saved_state.pc == idle_addr ); check( saved_state.pc == idle_addr );
saved_state = cpu.r; saved_state = cpu.r;
special_event( "play called during init" ); special_event( "play called during init" );
} }
jsr_then_stop( header_.play_addr ); jsr_then_stop( header_.play_addr );
} }
} }
} }
void Nsf_Impl::run_until( time_t end ) void Nsf_Impl::run_until( time_t end )
{ {
while ( time() < end ) while ( time() < end )
run_once( end ); run_once( end );
} }
void Nsf_Impl::end_frame( time_t end ) void Nsf_Impl::end_frame( time_t end )
{ {
if ( time() < end ) if ( time() < end )
run_until( end ); run_until( end );
cpu.adjust_time( -end ); cpu.adjust_time( -end );
// Localize to new time frame // Localize to new time frame
next_play -= end; next_play -= end;
check( next_play >= 0 ); check( next_play >= 0 );
if ( next_play < 0 ) if ( next_play < 0 )
next_play = 0; next_play = 0;
apu.end_frame( end ); apu.end_frame( end );
} }

View file

@ -1,194 +1,194 @@
// Loads NSF file and emulates CPU and RAM, no sound chips // Loads NSF file and emulates CPU and RAM, no sound chips
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef NSF_IMPL_H #ifndef NSF_IMPL_H
#define NSF_IMPL_H #define NSF_IMPL_H
#include "Gme_Loader.h" #include "Gme_Loader.h"
#include "Nes_Cpu.h" #include "Nes_Cpu.h"
#include "Rom_Data.h" #include "Rom_Data.h"
#include "Nes_Apu.h" #include "Nes_Apu.h"
// NSF file header // NSF file header
struct nsf_header_t struct nsf_header_t
{ {
typedef unsigned char byte; typedef unsigned char byte;
enum { size = 0x80 }; enum { size = 0x80 };
char tag [ 5]; char tag [ 5];
byte vers; byte vers;
byte track_count; byte track_count;
byte first_track; byte first_track;
byte load_addr [ 2]; byte load_addr [ 2];
byte init_addr [ 2]; byte init_addr [ 2];
byte play_addr [ 2]; byte play_addr [ 2];
char game [32]; // NOT null-terminated if 32 chars in length char game [32]; // NOT null-terminated if 32 chars in length
char author [32]; char author [32];
char copyright [32]; char copyright [32];
byte ntsc_speed [ 2]; byte ntsc_speed [ 2];
byte banks [ 8]; byte banks [ 8];
byte pal_speed [ 2]; byte pal_speed [ 2];
byte speed_flags; byte speed_flags;
byte chip_flags; byte chip_flags;
byte unused [ 4]; byte unused [ 4];
// Sound chip masks // Sound chip masks
enum { enum {
vrc6_mask = 1 << 0, vrc6_mask = 1 << 0,
vrc7_mask = 1 << 1, vrc7_mask = 1 << 1,
fds_mask = 1 << 2, fds_mask = 1 << 2,
mmc5_mask = 1 << 3, mmc5_mask = 1 << 3,
namco_mask = 1 << 4, namco_mask = 1 << 4,
fme7_mask = 1 << 5, fme7_mask = 1 << 5,
all_mask = (1 << 6) - 1 all_mask = (1 << 6) - 1
}; };
// True if header has proper NSF file signature // True if header has proper NSF file signature
bool valid_tag() const; bool valid_tag() const;
// True if file supports only PAL speed // True if file supports only PAL speed
bool pal_only() const { return (speed_flags & 3) == 1; } bool pal_only() const { return (speed_flags & 3) == 1; }
// Clocks per second // Clocks per second
double clock_rate() const; double clock_rate() const;
// Clocks between calls to play routine // Clocks between calls to play routine
int play_period() const; int play_period() const;
}; };
/* Loads NSF file into memory, then emulates CPU, RAM, and ROM. /* Loads NSF file into memory, then emulates CPU, RAM, and ROM.
Non-memory accesses are routed through cpu_read() and cpu_write(). */ Non-memory accesses are routed through cpu_read() and cpu_write(). */
class Nsf_Impl : public Gme_Loader { class Nsf_Impl : public Gme_Loader {
public: public:
// Sound chip // Sound chip
Nes_Apu* nes_apu() { return &apu; } Nes_Apu* nes_apu() { return &apu; }
// Starts track, where 0 is the first // Starts track, where 0 is the first
virtual blargg_err_t start_track( int ); virtual blargg_err_t start_track( int );
// Emulates to at least time t, then begins new time frame at // Emulates to at least time t, then begins new time frame at
// time t. Might emulate a few clocks extra, so after returning, // time t. Might emulate a few clocks extra, so after returning,
// time() may not be zero. // time() may not be zero.
typedef int time_t; // clock count typedef int time_t; // clock count
virtual void end_frame( time_t n ); virtual void end_frame( time_t n );
// Finer control // Finer control
// Header for currently loaded file // Header for currently loaded file
typedef nsf_header_t header_t; typedef nsf_header_t header_t;
header_t const& header() const { return header_; } header_t const& header() const { return header_; }
// Sets clocks between calls to play routine to p + 1/2 clock // Sets clocks between calls to play routine to p + 1/2 clock
void set_play_period( int p ) { play_period = p; } void set_play_period( int p ) { play_period = p; }
// Time play routine will next be called // Time play routine will next be called
time_t play_time() const { return next_play; } time_t play_time() const { return next_play; }
// Emulates to at least time t. Might emulate a few clocks extra. // Emulates to at least time t. Might emulate a few clocks extra.
virtual void run_until( time_t t ); virtual void run_until( time_t t );
// Time emulated to // Time emulated to
time_t time() const { return cpu.time(); } time_t time() const { return cpu.time(); }
void enable_w4011_(bool enable = true) { enable_w4011 = enable; } void enable_w4011_(bool enable = true) { enable_w4011 = enable; }
Rom_Data const& rom_() const { return rom; } Rom_Data const& rom_() const { return rom; }
protected: protected:
// Nsf_Core use // Nsf_Core use
typedef int addr_t; typedef int addr_t;
// Called for unmapped accesses. Default just prints info if debugging. // Called for unmapped accesses. Default just prints info if debugging.
virtual void unmapped_write( addr_t, int data ); virtual void unmapped_write( addr_t, int data );
virtual int unmapped_read( addr_t ); virtual int unmapped_read( addr_t );
// Override in derived class // Override in derived class
// Bank writes and RAM at 0-$7FF and $6000-$7FFF are handled internally // Bank writes and RAM at 0-$7FF and $6000-$7FFF are handled internally
virtual int cpu_read( addr_t a ) { return unmapped_read( a ); } virtual int cpu_read( addr_t a ) { return unmapped_read( a ); }
virtual void cpu_write( addr_t a, int data ){ unmapped_write( a, data ); } virtual void cpu_write( addr_t a, int data ){ unmapped_write( a, data ); }
// Reads byte as CPU would when executing code. Only works for RAM/ROM, // Reads byte as CPU would when executing code. Only works for RAM/ROM,
// NOT I/O like sound chips. // NOT I/O like sound chips.
int read_code( addr_t addr ) const; int read_code( addr_t addr ) const;
// Debugger services // Debugger services
enum { mem_size = 0x10000 }; enum { mem_size = 0x10000 };
// CPU sits here when waiting for next call to play routine // CPU sits here when waiting for next call to play routine
enum { idle_addr = 0x5FF6 }; enum { idle_addr = 0x5FF6 };
Nes_Cpu cpu; Nes_Cpu cpu;
// Runs CPU to at least time t and returns false, or returns true // Runs CPU to at least time t and returns false, or returns true
// if it encounters illegal instruction (halt). // if it encounters illegal instruction (halt).
virtual bool run_cpu_until( time_t t ); virtual bool run_cpu_until( time_t t );
// CPU calls through to these to access memory (except instructions) // CPU calls through to these to access memory (except instructions)
int read_mem( addr_t ); int read_mem( addr_t );
void write_mem( addr_t, int ); void write_mem( addr_t, int );
// Address of play routine // Address of play routine
addr_t play_addr() const { return get_addr( header_.play_addr ); } addr_t play_addr() const { return get_addr( header_.play_addr ); }
// Same as run_until, except emulation stops for any event (routine returned, // Same as run_until, except emulation stops for any event (routine returned,
// play routine called, illegal instruction). // play routine called, illegal instruction).
void run_once( time_t ); void run_once( time_t );
// Make a note of event // Make a note of event
virtual void special_event( const char str [] ); virtual void special_event( const char str [] );
// Implementation // Implementation
public: public:
Nsf_Impl(); Nsf_Impl();
~Nsf_Impl(); ~Nsf_Impl();
protected: protected:
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual void unload(); virtual void unload();
private: private:
enum { low_ram_size = 0x800 }; enum { low_ram_size = 0x800 };
enum { fdsram_size = 0x6000 }; enum { fdsram_size = 0x6000 };
enum { sram_size = 0x2000 }; enum { sram_size = 0x2000 };
enum { unmapped_size= Nes_Cpu::page_size + 8 }; enum { unmapped_size= Nes_Cpu::page_size + 8 };
enum { fds_banks = 2 }; enum { fds_banks = 2 };
enum { bank_count = fds_banks + 8 }; enum { bank_count = fds_banks + 8 };
enum { banks_addr = idle_addr }; enum { banks_addr = idle_addr };
enum { sram_addr = 0x6000 }; enum { sram_addr = 0x6000 };
blargg_vector<byte> high_ram; blargg_vector<byte> high_ram;
Rom_Data rom; Rom_Data rom;
// Play routine timing // Play routine timing
time_t next_play; time_t next_play;
time_t play_period; time_t play_period;
int play_extra; int play_extra;
int play_delay; int play_delay;
bool enable_w4011; bool enable_w4011;
Nes_Cpu::registers_t saved_state; // of interrupted init routine Nes_Cpu::registers_t saved_state; // of interrupted init routine
// Large objects after others // Large objects after others
header_t header_; header_t header_;
Nes_Apu apu; Nes_Apu apu;
byte low_ram [low_ram_size]; byte low_ram [low_ram_size];
// Larger RAM areas allocated separately // Larger RAM areas allocated separately
enum { fdsram_offset = sram_size + unmapped_size }; enum { fdsram_offset = sram_size + unmapped_size };
byte* sram() { return high_ram.begin(); } byte* sram() { return high_ram.begin(); }
byte* unmapped_code() { return &high_ram [sram_size]; } byte* unmapped_code() { return &high_ram [sram_size]; }
byte* fdsram() { return &high_ram [fdsram_offset]; } byte* fdsram() { return &high_ram [fdsram_offset]; }
int fds_enabled() const { return header_.chip_flags & header_t::fds_mask; } int fds_enabled() const { return header_.chip_flags & header_t::fds_mask; }
void map_memory(); void map_memory();
void write_bank( int index, int data ); void write_bank( int index, int data );
void jsr_then_stop( byte const addr [] ); void jsr_then_stop( byte const addr [] );
void push_byte( int ); void push_byte( int );
static addr_t get_addr( byte const [] ); static addr_t get_addr( byte const [] );
static int pcm_read( void*, int ); static int pcm_read( void*, int );
}; };
#endif #endif

View file

@ -1,74 +1,74 @@
// Nintendo NES/Famicom NSFE music file emulator // Nintendo NES/Famicom NSFE music file emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef NSFE_EMU_H #ifndef NSFE_EMU_H
#define NSFE_EMU_H #define NSFE_EMU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Nsf_Emu.h" #include "Nsf_Emu.h"
class Nsfe_Emu; class Nsfe_Emu;
// Allows reading info from NSFE file without creating emulator // Allows reading info from NSFE file without creating emulator
class Nsfe_Info { class Nsfe_Info {
public: public:
blargg_err_t load( Data_Reader&, Nsfe_Emu* ); blargg_err_t load( Data_Reader&, Nsfe_Emu* );
struct info_t : Nsf_Emu::header_t struct info_t : Nsf_Emu::header_t
{ {
char game [256]; char game [256];
char author [256]; char author [256];
char copyright [256]; char copyright [256];
char dumper [256]; char dumper [256];
} info; } info;
blargg_vector<unsigned char> data; blargg_vector<unsigned char> data;
void disable_playlist( bool = true ); void disable_playlist( bool = true );
blargg_err_t track_info_( track_info_t* out, int track ) const; blargg_err_t track_info_( track_info_t* out, int track ) const;
int remap_track( int i ) const; int remap_track( int i ) const;
void unload(); void unload();
// Implementation // Implementation
public: public:
Nsfe_Info(); Nsfe_Info();
~Nsfe_Info(); ~Nsfe_Info();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
private: private:
blargg_vector<char> track_name_data; blargg_vector<char> track_name_data;
blargg_vector<const char*> track_names; blargg_vector<const char*> track_names;
blargg_vector<unsigned char> playlist; blargg_vector<unsigned char> playlist;
blargg_vector<char [4]> track_times; blargg_vector<char [4]> track_times;
int actual_track_count_; int actual_track_count_;
bool playlist_disabled; bool playlist_disabled;
}; };
class Nsfe_Emu : public Nsf_Emu { class Nsfe_Emu : public Nsf_Emu {
public: public:
static gme_type_t static_type() { return gme_nsfe_type; } static gme_type_t static_type() { return gme_nsfe_type; }
struct header_t { char tag [4]; }; struct header_t { char tag [4]; };
// Implementation // Implementation
public: public:
Nsfe_Emu(); Nsfe_Emu();
~Nsfe_Emu(); ~Nsfe_Emu();
virtual void unload(); virtual void unload();
protected: protected:
virtual blargg_err_t load_( Data_Reader& ); virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t track_info_( track_info_t*, int track ) const; virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t start_track_( int ); virtual blargg_err_t start_track_( int );
virtual void clear_playlist_(); virtual void clear_playlist_();
private: private:
Nsfe_Info info; Nsfe_Info info;
void disable_playlist_( bool b ); void disable_playlist_( bool b );
friend class Nsfe_Info; friend class Nsfe_Info;
}; };
#endif #endif

View file

@ -1,71 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Okim6258_Emu.h"
#include "okim6258.h"
Okim6258_Emu::Okim6258_Emu() { chip = 0; }
Okim6258_Emu::~Okim6258_Emu()
{
if ( chip ) device_stop_okim6258( chip );
}
int Okim6258_Emu::set_rate( int clock, int divider, int adpcm_type, int output_12bits )
{
if ( chip )
{
device_stop_okim6258( chip );
chip = 0;
}
chip = device_start_okim6258( clock, divider, adpcm_type, output_12bits );
if ( !chip )
return 0;
reset();
return okim6258_get_vclk( chip );
}
void Okim6258_Emu::reset()
{
device_reset_okim6258( chip );
}
void Okim6258_Emu::write( int addr, int data )
{
okim6258_write( chip, addr, data );
}
int Okim6258_Emu::get_clock()
{
return okim6258_get_vclk( chip );
}
void Okim6258_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
okim6258_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,32 +0,0 @@
// OKIM6258 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef OKIM6258_EMU_H
#define OKIM6258_EMU_H
class Okim6258_Emu {
void* chip;
public:
Okim6258_Emu();
~Okim6258_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock, int divider, int adpcm_type, int output_12bits );
// Resets to power-up state
void reset();
// Returns current sample rate of the chip
int get_clock();
// Writes data to addr
void write( int addr, int data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,77 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Okim6295_Emu.h"
#include "okim6295.h"
Okim6295_Emu::Okim6295_Emu() { chip = 0; }
Okim6295_Emu::~Okim6295_Emu()
{
if ( chip ) device_stop_okim6295( chip );
}
int Okim6295_Emu::set_rate( int clock_rate )
{
if ( chip )
{
device_stop_okim6295( chip );
chip = 0;
}
chip = device_start_okim6295( clock_rate );
if ( !chip )
return 0;
reset();
return (clock_rate & 0x7FFFFFFF) / ((clock_rate & 0x80000000) ? 132 : 165);
}
void Okim6295_Emu::reset()
{
device_reset_okim6295( chip );
okim6295_set_mute_mask( chip, 0 );
}
void Okim6295_Emu::write( int addr, int data )
{
okim6295_w( chip, addr, data );
}
void Okim6295_Emu::write_rom( int size, int start, int length, void * data )
{
okim6295_write_rom( chip, size, start, length, (const UINT8 *) data );
}
void Okim6295_Emu::mute_voices( int mask )
{
okim6295_set_mute_mask( chip, mask );
}
void Okim6295_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
okim6295_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,36 +0,0 @@
// OKIM6295 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef OKIM6295_EMU_H
#define OKIM6295_EMU_H
class Okim6295_Emu {
void* chip;
public:
Okim6295_Emu();
~Okim6295_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock_rate );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 4 };
void mute_voices( int mask );
// Writes data to addr
void write( int addr, int data );
// Scales ROM size, then writes length bytes from data at start offset
void write_rom( int size, int start, int length, void * data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -2,8 +2,16 @@
#include "blargg_source.h" #include "blargg_source.h"
#include "ym2413.h" extern "C" {
#include "fmopl.h" #include "../vgmplay/chips/mamedef.h"
#include "../vgmplay/chips/emu2413.h"
#include "../vgmplay/chips/fmopl.h"
}
static unsigned char vrc7_inst[(16 + 3) * 8] =
{
#include "../vgmplay/chips/vrc7tone.h"
};
Opl_Apu::Opl_Apu() { opl = 0; opl_memory = 0; } Opl_Apu::Opl_Apu() { opl = 0; opl_memory = 0; }
@ -20,26 +28,29 @@ blargg_err_t Opl_Apu::init( long clock, long rate, blip_time_t period, type_t ty
case type_opll: case type_opll:
case type_msxmusic: case type_msxmusic:
case type_smsfmunit: case type_smsfmunit:
opl = ym2413_init( clock, rate, 0 ); opl = OPLL_new( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
OPLL_SetChipMode( (OPLL *) opl, 0);
break; break;
case type_vrc7: case type_vrc7:
opl = ym2413_init( clock, rate, 1 ); opl = OPLL_new( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
OPLL_SetChipMode((OPLL *) opl, 1 );
OPLL_setPatch((OPLL *) opl, vrc7_inst);
break; break;
case type_opl: case type_opl:
opl = ym3526_init( clock, rate ); opl = ym3526_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
break; break;
case type_msxaudio: case type_msxaudio:
//logfile = fopen("c:\\temp\\msxaudio.log", "wb"); //logfile = fopen("c:\\temp\\msxaudio.log", "wb");
opl = y8950_init( clock, rate ); opl = y8950_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
opl_memory = malloc( 32768 ); opl_memory = malloc( 32768 );
y8950_set_delta_t_memory( opl, opl_memory, 32768 ); y8950_set_delta_t_memory( opl, opl_memory, 32768 );
break; break;
case type_opl2: case type_opl2:
opl = ym3812_init( clock, rate ); opl = ym3812_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
break; break;
} }
reset(); reset();
@ -56,7 +67,7 @@ Opl_Apu::~Opl_Apu()
case type_msxmusic: case type_msxmusic:
case type_smsfmunit: case type_smsfmunit:
case type_vrc7: case type_vrc7:
ym2413_shutdown( opl ); OPLL_delete( (OPLL *) opl );
break; break;
case type_opl: case type_opl:
@ -88,7 +99,7 @@ void Opl_Apu::reset()
case type_msxmusic: case type_msxmusic:
case type_smsfmunit: case type_smsfmunit:
case type_vrc7: case type_vrc7:
ym2413_reset_chip( opl ); OPLL_reset( (OPLL *) opl );
break; break;
case type_opl: case type_opl:
@ -114,8 +125,8 @@ void Opl_Apu::write_data( blip_time_t time, int data )
case type_msxmusic: case type_msxmusic:
case type_smsfmunit: case type_smsfmunit:
case type_vrc7: case type_vrc7:
ym2413_write( opl, 0, addr ); OPLL_writeIO( (OPLL *) opl, 0, addr );
ym2413_write( opl, 1, data ); OPLL_writeIO( (OPLL *) opl, 1, data );
break; break;
case type_opl: case type_opl:
@ -149,7 +160,7 @@ int Opl_Apu::read( blip_time_t time, int port )
case type_msxmusic: case type_msxmusic:
case type_smsfmunit: case type_smsfmunit:
case type_vrc7: case type_vrc7:
return ym2413_read( opl, port ); return port ? 0xFF : 0;
case type_opl: case type_opl:
return ym3526_read( opl, port ); return ym3526_read( opl, port );
@ -192,15 +203,15 @@ void Opl_Apu::run_until( blip_time_t end_time )
case type_smsfmunit: case type_smsfmunit:
case type_vrc7: case type_vrc7:
{ {
SAMP bufMO[ 1024 ]; e_int32 bufMO[ 1024 ];
SAMP bufRO[ 1024 ]; e_int32 bufRO[ 1024 ];
SAMP * buffers[2] = { bufMO, bufRO }; e_int32 * buffers[2] = { bufMO, bufRO };
while ( count > 0 ) while ( count > 0 )
{ {
unsigned todo = count; unsigned todo = count;
if ( todo > 1024 ) todo = 1024; if ( todo > 1024 ) todo = 1024;
ym2413_update_one( opl, buffers, todo ); OPLL_calc_stereo( (OPLL *) opl, buffers, todo, -1 );
if ( output_ ) if ( output_ )
{ {
@ -229,7 +240,9 @@ void Opl_Apu::run_until( blip_time_t end_time )
case type_msxaudio: case type_msxaudio:
case type_opl2: case type_opl2:
{ {
OPLSAMPLE buffer[ 1024 ]; OPLSAMPLE bufL[ 1024 ];
OPLSAMPLE bufR[ 1024 ];
OPLSAMPLE* buffers[2] = {bufL, bufR};
while ( count > 0 ) while ( count > 0 )
{ {
@ -237,18 +250,18 @@ void Opl_Apu::run_until( blip_time_t end_time )
if ( todo > 1024 ) todo = 1024; if ( todo > 1024 ) todo = 1024;
switch (type_) switch (type_)
{ {
case type_opl: ym3526_update_one( opl, buffer, todo ); break; case type_opl: ym3526_update_one( opl, buffers, todo ); break;
case type_msxaudio: y8950_update_one( opl, buffer, todo ); break; case type_msxaudio: y8950_update_one( opl, buffers, todo ); break;
case type_opl2: ym3812_update_one( opl, buffer, todo ); break; case type_opl2: ym3812_update_one( opl, buffers, todo ); break;
default: break; default: break;
} }
if ( output_ ) if ( output_ )
{ {
int last_amp = this->last_amp; int last_amp = this->last_amp;
for ( unsigned i = 0; i < todo; i++ ) for ( unsigned i = 0; i < todo; i++ )
{ {
int amp = buffer [i]; int amp = bufL [i] + bufR [i];
int delta = amp - last_amp; int delta = amp - last_amp;
if ( delta ) if ( delta )
{ {

View file

@ -1,66 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Pwm_Emu.h"
#include "pwm.h"
Pwm_Emu::Pwm_Emu() { chip = 0; }
Pwm_Emu::~Pwm_Emu()
{
if ( chip ) device_stop_pwm( chip );
}
int Pwm_Emu::set_rate( int clock )
{
if ( chip )
{
device_stop_pwm( chip );
chip = 0;
}
chip = device_start_pwm( clock );
if ( !chip )
return 1;
reset();
return 0;
}
void Pwm_Emu::reset()
{
device_reset_pwm( chip );
}
void Pwm_Emu::write( int channel, int data )
{
pwm_chn_w( chip, channel, data );
}
void Pwm_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
pwm_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,33 +0,0 @@
// PWM sound chip emulator interface
// Game_Music_Emu $vers
#ifndef PWM_EMU_H
#define PWM_EMU_H
class Pwm_Emu {
void* chip;
public:
Pwm_Emu();
~Pwm_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 24 };
void mute_voices( int mask );
// Writes data to channel
void write( int channel, int data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,83 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Qsound_Apu.h"
#include "qmix.h"
Qsound_Apu::Qsound_Apu() { chip = 0; rom = 0; rom_size = 0; sample_rate = 0; }
Qsound_Apu::~Qsound_Apu()
{
if ( chip ) free( chip );
if ( rom ) free( rom );
}
int Qsound_Apu::set_rate( int clock_rate )
{
if ( chip )
{
free( chip );
chip = 0;
}
chip = malloc( _qmix_get_state_size() );
if ( !chip )
return 0;
reset();
return clock_rate / 166;
}
void Qsound_Apu::set_sample_rate( int sample_rate )
{
this->sample_rate = sample_rate;
if ( chip ) _qmix_set_sample_rate( chip, sample_rate );
}
void Qsound_Apu::reset()
{
_qmix_clear_state( chip );
_qmix_set_sample_rate( chip, sample_rate );
if ( rom ) _qmix_set_sample_rom( chip, rom, rom_size );
}
void Qsound_Apu::write( int addr, int data )
{
_qmix_command( chip, addr, data );
}
void Qsound_Apu::write_rom( int size, int start, int length, void const* data )
{
if ( size > rom_size )
{
rom_size = size;
rom = realloc( rom, size );
}
if ( start > size ) start = size;
if ( start + length > size ) length = size - start;
memcpy( (uint8*)rom + start, data, length );
if ( chip ) _qmix_set_sample_rom( chip, rom, rom_size );
}
void Qsound_Apu::run( int pair_count, sample_t* out )
{
sint16 buf[ 1024 * 2 ];
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
_qmix_render( chip, buf, todo );
for (int i = 0; i < todo * 2; i++)
{
int output = buf [i];
output += out [0];
if ( (short)output != output ) output = 0x7FFF ^ ( output >> 31 );
out [0] = output;
out++;
}
pair_count -= todo;
}
}

View file

@ -1,36 +0,0 @@
// Capcom QSound sound chip emulator interface
// Game_Music_Emu $vers
#ifndef QSOUND_APU_H
#define QSOUND_APU_H
class Qsound_Apu {
void* chip;
void* rom;
int rom_size;
int sample_rate;
public:
Qsound_Apu();
~Qsound_Apu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock_rate );
void set_sample_rate( int sample_rate );
// Resets to power-up state
void reset();
// Writes data to addr
void write( int addr, int data );
// Scales ROM size, then writes length bytes from data at start offset
void write_rom( int size, int start, int length, void const* data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,79 +1,79 @@
// $package. http://www.slack.net/~ant/ // $package. http://www.slack.net/~ant/
#include "Resampler.h" #include "Resampler.h"
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you /* Copyright (C) 2004-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Resampler::Resampler() Resampler::Resampler()
{ {
write_pos = 0; write_pos = 0;
rate_ = 0; rate_ = 0;
} }
Resampler::~Resampler() { } Resampler::~Resampler() { }
void Resampler::clear() void Resampler::clear()
{ {
write_pos = 0; write_pos = 0;
clear_(); clear_();
} }
inline int Resampler::resample_wrapper( sample_t out [], int* out_size, inline int Resampler::resample_wrapper( sample_t out [], int* out_size,
sample_t const in [], int in_size ) sample_t const in [], int in_size )
{ {
assert( rate() ); assert( rate() );
sample_t* out_ = out; sample_t* out_ = out;
int result = resample_( &out_, out + *out_size, in, in_size ) - in; int result = resample_( &out_, out + *out_size, in, in_size ) - in;
assert( out_ <= out + *out_size ); assert( out_ <= out + *out_size );
assert( result <= in_size ); assert( result <= in_size );
*out_size = out_ - out; *out_size = out_ - out;
return result; return result;
} }
int Resampler::resample( sample_t out [], int out_size, sample_t const in [], int* in_size ) int Resampler::resample( sample_t out [], int out_size, sample_t const in [], int* in_size )
{ {
*in_size = resample_wrapper( out, &out_size, in, *in_size ); *in_size = resample_wrapper( out, &out_size, in, *in_size );
return out_size; return out_size;
} }
//// Buffering //// Buffering
blargg_err_t Resampler::resize_buffer( int new_size ) blargg_err_t Resampler::resize_buffer( int new_size )
{ {
RETURN_ERR( buf.resize( new_size ) ); RETURN_ERR( buf.resize( new_size ) );
clear(); clear();
return blargg_ok; return blargg_ok;
} }
int Resampler::skip_input( int count ) int Resampler::skip_input( int count )
{ {
write_pos -= count; write_pos -= count;
if ( write_pos < 0 ) // occurs when downsampling if ( write_pos < 0 ) // occurs when downsampling
{ {
count += write_pos; count += write_pos;
write_pos = 0; write_pos = 0;
} }
memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] ); memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] );
return count; return count;
} }
int Resampler::read( sample_t out [], int out_size ) int Resampler::read( sample_t out [], int out_size )
{ {
if ( out_size ) if ( out_size )
skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) ); skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) );
return out_size; return out_size;
} }

View file

@ -1,110 +1,110 @@
// Common interface for resamplers // Common interface for resamplers
// $package // $package
#ifndef RESAMPLER_H #ifndef RESAMPLER_H
#define RESAMPLER_H #define RESAMPLER_H
#include "blargg_common.h" #include "blargg_common.h"
class Resampler { class Resampler {
public: public:
virtual ~Resampler(); virtual ~Resampler();
// Sets input/output resampling ratio // Sets input/output resampling ratio
blargg_err_t set_rate( double ); blargg_err_t set_rate( double );
// Current input/output ratio // Current input/output ratio
double rate() const { return rate_; } double rate() const { return rate_; }
// Samples are 16-bit signed // Samples are 16-bit signed
typedef short sample_t; typedef short sample_t;
// One of two different buffering schemes can be used, as decided by the caller: // One of two different buffering schemes can be used, as decided by the caller:
// External buffering (caller provides input buffer) // External buffering (caller provides input buffer)
// Resamples in to at most n out samples and returns number of samples actually // Resamples in to at most n out samples and returns number of samples actually
// written. Sets *in_size to number of input samples that aren't needed anymore // written. Sets *in_size to number of input samples that aren't needed anymore
// and should be removed from input. // and should be removed from input.
int resample( sample_t out [], int n, sample_t const in [], int* in_size ); int resample( sample_t out [], int n, sample_t const in [], int* in_size );
// Internal buffering (resampler manages buffer) // Internal buffering (resampler manages buffer)
// Resizes input buffer to n samples, then clears it // Resizes input buffer to n samples, then clears it
blargg_err_t resize_buffer( int n ); blargg_err_t resize_buffer( int n );
// Clears input buffer // Clears input buffer
void clear(); void clear();
// Writes at most n samples to input buffer and returns number actually written. // Writes at most n samples to input buffer and returns number actually written.
// Result will be less than n if there isn't enough free space in buffer. // Result will be less than n if there isn't enough free space in buffer.
int write( sample_t const in [], int n ); int write( sample_t const in [], int n );
// Number of input samples in buffer // Number of input samples in buffer
int written() const { return write_pos; } int written() const { return write_pos; }
// Removes first n input samples from buffer, fewer if there aren't that many. // Removes first n input samples from buffer, fewer if there aren't that many.
// Returns number of samples actually removed. // Returns number of samples actually removed.
int skip_input( int n ); int skip_input( int n );
// Resamples input to at most n output samples. Returns number of samples // Resamples input to at most n output samples. Returns number of samples
// actually written to out. Result will be less than n if there aren't // actually written to out. Result will be less than n if there aren't
// enough input samples in buffer. // enough input samples in buffer.
int read( sample_t out [], int n ); int read( sample_t out [], int n );
// Direct writing to input buffer, instead of using write( in, n ) above // Direct writing to input buffer, instead of using write( in, n ) above
// Pointer to place to write input samples // Pointer to place to write input samples
sample_t* buffer() { return &buf [write_pos]; } sample_t* buffer() { return &buf [write_pos]; }
// Number of samples that can be written to buffer() // Number of samples that can be written to buffer()
int buffer_free() const { return buf.size() - write_pos; } int buffer_free() const { return buf.size() - write_pos; }
// Notifies resampler that n input samples have been written to buffer(). // Notifies resampler that n input samples have been written to buffer().
// N must not be greater than buffer_free(). // N must not be greater than buffer_free().
void write( int n ); void write( int n );
// Derived interface // Derived interface
protected: protected:
virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; ) virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; )
virtual void clear_() { } virtual void clear_() { }
// Resample as many available in samples as will fit within out_size and // Resample as many available in samples as will fit within out_size and
// return pointer past last input sample read and set *out just past // return pointer past last input sample read and set *out just past
// the last output sample. // the last output sample.
virtual sample_t const* resample_( sample_t** out, sample_t const* out_end, virtual sample_t const* resample_( sample_t** out, sample_t const* out_end,
sample_t const in [], int in_size ) BLARGG_PURE( { return in; } ) sample_t const in [], int in_size ) BLARGG_PURE( { return in; } )
// Implementation // Implementation
public: public:
Resampler(); Resampler();
private: private:
blargg_vector<sample_t> buf; blargg_vector<sample_t> buf;
int write_pos; int write_pos;
double rate_; double rate_;
int resample_wrapper( sample_t out [], int* out_size, int resample_wrapper( sample_t out [], int* out_size,
sample_t const in [], int in_size ); sample_t const in [], int in_size );
}; };
inline void Resampler::write( int count ) inline void Resampler::write( int count )
{ {
write_pos += count; write_pos += count;
assert( (unsigned) write_pos <= buf.size() ); assert( (unsigned) write_pos <= buf.size() );
} }
inline blargg_err_t Resampler::set_rate_( double r ) inline blargg_err_t Resampler::set_rate_( double r )
{ {
rate_ = r; rate_ = r;
return blargg_ok; return blargg_ok;
} }
inline blargg_err_t Resampler::set_rate( double r ) inline blargg_err_t Resampler::set_rate( double r )
{ {
return set_rate_( r ); return set_rate_( r );
} }
#endif #endif

View file

@ -1,82 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Rf5C164_Emu.h"
#include "scd_pcm.h"
Rf5C164_Emu::Rf5C164_Emu() { chip = 0; }
Rf5C164_Emu::~Rf5C164_Emu()
{
if ( chip ) device_stop_rf5c164( chip );
}
int Rf5C164_Emu::set_rate( int clock )
{
if ( chip )
{
device_stop_rf5c164( chip );
chip = 0;
}
chip = device_start_rf5c164( clock );
if ( !chip )
return 1;
reset();
return 0;
}
void Rf5C164_Emu::reset()
{
device_reset_rf5c164( chip );
rf5c164_set_mute_mask( chip, 0 );
}
void Rf5C164_Emu::write( int addr, int data )
{
rf5c164_w( chip, addr, data );
}
void Rf5C164_Emu::write_mem( int addr, int data )
{
rf5c164_mem_w( chip, addr, data );
}
void Rf5C164_Emu::write_ram( int start, int length, void * data )
{
rf5c164_write_ram( chip, start, length, (const UINT8 *) data );
}
void Rf5C164_Emu::mute_voices( int mask )
{
rf5c164_set_mute_mask( chip, mask );
}
void Rf5C164_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
rf5c164_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,39 +0,0 @@
// RF5C164 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef RF5C164_EMU_H
#define RF5C164_EMU_H
class Rf5C164_Emu {
void* chip;
public:
Rf5C164_Emu();
~Rf5C164_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate( int clock );
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 8 };
void mute_voices( int mask );
// Writes data to addr
void write( int addr, int data );
// Writes to memory
void write_mem( int addr, int data );
// Writes length bytes from data at start offset in RAM
void write_ram( int start, int length, void * data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,82 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Rf5C68_Emu.h"
#include "rf5c68.h"
Rf5C68_Emu::Rf5C68_Emu() { chip = 0; }
Rf5C68_Emu::~Rf5C68_Emu()
{
if ( chip ) device_stop_rf5c68( chip );
}
int Rf5C68_Emu::set_rate()
{
if ( chip )
{
device_stop_rf5c68( chip );
chip = 0;
}
chip = device_start_rf5c68();
if ( !chip )
return 1;
reset();
return 0;
}
void Rf5C68_Emu::reset()
{
device_reset_rf5c68( chip );
rf5c68_set_mute_mask( chip, 0 );
}
void Rf5C68_Emu::write( int addr, int data )
{
rf5c68_w( chip, addr, data );
}
void Rf5C68_Emu::write_mem( int addr, int data )
{
rf5c68_mem_w( chip, addr, data );
}
void Rf5C68_Emu::write_ram( int start, int length, void * data )
{
rf5c68_write_ram( chip, start, length, (const UINT8 *) data );
}
void Rf5C68_Emu::mute_voices( int mask )
{
rf5c68_set_mute_mask( chip, mask );
}
void Rf5C68_Emu::run( int pair_count, sample_t* out )
{
stream_sample_t bufL[ 1024 ];
stream_sample_t bufR[ 1024 ];
stream_sample_t * buffers[2] = { bufL, bufR };
while (pair_count > 0)
{
int todo = pair_count;
if (todo > 1024) todo = 1024;
rf5c68_update( chip, buffers, todo );
for (int i = 0; i < todo; i++)
{
int output_l = bufL [i];
int output_r = bufR [i];
output_l += out [0];
output_r += out [1];
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
out [0] = output_l;
out [1] = output_r;
out += 2;
}
pair_count -= todo;
}
}

View file

@ -1,39 +0,0 @@
// RF5C68 sound chip emulator interface
// Game_Music_Emu $vers
#ifndef RF5C68_EMU_H
#define RF5C68_EMU_H
class Rf5C68_Emu {
void* chip;
public:
Rf5C68_Emu();
~Rf5C68_Emu();
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
// if error.
int set_rate();
// Resets to power-up state
void reset();
// Mutes voice n if bit n (1 << n) of mask is set
enum { channel_count = 8 };
void mute_voices( int mask );
// Writes data to addr
void write( int addr, int data );
// Writes to memory
void write_mem( int addr, int data );
// Writes length bytes from data at start offset in RAM
void write_ram( int start, int length, void * data );
// Runs and writes pair_count*2 samples to output
typedef short sample_t;
enum { out_chan_count = 2 }; // stereo
void run( int pair_count, sample_t* out );
};
#endif

View file

@ -1,339 +1,339 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Sap_Apu.h" #include "Sap_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const max_frequency = 12000; // pure waves above this frequency are silenced int const max_frequency = 12000; // pure waves above this frequency are silenced
static void gen_poly( unsigned mask, int count, byte out [] ) static void gen_poly( unsigned mask, int count, byte out [] )
{ {
unsigned n = 1; unsigned n = 1;
do do
{ {
int bits = 0; int bits = 0;
int b = 0; int b = 0;
do do
{ {
// implemented using "Galios configuration" // implemented using "Galios configuration"
bits |= (n & 1) << b; bits |= (n & 1) << b;
n = (n >> 1) ^ (mask * (n & 1)); n = (n >> 1) ^ (mask * (n & 1));
} }
while ( b++ < 7 ); while ( b++ < 7 );
*out++ = bits; *out++ = bits;
} }
while ( --count ); while ( --count );
} }
// poly5 // poly5
int const poly5_len = (1 << 5) - 1; int const poly5_len = (1 << 5) - 1;
unsigned const poly5_mask = (1U << poly5_len) - 1; unsigned const poly5_mask = (1U << poly5_len) - 1;
unsigned const poly5 = 0x167C6EA1; unsigned const poly5 = 0x167C6EA1;
inline unsigned run_poly5( unsigned in, int shift ) inline unsigned run_poly5( unsigned in, int shift )
{ {
return (in << shift & poly5_mask) | (in >> (poly5_len - shift)); return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
} }
#define POLY_MASK( width, tap1, tap2 ) \ #define POLY_MASK( width, tap1, tap2 ) \
((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2))) ((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2)))
Sap_Apu_Impl::Sap_Apu_Impl() Sap_Apu_Impl::Sap_Apu_Impl()
{ {
gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 ); gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 ); gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 ); gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
if ( 0 ) // comment out to recauculate poly5 constant if ( 0 ) // comment out to recauculate poly5 constant
{ {
byte poly5 [4]; byte poly5 [4];
gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 ); gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 + unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 +
poly5 [1] * 0x100 + poly5 [0]; poly5 [1] * 0x100 + poly5 [0];
unsigned rev = n & 1; unsigned rev = n & 1;
for ( int i = 1; i < poly5_len; i++ ) for ( int i = 1; i < poly5_len; i++ )
rev |= (n >> i & 1) << (poly5_len - i); rev |= (n >> i & 1) << (poly5_len - i);
dprintf( "poly5: 0x%08lX\n", rev ); dprintf( "poly5: 0x%08lX\n", rev );
} }
} }
void Sap_Apu::set_output( Blip_Buffer* b ) void Sap_Apu::set_output( Blip_Buffer* b )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; ++i )
set_output( i, b ); set_output( i, b );
} }
Sap_Apu::Sap_Apu() Sap_Apu::Sap_Apu()
{ {
impl = NULL; impl = NULL;
set_output( NULL ); set_output( NULL );
} }
void Sap_Apu::reset( Sap_Apu_Impl* new_impl ) void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
{ {
impl = new_impl; impl = new_impl;
last_time = 0; last_time = 0;
poly5_pos = 0; poly5_pos = 0;
poly4_pos = 0; poly4_pos = 0;
polym_pos = 0; polym_pos = 0;
control = 0; control = 0;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
memset( &oscs [i], 0, offsetof (osc_t,output) ); memset( &oscs [i], 0, offsetof (osc_t,output) );
} }
inline void Sap_Apu::calc_periods() inline void Sap_Apu::calc_periods()
{ {
// 15/64 kHz clock // 15/64 kHz clock
int divider = 28; int divider = 28;
if ( this->control & 1 ) if ( this->control & 1 )
divider = 114; divider = 114;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
{ {
osc_t* const osc = &oscs [i]; osc_t* const osc = &oscs [i];
int const osc_reload = osc->regs [0]; // cache int const osc_reload = osc->regs [0]; // cache
int period = (osc_reload + 1) * divider; int period = (osc_reload + 1) * divider;
static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 }; static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
if ( this->control & fast_bits [i] ) if ( this->control & fast_bits [i] )
{ {
period = osc_reload + 4; period = osc_reload + 4;
if ( i & 1 ) if ( i & 1 )
{ {
period = osc_reload * 0x100 + osc [-1].regs [0] + 7; period = osc_reload * 0x100 + osc [-1].regs [0] + 7;
if ( !(this->control & fast_bits [i - 1]) ) if ( !(this->control & fast_bits [i - 1]) )
period = (period - 6) * divider; period = (period - 6) * divider;
if ( (osc [-1].regs [1] & 0x1F) > 0x10 ) if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
dprintf( "Use of slave channel in 16-bit mode not supported\n" ); dprintf( "Use of slave channel in 16-bit mode not supported\n" );
} }
} }
osc->period = period; osc->period = period;
} }
} }
void Sap_Apu::run_until( blip_time_t end_time ) void Sap_Apu::run_until( blip_time_t end_time )
{ {
calc_periods(); calc_periods();
Sap_Apu_Impl* const impl = this->impl; // cache Sap_Apu_Impl* const impl = this->impl; // cache
// 17/9-bit poly selection // 17/9-bit poly selection
byte const* polym = impl->poly17; byte const* polym = impl->poly17;
int polym_len = poly17_len; int polym_len = poly17_len;
if ( this->control & 0x80 ) if ( this->control & 0x80 )
{ {
polym_len = poly9_len; polym_len = poly9_len;
polym = impl->poly9; polym = impl->poly9;
} }
polym_pos %= polym_len; polym_pos %= polym_len;
for ( int i = 0; i < osc_count; i++ ) for ( int i = 0; i < osc_count; i++ )
{ {
osc_t* const osc = &oscs [i]; osc_t* const osc = &oscs [i];
blip_time_t time = last_time + osc->delay; blip_time_t time = last_time + osc->delay;
blip_time_t const period = osc->period; blip_time_t const period = osc->period;
// output // output
Blip_Buffer* output = osc->output; Blip_Buffer* output = osc->output;
if ( output ) if ( output )
{ {
int const osc_control = osc->regs [1]; // cache int const osc_control = osc->regs [1]; // cache
int volume = (osc_control & 0x0F) * 2; int volume = (osc_control & 0x0F) * 2;
if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) ) ((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
{ {
if ( !(osc_control & 0x10) ) if ( !(osc_control & 0x10) )
volume >>= 1; // inaudible frequency = half volume volume >>= 1; // inaudible frequency = half volume
int delta = volume - osc->last_amp; int delta = volume - osc->last_amp;
if ( delta ) if ( delta )
{ {
osc->last_amp = volume; osc->last_amp = volume;
output->set_modified(); output->set_modified();
impl->synth.offset( last_time, delta, output ); impl->synth.offset( last_time, delta, output );
} }
// TODO: doesn't maintain high pass flip-flop (very minor issue) // TODO: doesn't maintain high pass flip-flop (very minor issue)
} }
else else
{ {
// high pass // high pass
static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 }; static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
blip_time_t period2 = 0; // unused if no high pass blip_time_t period2 = 0; // unused if no high pass
blip_time_t time2 = end_time; blip_time_t time2 = end_time;
if ( this->control & hipass_bits [i] ) if ( this->control & hipass_bits [i] )
{ {
period2 = osc [2].period; period2 = osc [2].period;
time2 = last_time + osc [2].delay; time2 = last_time + osc [2].delay;
if ( osc->invert ) if ( osc->invert )
{ {
// trick inner wave loop into inverting output // trick inner wave loop into inverting output
osc->last_amp -= volume; osc->last_amp -= volume;
volume = -volume; volume = -volume;
} }
} }
if ( time < end_time || time2 < end_time ) if ( time < end_time || time2 < end_time )
{ {
// poly source // poly source
static byte const poly1 [] = { 0x55, 0x55 }; // square wave static byte const poly1 [] = { 0x55, 0x55 }; // square wave
byte const* poly = poly1; byte const* poly = poly1;
int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
int poly_pos = osc->phase & 1; int poly_pos = osc->phase & 1;
int poly_inc = 1; int poly_inc = 1;
if ( !(osc_control & 0x20) ) if ( !(osc_control & 0x20) )
{ {
poly = polym; poly = polym;
poly_len = polym_len; poly_len = polym_len;
poly_pos = polym_pos; poly_pos = polym_pos;
if ( osc_control & 0x40 ) if ( osc_control & 0x40 )
{ {
poly = impl->poly4; poly = impl->poly4;
poly_len = poly4_len; poly_len = poly4_len;
poly_pos = poly4_pos; poly_pos = poly4_pos;
} }
poly_inc = period % poly_len; poly_inc = period % poly_len;
poly_pos = (poly_pos + osc->delay) % poly_len; poly_pos = (poly_pos + osc->delay) % poly_len;
} }
poly_inc -= poly_len; // allows more optimized inner loop below poly_inc -= poly_len; // allows more optimized inner loop below
// square/poly5 wave // square/poly5 wave
unsigned wave = poly5; unsigned wave = poly5;
check( poly5 & 1 ); // low bit is set for pure wave check( poly5 & 1 ); // low bit is set for pure wave
int poly5_inc = 0; int poly5_inc = 0;
if ( !(osc_control & 0x80) ) if ( !(osc_control & 0x80) )
{ {
wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len ); wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
poly5_inc = period % poly5_len; poly5_inc = period % poly5_len;
} }
output->set_modified(); output->set_modified();
// Run wave and high pass interleved with each catching up to the other. // Run wave and high pass interleved with each catching up to the other.
// Disabled high pass has no performance effect since inner wave loop // Disabled high pass has no performance effect since inner wave loop
// makes no compromise for high pass, and only runs once in that case. // makes no compromise for high pass, and only runs once in that case.
int osc_last_amp = osc->last_amp; int osc_last_amp = osc->last_amp;
do do
{ {
// run high pass // run high pass
if ( time2 < time ) if ( time2 < time )
{ {
int delta = -osc_last_amp; int delta = -osc_last_amp;
if ( volume < 0 ) if ( volume < 0 )
delta += volume; delta += volume;
if ( delta ) if ( delta )
{ {
osc_last_amp += delta - volume; osc_last_amp += delta - volume;
volume = -volume; volume = -volume;
impl->synth.offset( time2, delta, output ); impl->synth.offset( time2, delta, output );
} }
} }
while ( time2 <= time ) // must advance *past* time to avoid hang while ( time2 <= time ) // must advance *past* time to avoid hang
time2 += period2; time2 += period2;
// run wave // run wave
blip_time_t end = end_time; blip_time_t end = end_time;
if ( end > time2 ) if ( end > time2 )
end = time2; end = time2;
while ( time < end ) while ( time < end )
{ {
if ( wave & 1 ) if ( wave & 1 )
{ {
int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1); int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
if ( (poly_pos += poly_inc) < 0 ) if ( (poly_pos += poly_inc) < 0 )
poly_pos += poly_len; poly_pos += poly_len;
int delta = amp - osc_last_amp; int delta = amp - osc_last_amp;
if ( delta ) if ( delta )
{ {
osc_last_amp = amp; osc_last_amp = amp;
impl->synth.offset( time, delta, output ); impl->synth.offset( time, delta, output );
} }
} }
wave = run_poly5( wave, poly5_inc ); wave = run_poly5( wave, poly5_inc );
time += period; time += period;
} }
} }
while ( time < end_time || time2 < end_time ); while ( time < end_time || time2 < end_time );
osc->phase = poly_pos; osc->phase = poly_pos;
osc->last_amp = osc_last_amp; osc->last_amp = osc_last_amp;
} }
osc->invert = 0; osc->invert = 0;
if ( volume < 0 ) if ( volume < 0 )
{ {
// undo inversion trickery // undo inversion trickery
osc->last_amp -= volume; osc->last_amp -= volume;
osc->invert = 1; osc->invert = 1;
} }
} }
} }
// maintain divider // maintain divider
blip_time_t remain = end_time - time; blip_time_t remain = end_time - time;
if ( remain > 0 ) if ( remain > 0 )
{ {
int count = (remain + period - 1) / period; int count = (remain + period - 1) / period;
osc->phase ^= count; osc->phase ^= count;
time += count * period; time += count * period;
} }
osc->delay = time - end_time; osc->delay = time - end_time;
} }
// advance polies // advance polies
blip_time_t duration = end_time - last_time; blip_time_t duration = end_time - last_time;
last_time = end_time; last_time = end_time;
poly4_pos = (poly4_pos + duration) % poly4_len; poly4_pos = (poly4_pos + duration) % poly4_len;
poly5_pos = (poly5_pos + duration) % poly5_len; poly5_pos = (poly5_pos + duration) % poly5_len;
polym_pos += duration; // will get %'d on next call polym_pos += duration; // will get %'d on next call
} }
void Sap_Apu::write_data( blip_time_t time, int addr, int data ) void Sap_Apu::write_data( blip_time_t time, int addr, int data )
{ {
run_until( time ); run_until( time );
int i = (addr - 0xD200) >> 1; int i = (addr - 0xD200) >> 1;
if ( (unsigned) i < osc_count ) if ( (unsigned) i < osc_count )
{ {
oscs [i].regs [addr & 1] = data; oscs [i].regs [addr & 1] = data;
} }
else if ( addr == 0xD208 ) else if ( addr == 0xD208 )
{ {
control = data; control = data;
} }
else if ( addr == 0xD209 ) else if ( addr == 0xD209 )
{ {
oscs [0].delay = 0; oscs [0].delay = 0;
oscs [1].delay = 0; oscs [1].delay = 0;
oscs [2].delay = 0; oscs [2].delay = 0;
oscs [3].delay = 0; oscs [3].delay = 0;
} }
/* /*
// TODO: are polynomials reset in this case? // TODO: are polynomials reset in this case?
else if ( addr == 0xD20F ) else if ( addr == 0xD20F )
{ {
if ( (data & 3) == 0 ) if ( (data & 3) == 0 )
polym_pos = 0; polym_pos = 0;
} }
*/ */
} }
void Sap_Apu::end_frame( blip_time_t end_time ) void Sap_Apu::end_frame( blip_time_t end_time )
{ {
if ( end_time > last_time ) if ( end_time > last_time )
run_until( end_time ); run_until( end_time );
last_time -= end_time; last_time -= end_time;
assert( last_time >= 0 ); assert( last_time >= 0 );
} }

View file

@ -1,103 +1,103 @@
// Atari POKEY sound chip emulator // Atari POKEY sound chip emulator
// Game_Music_Emu $vers // Game_Music_Emu $vers
#ifndef SAP_APU_H #ifndef SAP_APU_H
#define SAP_APU_H #define SAP_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Sap_Apu_Impl; class Sap_Apu_Impl;
class Sap_Apu { class Sap_Apu {
public: public:
// Basics // Basics
// Sets buffer to generate sound into, or 0 to mute // Sets buffer to generate sound into, or 0 to mute
void set_output( Blip_Buffer* ); void set_output( Blip_Buffer* );
// Emulates to time t, then writes data to addr // Emulates to time t, then writes data to addr
void write_data( blip_time_t t, int addr, int data ); void write_data( blip_time_t t, int addr, int data );
// Emulates to time t, then subtracts t from the current time. // Emulates to time t, then subtracts t from the current time.
// OK if previous write call had time slightly after t. // OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); void end_frame( blip_time_t t );
// More features // More features
// Same as set_output(), but for a particular channel // Same as set_output(), but for a particular channel
enum { osc_count = 4 }; enum { osc_count = 4 };
void set_output( int index, Blip_Buffer* ); void set_output( int index, Blip_Buffer* );
// Resets sound chip and sets Sap_Apu_Impl // Resets sound chip and sets Sap_Apu_Impl
void reset( Sap_Apu_Impl* impl ); void reset( Sap_Apu_Impl* impl );
// Registers are at io_addr to io_addr+io_size-1 // Registers are at io_addr to io_addr+io_size-1
enum { io_addr = 0xD200 }; enum { io_addr = 0xD200 };
enum { io_size = 0x0A }; enum { io_size = 0x0A };
private: private:
// noncopyable // noncopyable
Sap_Apu( const Sap_Apu& ); Sap_Apu( const Sap_Apu& );
Sap_Apu& operator = ( const Sap_Apu& ); Sap_Apu& operator = ( const Sap_Apu& );
// Implementation // Implementation
public: public:
Sap_Apu(); Sap_Apu();
private: private:
struct osc_t struct osc_t
{ {
unsigned char regs [2]; unsigned char regs [2];
unsigned char phase; unsigned char phase;
unsigned char invert; unsigned char invert;
int last_amp; int last_amp;
blip_time_t delay; blip_time_t delay;
blip_time_t period; // always recalculated before use; here for convenience blip_time_t period; // always recalculated before use; here for convenience
Blip_Buffer* output; Blip_Buffer* output;
}; };
osc_t oscs [osc_count]; osc_t oscs [osc_count];
Sap_Apu_Impl* impl; Sap_Apu_Impl* impl;
blip_time_t last_time; blip_time_t last_time;
int poly5_pos; int poly5_pos;
int poly4_pos; int poly4_pos;
int polym_pos; int polym_pos;
int control; int control;
void calc_periods(); void calc_periods();
void run_until( blip_time_t ); void run_until( blip_time_t );
enum { poly4_len = (1 << 4) - 1 }; enum { poly4_len = (1 << 4) - 1 };
enum { poly9_len = (1 << 9) - 1 }; enum { poly9_len = (1 << 9) - 1 };
enum { poly17_len = (1 << 17) - 1 }; enum { poly17_len = (1 << 17) - 1 };
friend class Sap_Apu_Impl; friend class Sap_Apu_Impl;
}; };
// Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects // Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects
class Sap_Apu_Impl { class Sap_Apu_Impl {
public: public:
// Set treble with synth.treble_eq() // Set treble with synth.treble_eq()
Blip_Synth_Norm synth; Blip_Synth_Norm synth;
// Sets overall volume, where 1.0is normal // Sets overall volume, where 1.0is normal
void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); } void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); }
// Implementation // Implementation
public: public:
Sap_Apu_Impl(); Sap_Apu_Impl();
private: private:
BOOST::uint8_t poly4 [Sap_Apu::poly4_len /8 + 1]; BOOST::uint8_t poly4 [Sap_Apu::poly4_len /8 + 1];
BOOST::uint8_t poly9 [Sap_Apu::poly9_len /8 + 1]; BOOST::uint8_t poly9 [Sap_Apu::poly9_len /8 + 1];
BOOST::uint8_t poly17 [Sap_Apu::poly17_len/8 + 1]; BOOST::uint8_t poly17 [Sap_Apu::poly17_len/8 + 1];
friend class Sap_Apu; friend class Sap_Apu;
}; };
inline void Sap_Apu::set_output( int i, Blip_Buffer* b ) inline void Sap_Apu::set_output( int i, Blip_Buffer* b )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = b; oscs [i].output = b;
} }
#endif #endif

View file

@ -1,192 +1,192 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Sap_Core.h" #include "Sap_Core.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation, License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const idle_addr = 0xD2D2; int const idle_addr = 0xD2D2;
Sap_Core::Sap_Core() Sap_Core::Sap_Core()
{ {
set_tempo( 1 ); set_tempo( 1 );
} }
void Sap_Core::push( int b ) void Sap_Core::push( int b )
{ {
mem.ram [0x100 + cpu.r.sp--] = (byte) b; mem.ram [0x100 + cpu.r.sp--] = (byte) b;
} }
void Sap_Core::jsr_then_stop( addr_t addr ) void Sap_Core::jsr_then_stop( addr_t addr )
{ {
cpu.r.pc = addr; cpu.r.pc = addr;
// Some rips pop three bytes off stack before RTS. // Some rips pop three bytes off stack before RTS.
push( (idle_addr - 1) >> 8 ); push( (idle_addr - 1) >> 8 );
push( idle_addr - 1 ); push( idle_addr - 1 );
// 3 bytes so that RTI or RTS will jump to idle_addr. // 3 bytes so that RTI or RTS will jump to idle_addr.
// RTI will use the first two bytes as the address, 0xD2D2. // RTI will use the first two bytes as the address, 0xD2D2.
// RTS will use the last two bytes, 0xD2D1, which it internally increments. // RTS will use the last two bytes, 0xD2D1, which it internally increments.
push( (idle_addr - 1) >> 8 ); push( (idle_addr - 1) >> 8 );
push( (idle_addr - 1) >> 8 ); push( (idle_addr - 1) >> 8 );
push( idle_addr - 1 ); push( idle_addr - 1 );
} }
// Runs routine and allows it up to one second to return // Runs routine and allows it up to one second to return
void Sap_Core::run_routine( addr_t addr ) void Sap_Core::run_routine( addr_t addr )
{ {
jsr_then_stop( addr ); jsr_then_stop( addr );
run_cpu( lines_per_frame * base_scanline_period * 60 ); run_cpu( lines_per_frame * base_scanline_period * 60 );
check( cpu.r.pc == idle_addr ); check( cpu.r.pc == idle_addr );
check( cpu.r.sp >= 0xFF - 6 ); check( cpu.r.sp >= 0xFF - 6 );
} }
inline void Sap_Core::call_init( int track ) inline void Sap_Core::call_init( int track )
{ {
cpu.r.a = track; cpu.r.a = track;
switch ( info.type ) switch ( info.type )
{ {
case 'B': case 'B':
run_routine( info.init_addr ); run_routine( info.init_addr );
break; break;
case 'C': case 'C':
cpu.r.a = 0x70; cpu.r.a = 0x70;
cpu.r.x = info.music_addr&0xFF; cpu.r.x = info.music_addr&0xFF;
cpu.r.y = info.music_addr >> 8; cpu.r.y = info.music_addr >> 8;
run_routine( info.play_addr + 3 ); run_routine( info.play_addr + 3 );
cpu.r.a = 0; cpu.r.a = 0;
cpu.r.x = track; cpu.r.x = track;
run_routine( info.play_addr + 3 ); run_routine( info.play_addr + 3 );
break; break;
case 'D': case 'D':
check( info.fastplay == lines_per_frame ); check( info.fastplay == lines_per_frame );
jsr_then_stop( info.init_addr ); jsr_then_stop( info.init_addr );
break; break;
} }
} }
void Sap_Core::setup_ram() void Sap_Core::setup_ram()
{ {
memset( &mem, 0, sizeof mem ); memset( &mem, 0, sizeof mem );
ram() [idle_addr] = cpu.halt_opcode; ram() [idle_addr] = cpu.halt_opcode;
addr_t const irq_addr = idle_addr - 1; addr_t const irq_addr = idle_addr - 1;
ram() [irq_addr] = cpu.halt_opcode; ram() [irq_addr] = cpu.halt_opcode;
ram() [0xFFFE] = (byte) irq_addr; ram() [0xFFFE] = (byte) irq_addr;
ram() [0xFFFF] = irq_addr >> 8; ram() [0xFFFF] = irq_addr >> 8;
} }
blargg_err_t Sap_Core::start_track( int track, info_t const& new_info ) blargg_err_t Sap_Core::start_track( int track, info_t const& new_info )
{ {
info = new_info; info = new_info;
check( ram() [idle_addr] == cpu.halt_opcode ); check( ram() [idle_addr] == cpu.halt_opcode );
apu_ .reset( &apu_impl_ ); apu_ .reset( &apu_impl_ );
apu2_.reset( &apu_impl_ ); apu2_.reset( &apu_impl_ );
cpu.reset( ram() ); cpu.reset( ram() );
frame_start = 0; frame_start = 0;
next_play = play_period() * 4; next_play = play_period() * 4;
saved_state.pc = idle_addr; saved_state.pc = idle_addr;
time_mask = 0; // disables sound during init time_mask = 0; // disables sound during init
call_init( track ); call_init( track );
time_mask = ~0; time_mask = ~0;
return blargg_ok; return blargg_ok;
} }
blargg_err_t Sap_Core::run_until( time_t end ) blargg_err_t Sap_Core::run_until( time_t end )
{ {
while ( cpu.time() < end ) while ( cpu.time() < end )
{ {
time_t next = min( next_play, end ); time_t next = min( next_play, end );
if ( (run_cpu( next ) && cpu.r.pc != idle_addr) || cpu.error_count() ) if ( (run_cpu( next ) && cpu.r.pc != idle_addr) || cpu.error_count() )
// TODO: better error // TODO: better error
return BLARGG_ERR( BLARGG_ERR_GENERIC, "Emulation error (illegal instruction)" ); return BLARGG_ERR( BLARGG_ERR_GENERIC, "Emulation error (illegal instruction)" );
if ( cpu.r.pc == idle_addr ) if ( cpu.r.pc == idle_addr )
{ {
if ( saved_state.pc == idle_addr ) if ( saved_state.pc == idle_addr )
{ {
// no code to run until next play call // no code to run until next play call
cpu.set_time( next ); cpu.set_time( next );
} }
else else
{ {
// play had interrupted non-returning init, so restore registers // play had interrupted non-returning init, so restore registers
// init routine was running // init routine was running
check( cpu.r.sp == saved_state.sp - 3 ); check( cpu.r.sp == saved_state.sp - 3 );
cpu.r = saved_state; cpu.r = saved_state;
saved_state.pc = idle_addr; saved_state.pc = idle_addr;
} }
} }
if ( cpu.time() >= next_play ) if ( cpu.time() >= next_play )
{ {
next_play += play_period(); next_play += play_period();
if ( cpu.r.pc == idle_addr || info.type == 'D' ) if ( cpu.r.pc == idle_addr || info.type == 'D' )
{ {
// Save state if init routine is still running // Save state if init routine is still running
if ( cpu.r.pc != idle_addr ) if ( cpu.r.pc != idle_addr )
{ {
check( info.type == 'D' ); check( info.type == 'D' );
check( saved_state.pc == idle_addr ); check( saved_state.pc == idle_addr );
saved_state = cpu.r; saved_state = cpu.r;
} }
addr_t addr = info.play_addr; addr_t addr = info.play_addr;
if ( info.type == 'C' ) if ( info.type == 'C' )
addr += 6; addr += 6;
jsr_then_stop( addr ); jsr_then_stop( addr );
} }
else else
{ {
dprintf( "init/play hadn't returned before next play call\n" ); dprintf( "init/play hadn't returned before next play call\n" );
} }
} }
} }
return blargg_ok; return blargg_ok;
} }
blargg_err_t Sap_Core::end_frame( time_t end ) blargg_err_t Sap_Core::end_frame( time_t end )
{ {
RETURN_ERR( run_until( end ) ); RETURN_ERR( run_until( end ) );
cpu.adjust_time( -end ); cpu.adjust_time( -end );
time_t frame_time = lines_per_frame * scanline_period; time_t frame_time = lines_per_frame * scanline_period;
while ( frame_start < end ) while ( frame_start < end )
frame_start += frame_time; frame_start += frame_time;
frame_start -= end + frame_time; frame_start -= end + frame_time;
if ( (next_play -= end) < 0 ) if ( (next_play -= end) < 0 )
{ {
next_play = 0; next_play = 0;
check( false ); check( false );
} }
apu_.end_frame( end ); apu_.end_frame( end );
if ( info.stereo ) if ( info.stereo )
apu2_.end_frame( end ); apu2_.end_frame( end );
return blargg_ok; return blargg_ok;
} }

Some files were not shown because too many files have changed in this diff Show more