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

View file

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

View file

@ -1,190 +1,190 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Ay_Core.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
inline void Ay_Core::disable_beeper()
{
beeper_mask = 0;
last_beeper = 0;
}
Ay_Core::Ay_Core()
{
beeper_output = NULL;
disable_beeper();
}
Ay_Core::~Ay_Core() { }
void Ay_Core::set_beeper_output( Blip_Buffer* b )
{
beeper_output = b;
if ( b && !cpc_mode )
beeper_mask = 0x10;
else
disable_beeper();
}
void Ay_Core::start_track( registers_t const& r, addr_t play )
{
play_addr = play;
memset( mem_.padding1, 0xFF, sizeof mem_.padding1 );
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 );
memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh)
cpu.reset( mem_.padding1, mem_.padding1 );
cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram );
cpu.r = r;
beeper_delta = (int) (apu_.amp_range * 0.8);
last_beeper = 0;
next_play = play_period;
spectrum_mode = false;
cpc_mode = false;
cpc_latch = 0;
set_beeper_output( beeper_output );
apu_.reset();
// a few tunes rely on channels having tone enabled at the beginning
apu_.write_addr( 7 );
apu_.write_data( 0, 0x38 );
}
// Emulation
void Ay_Core::cpu_out_( time_t time, addr_t addr, int data )
{
// Spectrum
if ( !cpc_mode )
{
switch ( addr & 0xFEFF )
{
case 0xFEFD:
spectrum_mode = true;
apu_.write_addr( data );
return;
case 0xBEFD:
spectrum_mode = true;
apu_.write_data( time, data );
return;
}
}
// CPC
if ( !spectrum_mode )
{
switch ( addr >> 8 )
{
case 0xF6:
switch ( data & 0xC0 )
{
case 0xC0:
apu_.write_addr( cpc_latch );
goto enable_cpc;
case 0x80:
apu_.write_data( time, cpc_latch );
goto enable_cpc;
}
break;
case 0xF4:
cpc_latch = data;
goto enable_cpc;
}
}
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
return;
enable_cpc:
if ( !cpc_mode )
{
cpc_mode = true;
disable_beeper();
set_cpc_callback.f( set_cpc_callback.data );
}
}
int Ay_Core::cpu_in( addr_t addr )
{
// keyboard read and other things
if ( (addr & 0xFF) == 0xFE )
return 0xFF; // other values break some beeper tunes
dprintf( "Unmapped IN : $%04X\n", addr );
return 0xFF;
}
void Ay_Core::end_frame( time_t* end )
{
cpu.set_time( 0 );
// 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
// as much until mode is known.
if ( !(spectrum_mode | cpc_mode) )
*end /= 2;
while ( cpu.time() < *end )
{
run_cpu( min( *end, next_play ) );
if ( cpu.time() >= next_play )
{
// next frame
next_play += play_period;
if ( cpu.r.iff1 )
{
// interrupt enabled
if ( mem_.ram [cpu.r.pc] == 0x76 )
cpu.r.pc++; // advance past HALT instruction
cpu.r.iff1 = 0;
cpu.r.iff2 = 0;
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8);
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc);
// fixed interrupt
cpu.r.pc = 0x38;
cpu.adjust_time( 12 );
if ( cpu.r.im == 2 )
{
// vectored interrupt
addr_t addr = cpu.r.i * 0x100 + 0xFF;
cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr];
cpu.adjust_time( 6 );
}
}
}
}
// End time frame
*end = cpu.time();
next_play -= *end;
check( next_play >= 0 );
cpu.adjust_time( -*end );
apu_.end_frame( *end );
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Ay_Core.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
inline void Ay_Core::disable_beeper()
{
beeper_mask = 0;
last_beeper = 0;
}
Ay_Core::Ay_Core()
{
beeper_output = NULL;
disable_beeper();
}
Ay_Core::~Ay_Core() { }
void Ay_Core::set_beeper_output( Blip_Buffer* b )
{
beeper_output = b;
if ( b && !cpc_mode )
beeper_mask = 0x10;
else
disable_beeper();
}
void Ay_Core::start_track( registers_t const& r, addr_t play )
{
play_addr = play;
memset( mem_.padding1, 0xFF, sizeof mem_.padding1 );
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 );
memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh)
cpu.reset( mem_.padding1, mem_.padding1 );
cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram );
cpu.r = r;
beeper_delta = (int) (apu_.amp_range * 0.8);
last_beeper = 0;
next_play = play_period;
spectrum_mode = false;
cpc_mode = false;
cpc_latch = 0;
set_beeper_output( beeper_output );
apu_.reset();
// a few tunes rely on channels having tone enabled at the beginning
apu_.write_addr( 7 );
apu_.write_data( 0, 0x38 );
}
// Emulation
void Ay_Core::cpu_out_( time_t time, addr_t addr, int data )
{
// Spectrum
if ( !cpc_mode )
{
switch ( addr & 0xFEFF )
{
case 0xFEFD:
spectrum_mode = true;
apu_.write_addr( data );
return;
case 0xBEFD:
spectrum_mode = true;
apu_.write_data( time, data );
return;
}
}
// CPC
if ( !spectrum_mode )
{
switch ( addr >> 8 )
{
case 0xF6:
switch ( data & 0xC0 )
{
case 0xC0:
apu_.write_addr( cpc_latch );
goto enable_cpc;
case 0x80:
apu_.write_data( time, cpc_latch );
goto enable_cpc;
}
break;
case 0xF4:
cpc_latch = data;
goto enable_cpc;
}
}
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
return;
enable_cpc:
if ( !cpc_mode )
{
cpc_mode = true;
disable_beeper();
set_cpc_callback.f( set_cpc_callback.data );
}
}
int Ay_Core::cpu_in( addr_t addr )
{
// keyboard read and other things
if ( (addr & 0xFF) == 0xFE )
return 0xFF; // other values break some beeper tunes
dprintf( "Unmapped IN : $%04X\n", addr );
return 0xFF;
}
void Ay_Core::end_frame( time_t* end )
{
cpu.set_time( 0 );
// 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
// as much until mode is known.
if ( !(spectrum_mode | cpc_mode) )
*end /= 2;
while ( cpu.time() < *end )
{
run_cpu( min( *end, next_play ) );
if ( cpu.time() >= next_play )
{
// next frame
next_play += play_period;
if ( cpu.r.iff1 )
{
// interrupt enabled
if ( mem_.ram [cpu.r.pc] == 0x76 )
cpu.r.pc++; // advance past HALT instruction
cpu.r.iff1 = 0;
cpu.r.iff2 = 0;
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8);
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc);
// fixed interrupt
cpu.r.pc = 0x38;
cpu.adjust_time( 12 );
if ( cpu.r.im == 2 )
{
// vectored interrupt
addr_t addr = cpu.r.i * 0x100 + 0xFF;
cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr];
cpu.adjust_time( 6 );
}
}
}
}
// End time frame
*end = cpu.time();
next_play -= *end;
check( next_play >= 0 );
cpu.adjust_time( -*end );
apu_.end_frame( *end );
}

View file

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

View file

@ -1,59 +1,59 @@
// $package. http://www.slack.net/~ant/
#include "Ay_Core.h"
#include "blargg_endian.h"
//#include "z80_cpu_log.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Ay_Core::cpu_out( time_t time, addr_t addr, int data )
{
if ( (addr & 0xFF) == 0xFE )
{
check( !cpc_mode );
spectrum_mode = !cpc_mode;
// beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output)
if ( (data &= beeper_mask) != last_beeper )
{
last_beeper = data;
int delta = -beeper_delta;
beeper_delta = delta;
Blip_Buffer* bb = beeper_output;
bb->set_modified();
apu_.synth_.offset( time, delta, bb );
}
}
else
{
cpu_out_( time, addr, data );
}
}
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
#define IN_PORT( addr ) cpu_in( addr )
#define FLAT_MEM mem
#define CPU cpu
#define CPU_BEGIN \
bool Ay_Core::run_cpu( time_t end_time ) \
{\
cpu.set_end_time( end_time );\
byte* const mem = mem_.ram; // cache
#include "Z80_Cpu_run.h"
return warning;
}
// $package. http://www.slack.net/~ant/
#include "Ay_Core.h"
#include "blargg_endian.h"
//#include "z80_cpu_log.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Ay_Core::cpu_out( time_t time, addr_t addr, int data )
{
if ( (addr & 0xFF) == 0xFE )
{
check( !cpc_mode );
spectrum_mode = !cpc_mode;
// beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output)
if ( (data &= beeper_mask) != last_beeper )
{
last_beeper = data;
int delta = -beeper_delta;
beeper_delta = delta;
Blip_Buffer* bb = beeper_output;
bb->set_modified();
apu_.synth_.offset( time, delta, bb );
}
}
else
{
cpu_out_( time, addr, data );
}
}
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
#define IN_PORT( addr ) cpu_in( addr )
#define FLAT_MEM mem
#define CPU cpu
#define CPU_BEGIN \
bool Ay_Core::run_cpu( time_t end_time ) \
{\
cpu.set_end_time( end_time );\
byte* const mem = mem_.ram; // cache
#include "Z80_Cpu_run.h"
return warning;
}

View file

@ -1,60 +1,60 @@
// Sinclair Spectrum AY music file emulator
// Game_Music_Emu $vers
#ifndef AY_EMU_H
#define AY_EMU_H
#include "Classic_Emu.h"
#include "Ay_Core.h"
class Ay_Emu : public Classic_Emu {
public:
// AY file header
struct header_t
{
enum { size = 0x14 };
byte tag [8];
byte vers;
byte player;
byte unused [2];
byte author [2];
byte comment [2];
byte max_track;
byte first_track;
byte track_info [2];
};
static gme_type_t static_type() { return gme_ay_type; }
// Implementation
public:
Ay_Emu();
~Ay_Emu();
struct file_t {
header_t const* header;
byte const* tracks;
byte const* end; // end of file data
};
blargg_err_t hash_( Hash_Function& out ) const;
protected:
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 start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& );
private:
file_t file;
Ay_Core core;
void enable_cpc();
static void enable_cpc_( void* data );
};
#endif
// Sinclair Spectrum AY music file emulator
// Game_Music_Emu $vers
#ifndef AY_EMU_H
#define AY_EMU_H
#include "Classic_Emu.h"
#include "Ay_Core.h"
class Ay_Emu : public Classic_Emu {
public:
// AY file header
struct header_t
{
enum { size = 0x14 };
byte tag [8];
byte vers;
byte player;
byte unused [2];
byte author [2];
byte comment [2];
byte max_track;
byte first_track;
byte track_info [2];
};
static gme_type_t static_type() { return gme_ay_type; }
// Implementation
public:
Ay_Emu();
~Ay_Emu();
struct file_t {
header_t const* header;
byte const* tracks;
byte const* end; // end of file data
};
blargg_err_t hash_( Hash_Function& out ) const;
protected:
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 start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& );
private:
file_t file;
Ay_Core core;
void enable_cpc();
static void enable_cpc_( void* data );
};
#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/
#include "Classic_Emu.h"
#include "Multi_Buffer.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Classic_Emu::Classic_Emu()
{
buf = NULL;
stereo_buffer = NULL;
voice_types = NULL;
// avoid inconsistency in our duplicated constants
assert( (int) wave_type == (int) Multi_Buffer::wave_type );
assert( (int) noise_type == (int) Multi_Buffer::noise_type );
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
}
Classic_Emu::~Classic_Emu()
{
delete stereo_buffer;
delete effects_buffer_;
effects_buffer_ = NULL;
}
void Classic_Emu::set_equalizer_( equalizer_t const& eq )
{
Music_Emu::set_equalizer_( eq );
update_eq( eq.treble );
if ( buf )
buf->bass_freq( (int) equalizer().bass );
}
blargg_err_t Classic_Emu::set_sample_rate_( int rate )
{
if ( !buf )
{
if ( !stereo_buffer )
CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
buf = stereo_buffer;
}
return buf->set_sample_rate( rate, 1000 / 20 );
}
void Classic_Emu::mute_voices_( int mask )
{
Music_Emu::mute_voices_( mask );
for ( int i = voice_count(); i--; )
{
if ( mask & (1 << i) )
{
set_voice( i, NULL, NULL, NULL );
}
else
{
Multi_Buffer::channel_t ch = buf->channel( i );
assert( (ch.center && ch.left && ch.right) ||
(!ch.center && !ch.left && !ch.right) ); // all or nothing
set_voice( i, ch.center, ch.left, ch.right );
}
}
}
void Classic_Emu::change_clock_rate( int rate )
{
clock_rate_ = rate;
buf->clock_rate( rate );
}
blargg_err_t Classic_Emu::setup_buffer( int rate )
{
change_clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) );
set_equalizer( equalizer() );
buf_changed_count = buf->channels_changed_count();
return blargg_ok;
}
blargg_err_t Classic_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
buf->clear();
return blargg_ok;
}
blargg_err_t Classic_Emu::play_( int count, sample_t out [] )
{
// read from buffer, then refill buffer and repeat if necessary
int remain = count;
while ( remain )
{
buf->disable_immediate_removal();
remain -= buf->read_samples( &out [count - remain], remain );
if ( remain )
{
if ( buf_changed_count != buf->channels_changed_count() )
{
buf_changed_count = buf->channels_changed_count();
remute_voices();
}
// TODO: use more accurate length calculation
int msec = buf->length();
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100;
RETURN_ERR( run_clocks( clocks_emulated, msec ) );
assert( clocks_emulated );
buf->end_frame( clocks_emulated );
}
}
return blargg_ok;
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Classic_Emu.h"
#include "Multi_Buffer.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Classic_Emu::Classic_Emu()
{
buf = NULL;
stereo_buffer = NULL;
voice_types = NULL;
// avoid inconsistency in our duplicated constants
assert( (int) wave_type == (int) Multi_Buffer::wave_type );
assert( (int) noise_type == (int) Multi_Buffer::noise_type );
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
}
Classic_Emu::~Classic_Emu()
{
delete stereo_buffer;
delete effects_buffer_;
effects_buffer_ = NULL;
}
void Classic_Emu::set_equalizer_( equalizer_t const& eq )
{
Music_Emu::set_equalizer_( eq );
update_eq( eq.treble );
if ( buf )
buf->bass_freq( (int) equalizer().bass );
}
blargg_err_t Classic_Emu::set_sample_rate_( int rate )
{
if ( !buf )
{
if ( !stereo_buffer )
CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
buf = stereo_buffer;
}
return buf->set_sample_rate( rate, 1000 / 20 );
}
void Classic_Emu::mute_voices_( int mask )
{
Music_Emu::mute_voices_( mask );
for ( int i = voice_count(); i--; )
{
if ( mask & (1 << i) )
{
set_voice( i, NULL, NULL, NULL );
}
else
{
Multi_Buffer::channel_t ch = buf->channel( i );
assert( (ch.center && ch.left && ch.right) ||
(!ch.center && !ch.left && !ch.right) ); // all or nothing
set_voice( i, ch.center, ch.left, ch.right );
}
}
}
void Classic_Emu::change_clock_rate( int rate )
{
clock_rate_ = rate;
buf->clock_rate( rate );
}
blargg_err_t Classic_Emu::setup_buffer( int rate )
{
change_clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) );
set_equalizer( equalizer() );
buf_changed_count = buf->channels_changed_count();
return blargg_ok;
}
blargg_err_t Classic_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
buf->clear();
return blargg_ok;
}
blargg_err_t Classic_Emu::play_( int count, sample_t out [] )
{
// read from buffer, then refill buffer and repeat if necessary
int remain = count;
while ( remain )
{
buf->disable_immediate_removal();
remain -= buf->read_samples( &out [count - remain], remain );
if ( remain )
{
if ( buf_changed_count != buf->channels_changed_count() )
{
buf_changed_count = buf->channels_changed_count();
remute_voices();
}
// TODO: use more accurate length calculation
int msec = buf->length();
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100;
RETURN_ERR( run_clocks( clocks_emulated, msec ) );
assert( clocks_emulated );
buf->end_frame( clocks_emulated );
}
}
return blargg_ok;
}

View file

@ -1,79 +1,79 @@
// Common aspects of emulators which use Blip_Buffer for sound output
// Game_Music_Emu $vers
#ifndef CLASSIC_EMU_H
#define CLASSIC_EMU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
#include "Music_Emu.h"
class Classic_Emu : public Music_Emu {
protected:
// Derived interface
// 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
// 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 };
void set_voice_types( int const types [] ) { voice_types = types; }
// Sets up Blip_Buffers after loading file
blargg_err_t setup_buffer( int clock_rate );
// Clock rate of Blip_buffers
int clock_rate() const { return clock_rate_; }
// Changes clock rate of Blip_Buffers (experimental)
void change_clock_rate( int );
// Overrides should do the indicated task
// Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL
virtual void set_voice( int index, Blip_Buffer* center,
Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; )
// Update equalization
virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; )
// Start track
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
// actually run for. After returning, Blip_Buffers have time frame of time_io clocks
// ended.
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; )
// Internal
public:
Classic_Emu();
~Classic_Emu();
virtual void set_buffer( Multi_Buffer* );
protected:
virtual blargg_err_t set_sample_rate_( int sample_rate );
virtual void mute_voices_( int );
virtual void set_equalizer_( equalizer_t const& );
virtual blargg_err_t play_( int, sample_t [] );
private:
Multi_Buffer* buf;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer
int clock_rate_;
unsigned buf_changed_count;
int const* voice_types;
};
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
{
assert( !buf && new_buf );
buf = new_buf;
}
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { }
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; }
#endif
// Common aspects of emulators which use Blip_Buffer for sound output
// Game_Music_Emu $vers
#ifndef CLASSIC_EMU_H
#define CLASSIC_EMU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
#include "Music_Emu.h"
class Classic_Emu : public Music_Emu {
protected:
// Derived interface
// 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
// 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 };
void set_voice_types( int const types [] ) { voice_types = types; }
// Sets up Blip_Buffers after loading file
blargg_err_t setup_buffer( int clock_rate );
// Clock rate of Blip_buffers
int clock_rate() const { return clock_rate_; }
// Changes clock rate of Blip_Buffers (experimental)
void change_clock_rate( int );
// Overrides should do the indicated task
// Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL
virtual void set_voice( int index, Blip_Buffer* center,
Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; )
// Update equalization
virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; )
// Start track
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
// actually run for. After returning, Blip_Buffers have time frame of time_io clocks
// ended.
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; )
// Internal
public:
Classic_Emu();
~Classic_Emu();
virtual void set_buffer( Multi_Buffer* );
protected:
virtual blargg_err_t set_sample_rate_( int sample_rate );
virtual void mute_voices_( int );
virtual void set_equalizer_( equalizer_t const& );
virtual blargg_err_t play_( int, sample_t [] );
private:
Multi_Buffer* buf;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer
int clock_rate_;
unsigned buf_changed_count;
int const* voice_types;
};
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
{
assert( !buf && new_buf );
buf = new_buf;
}
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { }
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; }
#endif

View file

@ -1,315 +1,315 @@
// File_Extractor 0.4.0. http://www.slack.net/~ant/
#include "Data_Reader.h"
#include "blargg_endian.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
const char Data_Reader::eof_error [] = "Unexpected end of file";
blargg_err_t Data_Reader::read( void* p, long s )
{
long result = read_avail( p, s );
if ( result != s )
{
if ( result >= 0 && result < s )
return eof_error;
return "Read error";
}
return 0;
}
blargg_err_t Data_Reader::skip( long count )
{
char buf [512];
while ( count )
{
long n = sizeof buf;
if ( n > count )
n = count;
count -= n;
RETURN_ERR( read( buf, n ) );
}
return 0;
}
long File_Reader::remain() const { return size() - tell(); }
blargg_err_t File_Reader::skip( long n )
{
assert( n >= 0 );
if ( !n )
return 0;
return seek( tell() + n );
}
// Subset_Reader
Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
{
in = dr;
remain_ = dr->remain();
if ( remain_ > size )
remain_ = size;
}
long Subset_Reader::remain() const { return remain_; }
long Subset_Reader::read_avail( void* p, long s )
{
if ( s > remain_ )
s = remain_;
remain_ -= s;
return in->read_avail( p, s );
}
// Remaining_Reader
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
{
header = (char const*) h;
header_end = header + size;
in = r;
}
long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
long Remaining_Reader::read_first( void* out, long count )
{
long first = header_end - header;
if ( first )
{
if ( first > count )
first = count;
void const* old = header;
header += first;
memcpy( out, old, first );
}
return first;
}
long Remaining_Reader::read_avail( void* out, long count )
{
long first = read_first( out, count );
long second = count - first;
if ( second )
{
second = in->read_avail( (char*) out + first, second );
if ( second <= 0 )
return second;
}
return first + second;
}
blargg_err_t Remaining_Reader::read( void* out, long count )
{
long first = read_first( out, count );
long second = count - first;
if ( !second )
return 0;
return in->read( (char*) out + first, second );
}
// Mem_File_Reader
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
begin( (const char*) p ),
size_( s )
{
pos = 0;
}
long Mem_File_Reader::size() const { return size_; }
long Mem_File_Reader::read_avail( void* p, long s )
{
long r = remain();
if ( s > r )
s = r;
memcpy( p, begin + pos, s );
pos += s;
return s;
}
long Mem_File_Reader::tell() const { return pos; }
blargg_err_t Mem_File_Reader::seek( long n )
{
if ( n > size_ )
return eof_error;
pos = n;
return 0;
}
// Callback_Reader
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ),
data( d )
{
remain_ = size;
}
long Callback_Reader::remain() const { return remain_; }
long Callback_Reader::read_avail( void* out, long count )
{
if ( count > remain_ )
count = remain_;
if ( Callback_Reader::read( out, count ) )
count = -1;
return count;
}
blargg_err_t Callback_Reader::read( void* out, long count )
{
if ( count > remain_ )
return eof_error;
return callback( data, out, count );
}
// Std_File_Reader
Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
Std_File_Reader::~Std_File_Reader() { close(); }
blargg_err_t Std_File_Reader::open( const char* path )
{
file_ = fopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
long Std_File_Reader::size() const
{
long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END );
long result = tell();
fseek( (FILE*) file_, pos, SEEK_SET );
return result;
}
long Std_File_Reader::read_avail( void* p, long s )
{
return fread( p, 1, s, (FILE*) file_ );
}
blargg_err_t Std_File_Reader::read( void* p, long s )
{
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
return 0;
if ( feof( (FILE*) file_ ) )
return eof_error;
return "Couldn't read from file";
}
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }
blargg_err_t Std_File_Reader::seek( long n )
{
if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
return 0;
if ( n > size() )
return eof_error;
return "Error seeking in file";
}
void Std_File_Reader::close()
{
if ( file_ )
{
fclose( (FILE*) file_ );
file_ = 0;
}
}
// Gzip_File_Reader
#ifdef HAVE_ZLIB_H
#include "zlib.h"
static const char* get_gzip_eof( const char* path, long* eof )
{
FILE* file = fopen( path, "rb" );
if ( !file )
return "Couldn't open file";
unsigned char buf [4];
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{
fseek( file, -4, SEEK_END );
fread( buf, 4, 1, file );
*eof = get_le32( buf );
}
else
{
fseek( file, 0, SEEK_END );
*eof = ftell( file );
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
fclose( file );
return err;
}
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
blargg_err_t Gzip_File_Reader::open( const char* path )
{
close();
RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
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::tell() const { return gztell( file_ ); }
blargg_err_t Gzip_File_Reader::seek( long n )
{
if ( gzseek( file_, n, SEEK_SET ) >= 0 )
return 0;
if ( n > size_ )
return eof_error;
return "Error seeking in file";
}
void Gzip_File_Reader::close()
{
if ( file_ )
{
gzclose( file_ );
file_ = 0;
}
}
#endif
// File_Extractor 0.4.0. http://www.slack.net/~ant/
#include "Data_Reader.h"
#include "blargg_endian.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
const char Data_Reader::eof_error [] = "Unexpected end of file";
blargg_err_t Data_Reader::read( void* p, long s )
{
long result = read_avail( p, s );
if ( result != s )
{
if ( result >= 0 && result < s )
return eof_error;
return "Read error";
}
return 0;
}
blargg_err_t Data_Reader::skip( long count )
{
char buf [512];
while ( count )
{
long n = sizeof buf;
if ( n > count )
n = count;
count -= n;
RETURN_ERR( read( buf, n ) );
}
return 0;
}
long File_Reader::remain() const { return size() - tell(); }
blargg_err_t File_Reader::skip( long n )
{
assert( n >= 0 );
if ( !n )
return 0;
return seek( tell() + n );
}
// Subset_Reader
Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
{
in = dr;
remain_ = dr->remain();
if ( remain_ > size )
remain_ = size;
}
long Subset_Reader::remain() const { return remain_; }
long Subset_Reader::read_avail( void* p, long s )
{
if ( s > remain_ )
s = remain_;
remain_ -= s;
return in->read_avail( p, s );
}
// Remaining_Reader
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
{
header = (char const*) h;
header_end = header + size;
in = r;
}
long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
long Remaining_Reader::read_first( void* out, long count )
{
long first = header_end - header;
if ( first )
{
if ( first > count )
first = count;
void const* old = header;
header += first;
memcpy( out, old, first );
}
return first;
}
long Remaining_Reader::read_avail( void* out, long count )
{
long first = read_first( out, count );
long second = count - first;
if ( second )
{
second = in->read_avail( (char*) out + first, second );
if ( second <= 0 )
return second;
}
return first + second;
}
blargg_err_t Remaining_Reader::read( void* out, long count )
{
long first = read_first( out, count );
long second = count - first;
if ( !second )
return 0;
return in->read( (char*) out + first, second );
}
// Mem_File_Reader
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
begin( (const char*) p ),
size_( s )
{
pos = 0;
}
long Mem_File_Reader::size() const { return size_; }
long Mem_File_Reader::read_avail( void* p, long s )
{
long r = remain();
if ( s > r )
s = r;
memcpy( p, begin + pos, s );
pos += s;
return s;
}
long Mem_File_Reader::tell() const { return pos; }
blargg_err_t Mem_File_Reader::seek( long n )
{
if ( n > size_ )
return eof_error;
pos = n;
return 0;
}
// Callback_Reader
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ),
data( d )
{
remain_ = size;
}
long Callback_Reader::remain() const { return remain_; }
long Callback_Reader::read_avail( void* out, long count )
{
if ( count > remain_ )
count = remain_;
if ( Callback_Reader::read( out, count ) )
count = -1;
return count;
}
blargg_err_t Callback_Reader::read( void* out, long count )
{
if ( count > remain_ )
return eof_error;
return callback( data, out, count );
}
// Std_File_Reader
Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
Std_File_Reader::~Std_File_Reader() { close(); }
blargg_err_t Std_File_Reader::open( const char* path )
{
file_ = fopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
long Std_File_Reader::size() const
{
long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END );
long result = tell();
fseek( (FILE*) file_, pos, SEEK_SET );
return result;
}
long Std_File_Reader::read_avail( void* p, long s )
{
return fread( p, 1, s, (FILE*) file_ );
}
blargg_err_t Std_File_Reader::read( void* p, long s )
{
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
return 0;
if ( feof( (FILE*) file_ ) )
return eof_error;
return "Couldn't read from file";
}
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }
blargg_err_t Std_File_Reader::seek( long n )
{
if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
return 0;
if ( n > size() )
return eof_error;
return "Error seeking in file";
}
void Std_File_Reader::close()
{
if ( file_ )
{
fclose( (FILE*) file_ );
file_ = 0;
}
}
// Gzip_File_Reader
#ifdef HAVE_ZLIB_H
#include "zlib.h"
static const char* get_gzip_eof( const char* path, long* eof )
{
FILE* file = fopen( path, "rb" );
if ( !file )
return "Couldn't open file";
unsigned char buf [4];
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{
fseek( file, -4, SEEK_END );
fread( buf, 4, 1, file );
*eof = get_le32( buf );
}
else
{
fseek( file, 0, SEEK_END );
*eof = ftell( file );
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
fclose( file );
return err;
}
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
blargg_err_t Gzip_File_Reader::open( const char* path )
{
close();
RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
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::tell() const { return gztell( file_ ); }
blargg_err_t Gzip_File_Reader::seek( long n )
{
if ( gzseek( file_, n, SEEK_SET ) >= 0 )
return 0;
if ( n > size_ )
return eof_error;
return "Error seeking in file";
}
void Gzip_File_Reader::close()
{
if ( file_ )
{
gzclose( file_ );
file_ = 0;
}
}
#endif

View file

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

View file

@ -1,74 +1,74 @@
// $package. http://www.slack.net/~ant/
#include "Downsampler.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const shift = 14;
int const unit = 1 << shift;
void Downsampler::clear_()
{
pos = 0;
Resampler::clear_();
}
Downsampler::Downsampler()
{
clear();
}
blargg_err_t Downsampler::set_rate_( double new_factor )
{
step = (int) (new_factor * unit + 0.5);
return Resampler::set_rate_( 1.0 / unit * step );
}
Resampler::sample_t const* Downsampler::resample_( sample_t** out_,
sample_t const* out_end, sample_t const in [], int in_size )
{
in_size -= write_offset;
if ( in_size > 0 )
{
sample_t* BLARGG_RESTRICT out = *out_;
sample_t const* const in_end = in + in_size;
int const step = this->step;
int pos = this->pos;
// TODO: IIR filter, then linear resample
// TODO: detect skipped sample, allowing merging of IIR and resample?
do
{
#define INTERP( i, out )\
out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\
in [8 + i] * pos) >> (shift + 2);
int out_0;
INTERP( 0, out_0 )
INTERP( 1, out [0] = out_0; out [1] )
out += stereo;
pos += step;
in += ((unsigned) pos >> shift) * stereo;
pos &= unit - 1;
}
while ( in < in_end && out < out_end );
this->pos = pos;
*out_ = out;
}
return in;
}
// $package. http://www.slack.net/~ant/
#include "Downsampler.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const shift = 14;
int const unit = 1 << shift;
void Downsampler::clear_()
{
pos = 0;
Resampler::clear_();
}
Downsampler::Downsampler()
{
clear();
}
blargg_err_t Downsampler::set_rate_( double new_factor )
{
step = (int) (new_factor * unit + 0.5);
return Resampler::set_rate_( 1.0 / unit * step );
}
Resampler::sample_t const* Downsampler::resample_( sample_t** out_,
sample_t const* out_end, sample_t const in [], int in_size )
{
in_size -= write_offset;
if ( in_size > 0 )
{
sample_t* BLARGG_RESTRICT out = *out_;
sample_t const* const in_end = in + in_size;
int const step = this->step;
int pos = this->pos;
// TODO: IIR filter, then linear resample
// TODO: detect skipped sample, allowing merging of IIR and resample?
do
{
#define INTERP( i, out )\
out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\
in [8 + i] * pos) >> (shift + 2);
int out_0;
INTERP( 0, out_0 )
INTERP( 1, out [0] = out_0; out [1] )
out += stereo;
pos += step;
in += ((unsigned) pos >> shift) * stereo;
pos &= unit - 1;
}
while ( in < in_end && out < out_end );
this->pos = pos;
*out_ = out;
}
return in;
}

View file

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

View file

@ -1,61 +1,61 @@
// Combination of Fir_Resampler and Stereo_Buffer mixing. Used by Sega FM emulators.
// Game_Music_Emu $vers
#ifndef DUAL_RESAMPLER_H
#define DUAL_RESAMPLER_H
#include "Multi_Buffer.h"
#if GME_VGM_FAST_RESAMPLER
#include "Downsampler.h"
typedef Downsampler Dual_Resampler_Downsampler;
#else
#include "Fir_Resampler.h"
typedef Fir_Resampler_Norm Dual_Resampler_Downsampler;
#endif
class Dual_Resampler {
public:
typedef short dsample_t;
blargg_err_t setup( double oversample, double rolloff, double gain );
double rate() const { return resampler.rate(); }
blargg_err_t reset( int max_pairs );
void resize( int pairs_per_frame );
void clear();
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;
// Implementation
public:
Dual_Resampler();
~Dual_Resampler();
private:
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_;
Dual_Resampler_Downsampler resampler;
void mix_samples( Stereo_Buffer&, dsample_t [], int, Stereo_Buffer**, int );
void mix_mono( 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_stereo( Stereo_Buffer&, dsample_t [], int );
int play_frame_( Stereo_Buffer&, dsample_t [], Stereo_Buffer**, int );
};
inline blargg_err_t Dual_Resampler::setup( double oversample, double rolloff, double gain )
{
gain_ = (int) ((1 << gain_bits) * gain);
return resampler.set_rate( oversample );
}
#endif
// Combination of Fir_Resampler and Stereo_Buffer mixing. Used by Sega FM emulators.
// Game_Music_Emu $vers
#ifndef DUAL_RESAMPLER_H
#define DUAL_RESAMPLER_H
#include "Multi_Buffer.h"
#if GME_VGM_FAST_RESAMPLER
#include "Downsampler.h"
typedef Downsampler Dual_Resampler_Downsampler;
#else
#include "Fir_Resampler.h"
typedef Fir_Resampler_Norm Dual_Resampler_Downsampler;
#endif
class Dual_Resampler {
public:
typedef short dsample_t;
blargg_err_t setup( double oversample, double rolloff, double gain );
double rate() const { return resampler.rate(); }
blargg_err_t reset( int max_pairs );
void resize( int pairs_per_frame );
void clear();
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;
// Implementation
public:
Dual_Resampler();
~Dual_Resampler();
private:
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_;
Dual_Resampler_Downsampler resampler;
void mix_samples( Stereo_Buffer&, dsample_t [], int, Stereo_Buffer**, int );
void mix_mono( 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_stereo( Stereo_Buffer&, dsample_t [], int );
int play_frame_( Stereo_Buffer&, dsample_t [], Stereo_Buffer**, int );
};
inline blargg_err_t Dual_Resampler::setup( double oversample, double rolloff, double gain )
{
gain_ = (int) ((1 << gain_bits) * gain);
return resampler.set_rate( oversample );
}
#endif

View file

@ -18,13 +18,13 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
int const fixed_shift = 12;
#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift))
#define FROM_FIXED( f ) ((f) >> fixed_shift)
int const max_read = 2560; // determines minimum delay
int const fixed_shift = 12;
#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift))
#define FROM_FIXED( f ) ((f) >> fixed_shift)
int const max_read = 2560; // determines minimum delay
Effects_Buffer::Effects_Buffer( int max_bufs, int echo_size_ ) : Multi_Buffer( stereo )
{
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
// Game_Music_Emu $vers
#ifndef EFFECTS_BUFFER_H
#define EFFECTS_BUFFER_H
#include "Multi_Buffer.h"
// See Simple_Effects_Buffer (below) for a simpler interface
class Effects_Buffer : public Multi_Buffer {
public:
// 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
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 );
struct pan_vol_t
{
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal
float pan; // -1.0 = left, 0.0 = center, +1.0 = right
};
// Global configuration
struct config_t
{
bool enabled; // false = disable all effects
// Current sound is echoed at adjustable left/right delay,
// with reduced treble and volume (feedback).
float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent
int delay [2]; // left, right delays (msec)
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
};
config_t& config() { return config_; }
// Limits of delay (msec)
int min_delay() const;
int max_delay() const;
// Per-channel configuration. Two or more channels with matching parameters are
// optimized to internally use the same buffer.
struct chan_config_t : pan_vol_t
{
// (inherited from pan_vol_t)
//float vol; // these only affect center channel
//float pan;
bool surround; // if true, negates left volume to put sound in back
bool echo; // false = channel doesn't have any echo
};
chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; }
// Applies any changes made to config() and chan_config()
virtual void apply_config();
// Implementation
public:
~Effects_Buffer();
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 );
void clock_rate( int );
void bass_freq( int );
void clear();
channel_t channel( int );
void end_frame( blip_time_t );
int read_samples( blip_sample_t [], int );
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
enum { stereo = 2 };
typedef int fixed_t;
protected:
enum { extra_chans = stereo * stereo };
private:
config_t config_;
int clock_rate_;
int bass_freq_;
int echo_size;
struct chan_t
{
fixed_t vol [stereo];
chan_config_t cfg;
channel_t channel;
};
blargg_vector<chan_t> chans;
struct buf_t : Tracked_Blip_Buffer
{
// nasty: Blip_Buffer has something called fixed_t
Effects_Buffer::fixed_t vol [stereo];
bool echo;
void* operator new ( size_t, void* p ) { return p; }
void operator delete ( void* ) { }
~buf_t() { }
};
buf_t* bufs;
int bufs_size;
int bufs_max; // bufs_size <= bufs_max, to limit memory usage
Stereo_Mixer mixer;
struct {
int delay [stereo];
fixed_t treble;
fixed_t feedback;
fixed_t low_pass [stereo];
} s;
blargg_vector<fixed_t> echo;
int echo_pos;
bool no_effects;
bool no_echo;
void assign_buffers();
void clear_echo();
void mix_effects( blip_sample_t out [], int pair_count );
blargg_err_t new_bufs( int size );
void delete_bufs();
};
// Simpler interface and lower memory usage
class Simple_Effects_Buffer : public Effects_Buffer {
public:
struct config_t
{
bool enabled; // false = disable all effects
float echo; // 0.0 = none, 1.0 = lots
float stereo; // 0.0 = channels in center, 1.0 = channels on left/right
bool surround; // true = put some channels in back
};
config_t& config() { return config_; }
// Applies any changes made to config()
void apply_config();
// Implementation
public:
Simple_Effects_Buffer();
private:
config_t config_;
void chan_config(); // hide
};
#endif
// Multi-channel effects buffer with echo and individual panning for each channel
// Game_Music_Emu $vers
#ifndef EFFECTS_BUFFER_H
#define EFFECTS_BUFFER_H
#include "Multi_Buffer.h"
// See Simple_Effects_Buffer (below) for a simpler interface
class Effects_Buffer : public Multi_Buffer {
public:
// 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
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 );
struct pan_vol_t
{
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal
float pan; // -1.0 = left, 0.0 = center, +1.0 = right
};
// Global configuration
struct config_t
{
bool enabled; // false = disable all effects
// Current sound is echoed at adjustable left/right delay,
// with reduced treble and volume (feedback).
float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent
int delay [2]; // left, right delays (msec)
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
};
config_t& config() { return config_; }
// Limits of delay (msec)
int min_delay() const;
int max_delay() const;
// Per-channel configuration. Two or more channels with matching parameters are
// optimized to internally use the same buffer.
struct chan_config_t : pan_vol_t
{
// (inherited from pan_vol_t)
//float vol; // these only affect center channel
//float pan;
bool surround; // if true, negates left volume to put sound in back
bool echo; // false = channel doesn't have any echo
};
chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; }
// Applies any changes made to config() and chan_config()
virtual void apply_config();
// Implementation
public:
~Effects_Buffer();
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 );
void clock_rate( int );
void bass_freq( int );
void clear();
channel_t channel( int );
void end_frame( blip_time_t );
int read_samples( blip_sample_t [], int );
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
enum { stereo = 2 };
typedef int fixed_t;
protected:
enum { extra_chans = stereo * stereo };
private:
config_t config_;
int clock_rate_;
int bass_freq_;
int echo_size;
struct chan_t
{
fixed_t vol [stereo];
chan_config_t cfg;
channel_t channel;
};
blargg_vector<chan_t> chans;
struct buf_t : Tracked_Blip_Buffer
{
// nasty: Blip_Buffer has something called fixed_t
Effects_Buffer::fixed_t vol [stereo];
bool echo;
void* operator new ( size_t, void* p ) { return p; }
void operator delete ( void* ) { }
~buf_t() { }
};
buf_t* bufs;
int bufs_size;
int bufs_max; // bufs_size <= bufs_max, to limit memory usage
Stereo_Mixer mixer;
struct {
int delay [stereo];
fixed_t treble;
fixed_t feedback;
fixed_t low_pass [stereo];
} s;
blargg_vector<fixed_t> echo;
int echo_pos;
bool no_effects;
bool no_echo;
void assign_buffers();
void clear_echo();
void mix_effects( blip_sample_t out [], int pair_count );
blargg_err_t new_bufs( int size );
void delete_bufs();
};
// Simpler interface and lower memory usage
class Simple_Effects_Buffer : public Effects_Buffer {
public:
struct config_t
{
bool enabled; // false = disable all effects
float echo; // 0.0 = none, 1.0 = lots
float stereo; // 0.0 = channels in center, 1.0 = channels on left/right
bool surround; // true = put some channels in back
};
config_t& config() { return config_; }
// Applies any changes made to config()
void apply_config();
// Implementation
public:
Simple_Effects_Buffer();
private:
config_t config_;
void chan_config(); // hide
};
#endif

View file

@ -1,123 +1,123 @@
// $package. http://www.slack.net/~ant/
#include "Fir_Resampler.h"
#include <math.h>
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#undef PI
#define PI 3.1415926535897932384626433832795029
static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
int count, short* out )
{
double const maxh = 256;
double const step = PI / maxh * spacing;
double const to_w = maxh * 2 / width;
double const pow_a_n = pow( rolloff, maxh );
scale /= maxh * 2;
double angle = (count / 2 - 1 + offset) * -step;
while ( count-- )
{
*out++ = 0;
double w = angle * to_w;
if ( fabs( w ) < PI )
{
double rolloff_cos_a = rolloff * cos( angle );
double num = 1 - rolloff_cos_a -
pow_a_n * cos( maxh * angle ) +
pow_a_n * rolloff * cos( (maxh - 1) * angle );
double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
double sinc = scale * num / den - scale;
out [-1] = (short) (cos( w ) * sinc + sinc);
}
angle += step;
}
}
Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) :
width_( width ),
impulses( impulses_ )
{
imp = NULL;
}
void Fir_Resampler_::clear_()
{
imp = impulses;
Resampler::clear_();
}
blargg_err_t Fir_Resampler_::set_rate_( double new_factor )
{
double const rolloff = 0.999;
double const gain = 1.0;
// determine number of sub-phases that yield lowest error
double ratio_ = 0.0;
int res = -1;
{
double least_error = 2;
double pos = 0;
for ( int r = 1; r <= max_res; r++ )
{
pos += new_factor;
double nearest = floor( pos + 0.5 );
double error = fabs( pos - nearest );
if ( error < least_error )
{
res = r;
ratio_ = nearest / res;
least_error = error;
}
}
}
RETURN_ERR( Resampler::set_rate_( ratio_ ) );
// how much of input is used for each output sample
int const step = stereo * (int) floor( ratio_ );
double fraction = fmod( ratio_, 1.0 );
double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
double pos = 0.0;
//int input_per_cycle = 0;
sample_t* out = impulses;
for ( int n = res; --n >= 0; )
{
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
double (0x7FFF * gain * filter), (int) width_, out );
out += width_;
int cur_step = step;
pos += fraction;
if ( pos >= 0.9999999 )
{
pos -= 1.0;
cur_step += stereo;
}
*out++ = (cur_step - width_ * 2 + 4) * sizeof (sample_t);
*out++ = 4 * sizeof (sample_t);
//input_per_cycle += cur_step;
}
// last offset moves back to beginning of impulses
out [-1] -= (char*) out - (char*) impulses;
imp = impulses;
return blargg_ok;
}
// $package. http://www.slack.net/~ant/
#include "Fir_Resampler.h"
#include <math.h>
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#undef PI
#define PI 3.1415926535897932384626433832795029
static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
int count, short* out )
{
double const maxh = 256;
double const step = PI / maxh * spacing;
double const to_w = maxh * 2 / width;
double const pow_a_n = pow( rolloff, maxh );
scale /= maxh * 2;
double angle = (count / 2 - 1 + offset) * -step;
while ( count-- )
{
*out++ = 0;
double w = angle * to_w;
if ( fabs( w ) < PI )
{
double rolloff_cos_a = rolloff * cos( angle );
double num = 1 - rolloff_cos_a -
pow_a_n * cos( maxh * angle ) +
pow_a_n * rolloff * cos( (maxh - 1) * angle );
double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
double sinc = scale * num / den - scale;
out [-1] = (short) (cos( w ) * sinc + sinc);
}
angle += step;
}
}
Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) :
width_( width ),
impulses( impulses_ )
{
imp = NULL;
}
void Fir_Resampler_::clear_()
{
imp = impulses;
Resampler::clear_();
}
blargg_err_t Fir_Resampler_::set_rate_( double new_factor )
{
double const rolloff = 0.999;
double const gain = 1.0;
// determine number of sub-phases that yield lowest error
double ratio_ = 0.0;
int res = -1;
{
double least_error = 2;
double pos = 0;
for ( int r = 1; r <= max_res; r++ )
{
pos += new_factor;
double nearest = floor( pos + 0.5 );
double error = fabs( pos - nearest );
if ( error < least_error )
{
res = r;
ratio_ = nearest / res;
least_error = error;
}
}
}
RETURN_ERR( Resampler::set_rate_( ratio_ ) );
// how much of input is used for each output sample
int const step = stereo * (int) floor( ratio_ );
double fraction = fmod( ratio_, 1.0 );
double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
double pos = 0.0;
//int input_per_cycle = 0;
sample_t* out = impulses;
for ( int n = res; --n >= 0; )
{
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
double (0x7FFF * gain * filter), (int) width_, out );
out += width_;
int cur_step = step;
pos += fraction;
if ( pos >= 0.9999999 )
{
pos -= 1.0;
cur_step += stereo;
}
*out++ = (cur_step - width_ * 2 + 4) * sizeof (sample_t);
*out++ = 4 * sizeof (sample_t);
//input_per_cycle += cur_step;
}
// last offset moves back to beginning of impulses
out [-1] -= (char*) out - (char*) impulses;
imp = impulses;
return blargg_ok;
}

View file

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

View file

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

View file

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

View file

@ -1,51 +1,51 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gb_Cpu.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
inline void Gb_Cpu::set_code_page( int i, void* p )
{
byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size );
cpu_state_.code_map [i] = p2;
cpu_state->code_map [i] = p2;
}
void Gb_Cpu::reset( void* unmapped )
{
check( cpu_state == &cpu_state_ );
cpu_state = &cpu_state_;
cpu_state_.time = 0;
for ( int i = 0; i < page_count + 1; ++i )
set_code_page( i, unmapped );
memset( &r, 0, sizeof r );
blargg_verify_byte_order();
}
void Gb_Cpu::map_code( addr_t start, int size, void* data )
{
// address range must begin and end on page boundaries
require( start % page_size == 0 );
require( size % page_size == 0 );
require( start + size <= mem_size );
for ( int offset = 0; offset < size; offset += page_size )
set_code_page( (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset );
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gb_Cpu.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
inline void Gb_Cpu::set_code_page( int i, void* p )
{
byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size );
cpu_state_.code_map [i] = p2;
cpu_state->code_map [i] = p2;
}
void Gb_Cpu::reset( void* unmapped )
{
check( cpu_state == &cpu_state_ );
cpu_state = &cpu_state_;
cpu_state_.time = 0;
for ( int i = 0; i < page_count + 1; ++i )
set_code_page( i, unmapped );
memset( &r, 0, sizeof r );
blargg_verify_byte_order();
}
void Gb_Cpu::map_code( addr_t start, int size, void* data )
{
// address range must begin and end on page boundaries
require( start % page_size == 0 );
require( size % page_size == 0 );
require( start + size <= mem_size );
for ( int offset = 0; offset < size; offset += page_size )
set_code_page( (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset );
}

View file

@ -1,82 +1,82 @@
// Nintendo Game Boy CPU emulator
// Game_Music_Emu $vers
#ifndef GB_CPU_H
#define GB_CPU_H
#include "blargg_common.h"
class Gb_Cpu {
public:
typedef int addr_t;
typedef BOOST::uint8_t byte;
enum { mem_size = 0x10000 };
// Clears registers and map all pages to unmapped
void reset( void* unmapped = NULL );
// Maps code memory (memory accessed via the program counter). Start and size
// must be multiple of page_size.
enum { page_bits = 13 };
enum { page_size = 1 << page_bits };
void map_code( addr_t start, int size, void* code );
// Accesses emulated memory as CPU does
byte* get_code( addr_t );
// Game Boy Z-80 registers. NOT kept updated during emulation.
struct core_regs_t {
BOOST::uint16_t bc, de, hl, fa;
};
struct registers_t : core_regs_t {
int pc; // more than 16 bits to allow overflow detection
BOOST::uint16_t sp;
};
registers_t r;
// Base address for RST vectors, to simplify GBS player (normally 0)
addr_t rst_base;
// Current time.
int time() const { return cpu_state->time; }
// Changes time. Must not be called during emulation.
// Should be negative, because emulation stops once it becomes >= 0.
void set_time( int t ) { cpu_state->time = t; }
// Emulator reads this many bytes past end of a page
enum { cpu_padding = 8 };
// Implementation
public:
Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; }
enum { page_count = mem_size >> page_bits };
struct cpu_state_t {
byte* code_map [page_count + 1];
int time;
};
cpu_state_t* cpu_state; // points to state_ or a local copy within run()
cpu_state_t cpu_state_;
private:
void set_code_page( int, void* );
};
#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits)
#if BLARGG_NONPORTABLE
#define GB_CPU_OFFSET( addr ) (addr)
#else
#define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1))
#endif
inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr )
{
return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr );
}
#endif
// Nintendo Game Boy CPU emulator
// Game_Music_Emu $vers
#ifndef GB_CPU_H
#define GB_CPU_H
#include "blargg_common.h"
class Gb_Cpu {
public:
typedef int addr_t;
typedef BOOST::uint8_t byte;
enum { mem_size = 0x10000 };
// Clears registers and map all pages to unmapped
void reset( void* unmapped = NULL );
// Maps code memory (memory accessed via the program counter). Start and size
// must be multiple of page_size.
enum { page_bits = 13 };
enum { page_size = 1 << page_bits };
void map_code( addr_t start, int size, void* code );
// Accesses emulated memory as CPU does
byte* get_code( addr_t );
// Game Boy Z-80 registers. NOT kept updated during emulation.
struct core_regs_t {
BOOST::uint16_t bc, de, hl, fa;
};
struct registers_t : core_regs_t {
int pc; // more than 16 bits to allow overflow detection
BOOST::uint16_t sp;
};
registers_t r;
// Base address for RST vectors, to simplify GBS player (normally 0)
addr_t rst_base;
// Current time.
int time() const { return cpu_state->time; }
// Changes time. Must not be called during emulation.
// Should be negative, because emulation stops once it becomes >= 0.
void set_time( int t ) { cpu_state->time = t; }
// Emulator reads this many bytes past end of a page
enum { cpu_padding = 8 };
// Implementation
public:
Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; }
enum { page_count = mem_size >> page_bits };
struct cpu_state_t {
byte* code_map [page_count + 1];
int time;
};
cpu_state_t* cpu_state; // points to state_ or a local copy within run()
cpu_state_t cpu_state_;
private:
void set_code_page( int, void* );
};
#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits)
#if BLARGG_NONPORTABLE
#define GB_CPU_OFFSET( addr ) (addr)
#else
#define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1))
#endif
inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr )
{
return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr );
}
#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
// Gb_Snd_Emu $vers
#ifndef GB_OSCS_H
#define GB_OSCS_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
#ifndef GB_APU_OVERCLOCK
#define GB_APU_OVERCLOCK 1
#endif
#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1)
#error "GB_APU_OVERCLOCK must be a power of 2"
#endif
class Gb_Osc {
protected:
// 11-bit frequency in NRx3 and NRx4
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
void update_amp( blip_time_t, int new_amp );
int write_trig( int frame_phase, int max_len, int old_data );
public:
enum { clk_mul = GB_APU_OVERCLOCK };
enum { dac_bias = 7 };
Blip_Buffer* outputs [4];// NULL, right, left, center
Blip_Buffer* output; // where to output sound
BOOST::uint8_t* regs; // osc's 5 registers
int mode; // mode_dmg, mode_cgb, mode_agb
int dac_off_amp;// amplitude when DAC is off
int last_amp; // current amplitude in Blip_Buffer
Blip_Synth_Norm const* norm_synth;
Blip_Synth_Fast const* fast_synth;
int delay; // clocks until frequency timer expires
int length_ctr; // length counter
unsigned phase; // waveform phase (or equivalent)
bool enabled; // internal enabled flag
void clock_length();
void reset();
};
class Gb_Env : public Gb_Osc {
public:
int env_delay;
int volume;
bool env_enabled;
void clock_envelope();
bool write_register( int frame_phase, int reg, int old_data, int data );
void reset()
{
env_delay = 0;
volume = 0;
Gb_Osc::reset();
}
protected:
// Non-zero if DAC is enabled
int dac_enabled() const { return regs [2] & 0xF8; }
private:
void zombie_volume( int old, int data );
int reload_env_timer();
};
class Gb_Square : public Gb_Env {
public:
bool write_register( int frame_phase, int reg, int old_data, int data );
void run( blip_time_t, blip_time_t );
void reset()
{
Gb_Env::reset();
delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger)
}
private:
// Frequency timer period
int period() const { return (2048 - frequency()) * (4 * clk_mul); }
};
class Gb_Sweep_Square : public Gb_Square {
public:
int sweep_freq;
int sweep_delay;
bool sweep_enabled;
bool sweep_neg;
void clock_sweep();
void write_register( int frame_phase, int reg, int old_data, int data );
void reset()
{
sweep_freq = 0;
sweep_delay = 0;
sweep_enabled = false;
sweep_neg = false;
Gb_Square::reset();
}
private:
enum { period_mask = 0x70 };
enum { shift_mask = 0x07 };
void calc_sweep( bool update );
void reload_sweep_timer();
};
class Gb_Noise : public Gb_Env {
public:
int divider; // noise has more complex frequency divider setup
void run( blip_time_t, blip_time_t );
void write_register( int frame_phase, int reg, int old_data, int data );
void reset()
{
divider = 0;
Gb_Env::reset();
delay = 4 * clk_mul; // TODO: remove?
}
private:
enum { period2_mask = 0x1FFFF };
int period2_index() const { return regs [3] >> 4; }
int period2( int base = 8 ) const { return base << period2_index(); }
unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; }
};
class Gb_Wave : public Gb_Osc {
public:
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 run( blip_time_t, blip_time_t );
// Reads/writes wave RAM
int read( int addr ) const;
void write( int addr, int data );
void reset()
{
sample_buf = 0;
Gb_Osc::reset();
}
private:
enum { bank40_mask = 0x40 };
enum { bank_size = 32 };
int agb_mask; // 0xFF if AGB features enabled, 0 otherwise
BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU
friend class Gb_Apu;
// Frequency timer period
int period() const { return (2048 - frequency()) * (2 * clk_mul); }
// Non-zero if DAC is enabled
int dac_enabled() const { return regs [0] & 0x80; }
void corrupt_wave();
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
int access( int addr ) const;
};
inline int Gb_Wave::read( int addr ) const
{
int index = access( addr );
return (index < 0 ? 0xFF : wave_bank() [index]);
}
inline void Gb_Wave::write( int addr, int data )
{
int index = access( addr );
if ( index >= 0 )
wave_bank() [index] = data;;
}
#endif
// Private oscillators used by Gb_Apu
// Gb_Snd_Emu $vers
#ifndef GB_OSCS_H
#define GB_OSCS_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
#ifndef GB_APU_OVERCLOCK
#define GB_APU_OVERCLOCK 1
#endif
#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1)
#error "GB_APU_OVERCLOCK must be a power of 2"
#endif
class Gb_Osc {
protected:
// 11-bit frequency in NRx3 and NRx4
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
void update_amp( blip_time_t, int new_amp );
int write_trig( int frame_phase, int max_len, int old_data );
public:
enum { clk_mul = GB_APU_OVERCLOCK };
enum { dac_bias = 7 };
Blip_Buffer* outputs [4];// NULL, right, left, center
Blip_Buffer* output; // where to output sound
BOOST::uint8_t* regs; // osc's 5 registers
int mode; // mode_dmg, mode_cgb, mode_agb
int dac_off_amp;// amplitude when DAC is off
int last_amp; // current amplitude in Blip_Buffer
Blip_Synth_Norm const* norm_synth;
Blip_Synth_Fast const* fast_synth;
int delay; // clocks until frequency timer expires
int length_ctr; // length counter
unsigned phase; // waveform phase (or equivalent)
bool enabled; // internal enabled flag
void clock_length();
void reset();
};
class Gb_Env : public Gb_Osc {
public:
int env_delay;
int volume;
bool env_enabled;
void clock_envelope();
bool write_register( int frame_phase, int reg, int old_data, int data );
void reset()
{
env_delay = 0;
volume = 0;
Gb_Osc::reset();
}
protected:
// Non-zero if DAC is enabled
int dac_enabled() const { return regs [2] & 0xF8; }
private:
void zombie_volume( int old, int data );
int reload_env_timer();
};
class Gb_Square : public Gb_Env {
public:
bool write_register( int frame_phase, int reg, int old_data, int data );
void run( blip_time_t, blip_time_t );
void reset()
{
Gb_Env::reset();
delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger)
}
private:
// Frequency timer period
int period() const { return (2048 - frequency()) * (4 * clk_mul); }
};
class Gb_Sweep_Square : public Gb_Square {
public:
int sweep_freq;
int sweep_delay;
bool sweep_enabled;
bool sweep_neg;
void clock_sweep();
void write_register( int frame_phase, int reg, int old_data, int data );
void reset()
{
sweep_freq = 0;
sweep_delay = 0;
sweep_enabled = false;
sweep_neg = false;
Gb_Square::reset();
}
private:
enum { period_mask = 0x70 };
enum { shift_mask = 0x07 };
void calc_sweep( bool update );
void reload_sweep_timer();
};
class Gb_Noise : public Gb_Env {
public:
int divider; // noise has more complex frequency divider setup
void run( blip_time_t, blip_time_t );
void write_register( int frame_phase, int reg, int old_data, int data );
void reset()
{
divider = 0;
Gb_Env::reset();
delay = 4 * clk_mul; // TODO: remove?
}
private:
enum { period2_mask = 0x1FFFF };
int period2_index() const { return regs [3] >> 4; }
int period2( int base = 8 ) const { return base << period2_index(); }
unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; }
};
class Gb_Wave : public Gb_Osc {
public:
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 run( blip_time_t, blip_time_t );
// Reads/writes wave RAM
int read( int addr ) const;
void write( int addr, int data );
void reset()
{
sample_buf = 0;
Gb_Osc::reset();
}
private:
enum { bank40_mask = 0x40 };
enum { bank_size = 32 };
int agb_mask; // 0xFF if AGB features enabled, 0 otherwise
BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU
friend class Gb_Apu;
// Frequency timer period
int period() const { return (2048 - frequency()) * (2 * clk_mul); }
// Non-zero if DAC is enabled
int dac_enabled() const { return regs [0] & 0x80; }
void corrupt_wave();
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
int access( int addr ) const;
};
inline int Gb_Wave::read( int addr ) const
{
int index = access( addr );
return (index < 0 ? 0xFF : wave_bank() [index]);
}
inline void Gb_Wave::write( int addr, int data )
{
int index = access( addr );
if ( index >= 0 )
wave_bank() [index] = data;;
}
#endif

View file

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

View file

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

View file

@ -1,134 +1,134 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Core.h"
#include "blargg_endian.h"
//#include "gb_cpu_log.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef LOG_MEM
#define LOG_MEM( addr, str, data ) data
#endif
int Gbs_Core::read_mem( addr_t addr )
{
int result = *cpu.get_code( addr );
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
result = apu_.read_register( time(), addr );
#ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 )
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
#endif
return LOG_MEM( addr, ">", result );
}
inline void Gbs_Core::write_io_inline( int offset, int data, int base )
{
if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size )
apu_.write_register( time(), offset + base, data & 0xFF );
else if ( (unsigned) (offset - (0xFF06 - base)) < 2 )
update_timer();
else if ( offset == io_base - base )
ram [base - ram_addr + offset] = 0; // keep joypad return value 0
else
ram [base - ram_addr + offset] = 0xFF;
//if ( offset == 0xFFFF - base )
// dprintf( "Wrote interrupt mask\n" );
}
void Gbs_Core::write_mem( addr_t addr, int data )
{
(void) LOG_MEM( addr, "<", data );
int offset = addr - ram_addr;
if ( (unsigned) offset < 0x10000 - ram_addr )
{
ram [offset] = data;
offset -= 0xE000 - ram_addr;
if ( (unsigned) offset < 0x1F80 )
write_io_inline( offset, data, 0xE000 );
}
else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 )
{
set_bank( data & 0xFF );
}
#ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
{
dprintf( "Unmapped write $%04X\n", (unsigned) addr );
}
#endif
}
void Gbs_Core::write_io_( int offset, int data )
{
write_io_inline( offset, data, io_base );
}
inline void Gbs_Core::write_io( int offset, int data )
{
(void) LOG_MEM( offset + io_base, "<", data );
ram [io_base - ram_addr + offset] = data;
if ( (unsigned) offset < 0x80 )
write_io_( offset, data );
}
int Gbs_Core::read_io( int offset )
{
int const io_base = 0xFF00;
int result = ram [io_base - ram_addr + offset];
if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size )
{
result = apu_.read_register( time(), offset + io_base );
(void) LOG_MEM( offset + io_base, ">", result );
}
else
{
check( result == read_mem( offset + io_base ) );
}
return result;
}
#define READ_FAST( addr, out ) \
{\
out = READ_CODE( addr );\
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\
out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\
else\
check( out == read_mem( addr ) );\
}
#define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data )
#define WRITE_IO( addr, data ) write_io( addr, data )
#define READ_IO( addr, out ) out = read_io( addr )
#define CPU cpu
#define CPU_BEGIN \
void Gbs_Core::run_cpu()\
{
#include "Gb_Cpu_run.h"
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Core.h"
#include "blargg_endian.h"
//#include "gb_cpu_log.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef LOG_MEM
#define LOG_MEM( addr, str, data ) data
#endif
int Gbs_Core::read_mem( addr_t addr )
{
int result = *cpu.get_code( addr );
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
result = apu_.read_register( time(), addr );
#ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 )
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
#endif
return LOG_MEM( addr, ">", result );
}
inline void Gbs_Core::write_io_inline( int offset, int data, int base )
{
if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size )
apu_.write_register( time(), offset + base, data & 0xFF );
else if ( (unsigned) (offset - (0xFF06 - base)) < 2 )
update_timer();
else if ( offset == io_base - base )
ram [base - ram_addr + offset] = 0; // keep joypad return value 0
else
ram [base - ram_addr + offset] = 0xFF;
//if ( offset == 0xFFFF - base )
// dprintf( "Wrote interrupt mask\n" );
}
void Gbs_Core::write_mem( addr_t addr, int data )
{
(void) LOG_MEM( addr, "<", data );
int offset = addr - ram_addr;
if ( (unsigned) offset < 0x10000 - ram_addr )
{
ram [offset] = data;
offset -= 0xE000 - ram_addr;
if ( (unsigned) offset < 0x1F80 )
write_io_inline( offset, data, 0xE000 );
}
else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 )
{
set_bank( data & 0xFF );
}
#ifndef NDEBUG
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
{
dprintf( "Unmapped write $%04X\n", (unsigned) addr );
}
#endif
}
void Gbs_Core::write_io_( int offset, int data )
{
write_io_inline( offset, data, io_base );
}
inline void Gbs_Core::write_io( int offset, int data )
{
(void) LOG_MEM( offset + io_base, "<", data );
ram [io_base - ram_addr + offset] = data;
if ( (unsigned) offset < 0x80 )
write_io_( offset, data );
}
int Gbs_Core::read_io( int offset )
{
int const io_base = 0xFF00;
int result = ram [io_base - ram_addr + offset];
if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size )
{
result = apu_.read_register( time(), offset + io_base );
(void) LOG_MEM( offset + io_base, ">", result );
}
else
{
check( result == read_mem( offset + io_base ) );
}
return result;
}
#define READ_FAST( addr, out ) \
{\
out = READ_CODE( addr );\
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\
out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\
else\
check( out == read_mem( addr ) );\
}
#define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data )
#define WRITE_IO( addr, data ) write_io( addr, data )
#define READ_IO( addr, out ) out = read_io( addr )
#define CPU cpu
#define CPU_BEGIN \
void Gbs_Core::run_cpu()\
{
#include "Gb_Cpu_run.h"
}

View file

@ -1,167 +1,167 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Emu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#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::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::Gbs_Emu()
{
sound_hardware = sound_gbs;
enable_clicking( false );
set_type( gme_gbs_type );
set_silence_lookahead( 6 );
set_max_initial_silence( 21 );
set_gain( 1.2 );
// kind of midway between headphones and speaker
static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 };
set_equalizer( eq );
}
Gbs_Emu::~Gbs_Emu() { }
void Gbs_Emu::unload()
{
core_.unload();
Music_Emu::unload();
}
// Track info
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, author );
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 )
{
out.hash_( &h.vers, sizeof(h.vers) );
out.hash_( &h.track_count, sizeof(h.track_count) );
out.hash_( &h.first_track, sizeof(h.first_track) );
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) );
out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) );
out.hash_( &h.timer_mode, sizeof(h.timer_mode) );
out.hash_( data, data_size );
}
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
{
copy_gbs_fields( header(), out );
return blargg_ok;
}
struct Gbs_File : Gme_Info_
{
Gbs_Emu::header_t const* h;
Gbs_File() { set_type( gme_gbs_type ); }
blargg_err_t load_mem_( byte const begin [], int size )
{
h = ( Gbs_Emu::header_t * ) begin;
set_track_count( h->track_count );
if ( !h->valid_tag() )
return blargg_err_file_type;
return blargg_ok;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_gbs_fields( Gbs_Emu::header_t( *h ), out );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
return blargg_ok;
}
};
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
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 }};
// Setup
blargg_err_t Gbs_Emu::load_( Data_Reader& in )
{
RETURN_ERR( core_.load( in ) );
set_warning( core_.warning() );
set_track_count( header().track_count );
set_voice_count( Gb_Apu::osc_count );
core_.apu().volume( gain() );
static const char* const names [Gb_Apu::osc_count] = {
"Square 1", "Square 2", "Wave", "Noise"
};
set_voice_names( names );
static int const types [Gb_Apu::osc_count] = {
wave_type+1, wave_type+2, wave_type+3, mixed_type+1
};
set_voice_types( types );
return setup_buffer( 4194304 );
}
void Gbs_Emu::update_eq( blip_eq_t const& eq )
{
core_.apu().treble_eq( eq );
}
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
core_.apu().set_output( i, c, l, r );
}
void Gbs_Emu::set_tempo_( double t )
{
core_.set_tempo( t );
}
blargg_err_t Gbs_Emu::start_track_( int track )
{
sound_t mode = sound_hardware;
if ( mode == sound_gbs )
mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg;
RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) );
// clear buffer AFTER track is started, eliminating initial click
return Classic_Emu::start_track_( track );
}
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
{
return core_.end_frame( duration );
}
blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const
{
hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
return blargg_ok;
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Emu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#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::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::Gbs_Emu()
{
sound_hardware = sound_gbs;
enable_clicking( false );
set_type( gme_gbs_type );
set_silence_lookahead( 6 );
set_max_initial_silence( 21 );
set_gain( 1.2 );
// kind of midway between headphones and speaker
static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 };
set_equalizer( eq );
}
Gbs_Emu::~Gbs_Emu() { }
void Gbs_Emu::unload()
{
core_.unload();
Music_Emu::unload();
}
// Track info
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, author );
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 )
{
out.hash_( &h.vers, sizeof(h.vers) );
out.hash_( &h.track_count, sizeof(h.track_count) );
out.hash_( &h.first_track, sizeof(h.first_track) );
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) );
out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) );
out.hash_( &h.timer_mode, sizeof(h.timer_mode) );
out.hash_( data, data_size );
}
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
{
copy_gbs_fields( header(), out );
return blargg_ok;
}
struct Gbs_File : Gme_Info_
{
Gbs_Emu::header_t const* h;
Gbs_File() { set_type( gme_gbs_type ); }
blargg_err_t load_mem_( byte const begin [], int size )
{
h = ( Gbs_Emu::header_t * ) begin;
set_track_count( h->track_count );
if ( !h->valid_tag() )
return blargg_err_file_type;
return blargg_ok;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_gbs_fields( Gbs_Emu::header_t( *h ), out );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
return blargg_ok;
}
};
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
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 }};
// Setup
blargg_err_t Gbs_Emu::load_( Data_Reader& in )
{
RETURN_ERR( core_.load( in ) );
set_warning( core_.warning() );
set_track_count( header().track_count );
set_voice_count( Gb_Apu::osc_count );
core_.apu().volume( gain() );
static const char* const names [Gb_Apu::osc_count] = {
"Square 1", "Square 2", "Wave", "Noise"
};
set_voice_names( names );
static int const types [Gb_Apu::osc_count] = {
wave_type+1, wave_type+2, wave_type+3, mixed_type+1
};
set_voice_types( types );
return setup_buffer( 4194304 );
}
void Gbs_Emu::update_eq( blip_eq_t const& eq )
{
core_.apu().treble_eq( eq );
}
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
core_.apu().set_output( i, c, l, r );
}
void Gbs_Emu::set_tempo_( double t )
{
core_.set_tempo( t );
}
blargg_err_t Gbs_Emu::start_track_( int track )
{
sound_t mode = sound_hardware;
if ( mode == sound_gbs )
mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg;
RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) );
// clear buffer AFTER track is started, eliminating initial click
return Classic_Emu::start_track_( track );
}
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
{
return core_.end_frame( duration );
}
blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const
{
hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
return blargg_ok;
}

View file

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

View file

@ -1,86 +1,86 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gme_Loader.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Gme_Loader::unload()
{
file_begin_ = NULL;
file_end_ = NULL;
file_data.clear();
}
Gme_Loader::Gme_Loader()
{
warning_ = NULL;
Gme_Loader::unload();
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
}
Gme_Loader::~Gme_Loader() { }
blargg_err_t Gme_Loader::load_mem_( byte const data [], int size )
{
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
Mem_File_Reader in( data, size );
return load_( in );
}
inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size )
{
file_begin_ = data;
file_end_ = data + size;
return load_mem_( data, size );
}
blargg_err_t Gme_Loader::load_( Data_Reader& in )
{
RETURN_ERR( file_data.resize( in.remain() ) );
RETURN_ERR( in.read( 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 )
{
if ( err )
{
unload();
return err;
}
return post_load();
}
blargg_err_t Gme_Loader::load_mem( void const* in, long size )
{
pre_load();
return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) );
}
blargg_err_t Gme_Loader::load( Data_Reader& in )
{
pre_load();
return post_load_( load_( in ) );
}
blargg_err_t Gme_Loader::load_file( const char path [] )
{
pre_load();
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
return post_load_( load_( in ) );
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gme_Loader.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Gme_Loader::unload()
{
file_begin_ = NULL;
file_end_ = NULL;
file_data.clear();
}
Gme_Loader::Gme_Loader()
{
warning_ = NULL;
Gme_Loader::unload();
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
}
Gme_Loader::~Gme_Loader() { }
blargg_err_t Gme_Loader::load_mem_( byte const data [], int size )
{
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
Mem_File_Reader in( data, size );
return load_( in );
}
inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size )
{
file_begin_ = data;
file_end_ = data + size;
return load_mem_( data, size );
}
blargg_err_t Gme_Loader::load_( Data_Reader& in )
{
RETURN_ERR( file_data.resize( in.remain() ) );
RETURN_ERR( in.read( 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 )
{
if ( err )
{
unload();
return err;
}
return post_load();
}
blargg_err_t Gme_Loader::load_mem( void const* in, long size )
{
pre_load();
return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) );
}
blargg_err_t Gme_Loader::load( Data_Reader& in )
{
pre_load();
return post_load_( load_( in ) );
}
blargg_err_t Gme_Loader::load_file( const char path [] )
{
pre_load();
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
return post_load_( load_( in ) );
}

View file

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

View file

@ -1,361 +1,361 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Hes_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
bool const center_waves = true; // reduces asymmetry and clamping when starting notes
Hes_Apu::Hes_Apu()
{
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
osc->output [0] = NULL;
osc->output [1] = NULL;
osc->outputs [0] = NULL;
osc->outputs [1] = NULL;
osc->outputs [2] = NULL;
}
reset();
}
void Hes_Apu::reset()
{
latch = 0;
balance = 0xFF;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
memset( osc, 0, offsetof (Osc,output) );
osc->lfsr = 0;
osc->control = 0x40;
osc->balance = 0xFF;
}
// Only last two oscs support noise
oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR
oscs [osc_count - 1].lfsr = 0x200C3;
}
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)
require( !center || (center && !left && !right) || (center && left && right) );
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
if ( !center || !left || !right )
{
left = center;
right = center;
}
Osc& o = oscs [i];
o.outputs [0] = center;
o.outputs [1] = left;
o.outputs [2] = right;
balance_changed( o );
}
void Hes_Apu::run_osc( Blip_Synth_Fast& syn, Osc& o, blip_time_t end_time )
{
int vol0 = o.volume [0];
int vol1 = o.volume [1];
int dac = o.dac;
Blip_Buffer* out0 = o.output [0]; // cache often-used values
Blip_Buffer* out1 = o.output [1];
if ( !(o.control & 0x80) )
out0 = NULL;
if ( out0 )
{
// Update amplitudes
if ( out1 )
{
int delta = dac * vol1 - o.last_amp [1];
if ( delta )
{
syn.offset( o.last_time, delta, out1 );
out1->set_modified();
}
}
int delta = dac * vol0 - o.last_amp [0];
if ( delta )
{
syn.offset( o.last_time, delta, out0 );
out0->set_modified();
}
// Don't generate if silent
if ( !(vol0 | vol1) )
out0 = NULL;
}
// Generate noise
int noise = 0;
if ( o.lfsr )
{
noise = o.noise & 0x80;
blip_time_t time = o.last_time + o.noise_delay;
if ( time < end_time )
{
int period = (~o.noise & 0x1F) * 128;
if ( !period )
period = 64;
if ( noise && out0 )
{
unsigned lfsr = o.lfsr;
do
{
int new_dac = -(lfsr & 1);
lfsr = (lfsr >> 1) ^ (0x30061 & new_dac);
int delta = (new_dac &= 0x1F) - dac;
if ( delta )
{
dac = new_dac;
syn.offset( time, delta * vol0, out0 );
if ( out1 )
syn.offset( time, delta * vol1, out1 );
}
time += period;
}
while ( time < end_time );
if ( !lfsr )
{
lfsr = 1;
check( false );
}
o.lfsr = lfsr;
out0->set_modified();
if ( out1 )
out1->set_modified();
}
else
{
// Maintain phase when silent
int count = (end_time - time + period - 1) / period;
time += count * period;
// not worth it
//while ( count-- )
// o.lfsr = (o.lfsr >> 1) ^ (0x30061 * (o.lfsr & 1));
}
}
o.noise_delay = time - end_time;
}
// Generate wave
blip_time_t time = o.last_time + o.delay;
if ( time < end_time )
{
int phase = (o.phase + 1) & 0x1F; // pre-advance for optimal inner loop
int period = o.period * 2;
if ( period >= 14 && out0 && !((o.control & 0x40) | noise) )
{
do
{
int new_dac = o.wave [phase];
phase = (phase + 1) & 0x1F;
int delta = new_dac - dac;
if ( delta )
{
dac = new_dac;
syn.offset( time, delta * vol0, out0 );
if ( out1 )
syn.offset( time, delta * vol1, out1 );
}
time += period;
}
while ( time < end_time );
out0->set_modified();
if ( out1 )
out1->set_modified();
}
else
{
// Maintain phase when silent
int count = end_time - time;
if ( !period )
period = 1;
count = (count + period - 1) / period;
phase += count; // phase will be masked below
time += count * period;
}
// 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
// if channel is muted in player, but still has non-zero volume.
// City Hunter breaks when this check is removed.
if ( !(o.control & 0x40) && (vol0 | vol1) )
o.phase = (phase - 1) & 0x1F; // undo pre-advance
}
o.delay = time - end_time;
check( o.delay >= 0 );
o.last_time = end_time;
o.dac = dac;
o.last_amp [0] = dac * vol0;
o.last_amp [1] = dac * vol1;
}
void Hes_Apu::balance_changed( Osc& osc )
{
static short const log_table [32] = { // ~1.5 db per step
#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.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
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.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ),
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.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ),
#undef ENTRY
};
int vol = (osc.control & 0x1F) - 0x1E * 2;
int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E);
if ( left < 0 ) left = 0;
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
if ( right < 0 ) right = 0;
// optimizing for the common case of being centered also allows easy
// panning using Effects_Buffer
// Separate balance into center volume and additional on either left or right
osc.output [0] = osc.outputs [0]; // center
osc.output [1] = osc.outputs [2]; // right
int base = log_table [left ];
int side = log_table [right] - base;
if ( side < 0 )
{
base += side;
side = -side;
osc.output [1] = osc.outputs [1]; // left
}
// Optimize when output is far left, center, or far right
if ( !base || osc.output [0] == osc.output [1] )
{
base += side;
side = 0;
osc.output [0] = osc.output [1];
osc.output [1] = NULL;
osc.last_amp [1] = 0;
}
if ( center_waves )
{
// TODO: this can leave a non-zero level in a buffer (minor)
osc.last_amp [0] += (base - osc.volume [0]) * 16;
osc.last_amp [1] += (side - osc.volume [1]) * 16;
}
osc.volume [0] = base;
osc.volume [1] = side;
}
void Hes_Apu::write_data( blip_time_t time, int addr, int data )
{
if ( addr == 0x800 )
{
latch = data & 7;
}
else if ( addr == 0x801 )
{
if ( balance != data )
{
balance = data;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
run_osc( synth, *osc, time );
balance_changed( *oscs );
}
}
}
else if ( latch < osc_count )
{
Osc& osc = oscs [latch];
run_osc( synth, osc, time );
switch ( addr )
{
case 0x802:
osc.period = (osc.period & 0xF00) | data;
break;
case 0x803:
osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8);
break;
case 0x804:
if ( osc.control & 0x40 & ~data )
osc.phase = 0;
osc.control = data;
balance_changed( osc );
break;
case 0x805:
osc.balance = data;
balance_changed( osc );
break;
case 0x806:
data &= 0x1F;
if ( !(osc.control & 0x40) )
{
osc.wave [osc.phase] = data;
osc.phase = (osc.phase + 1) & 0x1F;
}
else if ( osc.control & 0x80 )
{
osc.dac = data;
}
break;
case 0x807:
osc.noise = data;
break;
case 0x809:
if ( !(data & 0x80) && (data & 0x03) != 0 )
dprintf( "HES LFO not supported\n" );
}
}
}
void Hes_Apu::end_frame( blip_time_t end_time )
{
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
if ( end_time > osc->last_time )
run_osc( synth, *osc, end_time );
osc->last_time -= end_time;
check( osc->last_time >= 0 );
}
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Hes_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
bool const center_waves = true; // reduces asymmetry and clamping when starting notes
Hes_Apu::Hes_Apu()
{
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
osc->output [0] = NULL;
osc->output [1] = NULL;
osc->outputs [0] = NULL;
osc->outputs [1] = NULL;
osc->outputs [2] = NULL;
}
reset();
}
void Hes_Apu::reset()
{
latch = 0;
balance = 0xFF;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
memset( osc, 0, offsetof (Osc,output) );
osc->lfsr = 0;
osc->control = 0x40;
osc->balance = 0xFF;
}
// Only last two oscs support noise
oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR
oscs [osc_count - 1].lfsr = 0x200C3;
}
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)
require( !center || (center && !left && !right) || (center && left && right) );
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
if ( !center || !left || !right )
{
left = center;
right = center;
}
Osc& o = oscs [i];
o.outputs [0] = center;
o.outputs [1] = left;
o.outputs [2] = right;
balance_changed( o );
}
void Hes_Apu::run_osc( Blip_Synth_Fast& syn, Osc& o, blip_time_t end_time )
{
int vol0 = o.volume [0];
int vol1 = o.volume [1];
int dac = o.dac;
Blip_Buffer* out0 = o.output [0]; // cache often-used values
Blip_Buffer* out1 = o.output [1];
if ( !(o.control & 0x80) )
out0 = NULL;
if ( out0 )
{
// Update amplitudes
if ( out1 )
{
int delta = dac * vol1 - o.last_amp [1];
if ( delta )
{
syn.offset( o.last_time, delta, out1 );
out1->set_modified();
}
}
int delta = dac * vol0 - o.last_amp [0];
if ( delta )
{
syn.offset( o.last_time, delta, out0 );
out0->set_modified();
}
// Don't generate if silent
if ( !(vol0 | vol1) )
out0 = NULL;
}
// Generate noise
int noise = 0;
if ( o.lfsr )
{
noise = o.noise & 0x80;
blip_time_t time = o.last_time + o.noise_delay;
if ( time < end_time )
{
int period = (~o.noise & 0x1F) * 128;
if ( !period )
period = 64;
if ( noise && out0 )
{
unsigned lfsr = o.lfsr;
do
{
int new_dac = -(lfsr & 1);
lfsr = (lfsr >> 1) ^ (0x30061 & new_dac);
int delta = (new_dac &= 0x1F) - dac;
if ( delta )
{
dac = new_dac;
syn.offset( time, delta * vol0, out0 );
if ( out1 )
syn.offset( time, delta * vol1, out1 );
}
time += period;
}
while ( time < end_time );
if ( !lfsr )
{
lfsr = 1;
check( false );
}
o.lfsr = lfsr;
out0->set_modified();
if ( out1 )
out1->set_modified();
}
else
{
// Maintain phase when silent
int count = (end_time - time + period - 1) / period;
time += count * period;
// not worth it
//while ( count-- )
// o.lfsr = (o.lfsr >> 1) ^ (0x30061 * (o.lfsr & 1));
}
}
o.noise_delay = time - end_time;
}
// Generate wave
blip_time_t time = o.last_time + o.delay;
if ( time < end_time )
{
int phase = (o.phase + 1) & 0x1F; // pre-advance for optimal inner loop
int period = o.period * 2;
if ( period >= 14 && out0 && !((o.control & 0x40) | noise) )
{
do
{
int new_dac = o.wave [phase];
phase = (phase + 1) & 0x1F;
int delta = new_dac - dac;
if ( delta )
{
dac = new_dac;
syn.offset( time, delta * vol0, out0 );
if ( out1 )
syn.offset( time, delta * vol1, out1 );
}
time += period;
}
while ( time < end_time );
out0->set_modified();
if ( out1 )
out1->set_modified();
}
else
{
// Maintain phase when silent
int count = end_time - time;
if ( !period )
period = 1;
count = (count + period - 1) / period;
phase += count; // phase will be masked below
time += count * period;
}
// 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
// if channel is muted in player, but still has non-zero volume.
// City Hunter breaks when this check is removed.
if ( !(o.control & 0x40) && (vol0 | vol1) )
o.phase = (phase - 1) & 0x1F; // undo pre-advance
}
o.delay = time - end_time;
check( o.delay >= 0 );
o.last_time = end_time;
o.dac = dac;
o.last_amp [0] = dac * vol0;
o.last_amp [1] = dac * vol1;
}
void Hes_Apu::balance_changed( Osc& osc )
{
static short const log_table [32] = { // ~1.5 db per step
#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.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
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.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ),
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.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ),
#undef ENTRY
};
int vol = (osc.control & 0x1F) - 0x1E * 2;
int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E);
if ( left < 0 ) left = 0;
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
if ( right < 0 ) right = 0;
// optimizing for the common case of being centered also allows easy
// panning using Effects_Buffer
// Separate balance into center volume and additional on either left or right
osc.output [0] = osc.outputs [0]; // center
osc.output [1] = osc.outputs [2]; // right
int base = log_table [left ];
int side = log_table [right] - base;
if ( side < 0 )
{
base += side;
side = -side;
osc.output [1] = osc.outputs [1]; // left
}
// Optimize when output is far left, center, or far right
if ( !base || osc.output [0] == osc.output [1] )
{
base += side;
side = 0;
osc.output [0] = osc.output [1];
osc.output [1] = NULL;
osc.last_amp [1] = 0;
}
if ( center_waves )
{
// TODO: this can leave a non-zero level in a buffer (minor)
osc.last_amp [0] += (base - osc.volume [0]) * 16;
osc.last_amp [1] += (side - osc.volume [1]) * 16;
}
osc.volume [0] = base;
osc.volume [1] = side;
}
void Hes_Apu::write_data( blip_time_t time, int addr, int data )
{
if ( addr == 0x800 )
{
latch = data & 7;
}
else if ( addr == 0x801 )
{
if ( balance != data )
{
balance = data;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
run_osc( synth, *osc, time );
balance_changed( *oscs );
}
}
}
else if ( latch < osc_count )
{
Osc& osc = oscs [latch];
run_osc( synth, osc, time );
switch ( addr )
{
case 0x802:
osc.period = (osc.period & 0xF00) | data;
break;
case 0x803:
osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8);
break;
case 0x804:
if ( osc.control & 0x40 & ~data )
osc.phase = 0;
osc.control = data;
balance_changed( osc );
break;
case 0x805:
osc.balance = data;
balance_changed( osc );
break;
case 0x806:
data &= 0x1F;
if ( !(osc.control & 0x40) )
{
osc.wave [osc.phase] = data;
osc.phase = (osc.phase + 1) & 0x1F;
}
else if ( osc.control & 0x80 )
{
osc.dac = data;
}
break;
case 0x807:
osc.noise = data;
break;
case 0x809:
if ( !(data & 0x80) && (data & 0x03) != 0 )
dprintf( "HES LFO not supported\n" );
}
}
}
void Hes_Apu::end_frame( blip_time_t end_time )
{
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
{
osc--;
if ( end_time > osc->last_time )
run_osc( synth, *osc, end_time );
osc->last_time -= end_time;
check( osc->last_time >= 0 );
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,123 +1,123 @@
// $package. http://www.slack.net/~ant/
#include "Hes_Cpu.h"
#include "blargg_endian.h"
#include "Hes_Core.h"
//#include "hes_cpu_log.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#define PAGE HES_CPU_PAGE
int Hes_Core::read_mem( addr_t addr )
{
check( addr < 0x10000 );
int result = *cpu.get_code( addr );
if ( cpu.mmr [PAGE( addr )] == 0xFF )
result = read_mem_( addr );
return result;
}
void Hes_Core::write_mem( addr_t addr, int data )
{
check( addr < 0x10000 );
byte* out = write_pages [PAGE( addr )];
if ( out )
out [addr & (cpu.page_size - 1)] = data;
else if ( cpu.mmr [PAGE( addr )] == 0xFF )
write_mem_( addr, data );
}
void Hes_Core::set_mmr( int page, int bank )
{
write_pages [page] = 0;
byte* data = rom.at_addr( bank * cpu.page_size );
if ( bank >= 0x80 )
{
data = 0;
switch ( bank )
{
case 0xF8:
data = ram;
break;
case 0xF9:
case 0xFA:
case 0xFB:
data = &sgx [(bank - 0xF9) * cpu.page_size];
break;
default:
if ( bank != 0xFF )
dprintf( "Unmapped bank $%02X\n", bank );
data = rom.unmapped();
goto end;
}
write_pages [page] = data;
}
end:
cpu.set_mmr( page, bank, data );
}
#define READ_FAST( addr, out ) \
{\
out = READ_CODE( addr );\
if ( CPU.mmr [PAGE( addr )] == 0xFF )\
{\
FLUSH_TIME();\
out = read_mem_( addr );\
CACHE_TIME();\
}\
}
#define WRITE_FAST( addr, data ) \
{\
int page = PAGE( addr );\
byte* out = write_pages [page];\
addr &= CPU.page_size - 1;\
if ( out )\
{\
out [addr] = data;\
}\
else if ( CPU.mmr [page] == 0xFF )\
{\
FLUSH_TIME();\
write_mem_( addr, data );\
CACHE_TIME();\
}\
}
#define READ_LOW( addr ) (ram [addr])
#define WRITE_LOW( addr, data ) (ram [addr] = data)
#define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( 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 SET_MMR( reg, bank ) set_mmr( reg, bank )
#define CPU cpu
#define IDLE_ADDR idle_addr
#define CPU_BEGIN \
bool Hes_Core::run_cpu( time_t end_time )\
{\
cpu.set_end_time( end_time );
#include "Hes_Cpu_run.h"
return illegal_encountered;
}
// $package. http://www.slack.net/~ant/
#include "Hes_Cpu.h"
#include "blargg_endian.h"
#include "Hes_Core.h"
//#include "hes_cpu_log.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#define PAGE HES_CPU_PAGE
int Hes_Core::read_mem( addr_t addr )
{
check( addr < 0x10000 );
int result = *cpu.get_code( addr );
if ( cpu.mmr [PAGE( addr )] == 0xFF )
result = read_mem_( addr );
return result;
}
void Hes_Core::write_mem( addr_t addr, int data )
{
check( addr < 0x10000 );
byte* out = write_pages [PAGE( addr )];
if ( out )
out [addr & (cpu.page_size - 1)] = data;
else if ( cpu.mmr [PAGE( addr )] == 0xFF )
write_mem_( addr, data );
}
void Hes_Core::set_mmr( int page, int bank )
{
write_pages [page] = 0;
byte* data = rom.at_addr( bank * cpu.page_size );
if ( bank >= 0x80 )
{
data = 0;
switch ( bank )
{
case 0xF8:
data = ram;
break;
case 0xF9:
case 0xFA:
case 0xFB:
data = &sgx [(bank - 0xF9) * cpu.page_size];
break;
default:
if ( bank != 0xFF )
dprintf( "Unmapped bank $%02X\n", bank );
data = rom.unmapped();
goto end;
}
write_pages [page] = data;
}
end:
cpu.set_mmr( page, bank, data );
}
#define READ_FAST( addr, out ) \
{\
out = READ_CODE( addr );\
if ( CPU.mmr [PAGE( addr )] == 0xFF )\
{\
FLUSH_TIME();\
out = read_mem_( addr );\
CACHE_TIME();\
}\
}
#define WRITE_FAST( addr, data ) \
{\
int page = PAGE( addr );\
byte* out = write_pages [page];\
addr &= CPU.page_size - 1;\
if ( out )\
{\
out [addr] = data;\
}\
else if ( CPU.mmr [page] == 0xFF )\
{\
FLUSH_TIME();\
write_mem_( addr, data );\
CACHE_TIME();\
}\
}
#define READ_LOW( addr ) (ram [addr])
#define WRITE_LOW( addr, data ) (ram [addr] = data)
#define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( 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 SET_MMR( reg, bank ) set_mmr( reg, bank )
#define CPU cpu
#define IDLE_ADDR idle_addr
#define CPU_BEGIN \
bool Hes_Core::run_cpu( time_t end_time )\
{\
cpu.set_end_time( end_time );
#include "Hes_Cpu_run.h"
return illegal_encountered;
}

View file

@ -1,139 +1,139 @@
// PC Engine CPU emulator for use with HES music files
// $package
#ifndef HES_CPU_H
#define HES_CPU_H
#include "blargg_common.h"
class Hes_Cpu {
public:
typedef BOOST::uint8_t byte;
typedef int time_t;
typedef int addr_t;
enum { future_time = INT_MAX/2 + 1 };
void reset();
enum { page_bits = 13 };
enum { page_size = 1 << page_bits };
enum { page_count = 0x10000 / page_size };
void set_mmr( int reg, int bank, void const* code );
byte const* get_code( addr_t );
// NOT kept updated during emulation.
struct registers_t {
BOOST::uint16_t pc;
byte a;
byte x;
byte y;
byte flags;
byte sp;
};
registers_t r;
// page mapping registers
byte mmr [page_count + 1];
// Time of beginning of next instruction to be executed
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 adjust_time( int delta ) { cpu_state->time += delta; }
// Clocks past end (negative if before)
int time_past_end() const { return cpu_state->time; }
// Time of next IRQ
time_t irq_time() const { return irq_time_; }
void set_irq_time( time_t );
// Emulation stops once time >= end_time
time_t end_time() const { return end_time_; }
void set_end_time( time_t );
// Subtracts t from all times
void end_frame( time_t t );
// Can read this many bytes past end of a page
enum { cpu_padding = 8 };
private:
// noncopyable
Hes_Cpu( const Hes_Cpu& );
Hes_Cpu& operator = ( const Hes_Cpu& );
// Implementation
public:
Hes_Cpu() { cpu_state = &cpu_state_; }
enum { irq_inhibit_mask = 0x04 };
struct cpu_state_t {
byte const* code_map [page_count + 1];
time_t base;
int time;
};
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
cpu_state_t cpu_state_;
time_t irq_time_;
time_t end_time_;
private:
void set_code_page( int, void const* );
inline void update_end_time( time_t end, time_t irq );
};
#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits)
#if BLARGG_NONPORTABLE
#define HES_CPU_OFFSET( addr ) (addr)
#else
#define HES_CPU_OFFSET( addr ) ((addr) & (Hes_Cpu::page_size - 1))
#endif
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 );
}
inline void Hes_Cpu::update_end_time( time_t end, time_t irq )
{
if ( end > irq && !(r.flags & irq_inhibit_mask) )
end = irq;
cpu_state->time += cpu_state->base - end;
cpu_state->base = end;
}
inline void Hes_Cpu::set_irq_time( time_t t )
{
irq_time_ = t;
update_end_time( end_time_, t );
}
inline void Hes_Cpu::set_end_time( time_t t )
{
end_time_ = t;
update_end_time( t, irq_time_ );
}
inline void Hes_Cpu::end_frame( time_t t )
{
assert( cpu_state == &cpu_state_ );
cpu_state_.base -= t;
if ( irq_time_ < future_time ) irq_time_ -= t;
if ( end_time_ < future_time ) end_time_ -= t;
}
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) bank < 0x100 );
mmr [reg] = bank;
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;
}
#endif
// PC Engine CPU emulator for use with HES music files
// $package
#ifndef HES_CPU_H
#define HES_CPU_H
#include "blargg_common.h"
class Hes_Cpu {
public:
typedef BOOST::uint8_t byte;
typedef int time_t;
typedef int addr_t;
enum { future_time = INT_MAX/2 + 1 };
void reset();
enum { page_bits = 13 };
enum { page_size = 1 << page_bits };
enum { page_count = 0x10000 / page_size };
void set_mmr( int reg, int bank, void const* code );
byte const* get_code( addr_t );
// NOT kept updated during emulation.
struct registers_t {
BOOST::uint16_t pc;
byte a;
byte x;
byte y;
byte flags;
byte sp;
};
registers_t r;
// page mapping registers
byte mmr [page_count + 1];
// Time of beginning of next instruction to be executed
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 adjust_time( int delta ) { cpu_state->time += delta; }
// Clocks past end (negative if before)
int time_past_end() const { return cpu_state->time; }
// Time of next IRQ
time_t irq_time() const { return irq_time_; }
void set_irq_time( time_t );
// Emulation stops once time >= end_time
time_t end_time() const { return end_time_; }
void set_end_time( time_t );
// Subtracts t from all times
void end_frame( time_t t );
// Can read this many bytes past end of a page
enum { cpu_padding = 8 };
private:
// noncopyable
Hes_Cpu( const Hes_Cpu& );
Hes_Cpu& operator = ( const Hes_Cpu& );
// Implementation
public:
Hes_Cpu() { cpu_state = &cpu_state_; }
enum { irq_inhibit_mask = 0x04 };
struct cpu_state_t {
byte const* code_map [page_count + 1];
time_t base;
int time;
};
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
cpu_state_t cpu_state_;
time_t irq_time_;
time_t end_time_;
private:
void set_code_page( int, void const* );
inline void update_end_time( time_t end, time_t irq );
};
#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits)
#if BLARGG_NONPORTABLE
#define HES_CPU_OFFSET( addr ) (addr)
#else
#define HES_CPU_OFFSET( addr ) ((addr) & (Hes_Cpu::page_size - 1))
#endif
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 );
}
inline void Hes_Cpu::update_end_time( time_t end, time_t irq )
{
if ( end > irq && !(r.flags & irq_inhibit_mask) )
end = irq;
cpu_state->time += cpu_state->base - end;
cpu_state->base = end;
}
inline void Hes_Cpu::set_irq_time( time_t t )
{
irq_time_ = t;
update_end_time( end_time_, t );
}
inline void Hes_Cpu::set_end_time( time_t t )
{
end_time_ = t;
update_end_time( t, irq_time_ );
}
inline void Hes_Cpu::end_frame( time_t t )
{
assert( cpu_state == &cpu_state_ );
cpu_state_.base -= t;
if ( irq_time_ < future_time ) irq_time_ -= t;
if ( end_time_ < future_time ) end_time_ -= t;
}
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) bank < 0x100 );
mmr [reg] = bank;
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;
}
#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
// Game_Music_Emu $vers
#ifndef HES_EMU_H
#define HES_EMU_H
#include "Classic_Emu.h"
#include "Hes_Core.h"
class Hes_Emu : public Classic_Emu {
public:
static gme_type_t static_type() { return gme_hes_type; }
// HES file header (see Hes_Core.h)
typedef Hes_Core::header_t header_t;
// Header for currently loaded file
header_t const& header() const { return core.header(); }
blargg_err_t hash_( Hash_Function& ) const;
// Implementation
public:
Hes_Emu();
~Hes_Emu();
virtual void unload();
protected:
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& );
private:
Hes_Core core;
};
#endif
// TurboGrafx-16/PC Engine HES music file emulator
// Game_Music_Emu $vers
#ifndef HES_EMU_H
#define HES_EMU_H
#include "Classic_Emu.h"
#include "Hes_Core.h"
class Hes_Emu : public Classic_Emu {
public:
static gme_type_t static_type() { return gme_hes_type; }
// HES file header (see Hes_Core.h)
typedef Hes_Core::header_t header_t;
// Header for currently loaded file
header_t const& header() const { return core.header(); }
blargg_err_t hash_( Hash_Function& ) const;
// Implementation
public:
Hes_Emu();
~Hes_Emu();
virtual void unload();
protected:
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int );
virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void update_eq( blip_eq_t const& );
private:
Hes_Core core;
};
#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/
#include "Kss_Core.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size )
{
memset( unmapped_read, 0xFF, sizeof unmapped_read );
}
Kss_Core::~Kss_Core() { }
void Kss_Core::unload()
{
rom.clear();
}
static blargg_err_t check_kss_header( void const* header )
{
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
return blargg_err_file_type;
return blargg_ok;
}
blargg_err_t Kss_Core::load_( Data_Reader& in )
{
memset( &header_, 0, sizeof header_ );
assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 );
RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) );
RETURN_ERR( check_kss_header( header_.tag ) );
header_.last_track [0] = 255;
if ( header_.tag [3] == 'C' )
{
if ( header_.extra_header )
{
header_.extra_header = 0;
set_warning( "Unknown data in header" );
}
if ( header_.device_flags & ~0x0F )
{
header_.device_flags &= 0x0F;
set_warning( "Unknown data in header" );
}
}
else if ( header_.extra_header )
{
if ( header_.extra_header != header_.ext_size )
{
header_.extra_header = 0;
set_warning( "Invalid extra_header_size" );
}
else
{
memcpy( header_.data_size, rom.begin(), header_.ext_size );
}
}
#ifndef NDEBUG
{
int ram_mode = header_.device_flags & 0x84; // MSX
if ( header_.device_flags & 0x02 ) // SMS
ram_mode = (header_.device_flags & 0x88);
if ( ram_mode )
dprintf( "RAM not supported\n" ); // TODO: support
}
#endif
return blargg_ok;
}
void Kss_Core::jsr( byte const (&addr) [2] )
{
ram [--cpu.r.sp] = idle_addr >> 8;
ram [--cpu.r.sp] = idle_addr & 0xFF;
cpu.r.pc = get_le16( addr );
}
blargg_err_t Kss_Core::start_track( int track )
{
memset( ram, 0xC9, 0x4000 );
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
// copy driver code to lo RAM
static byte const bios [] = {
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
};
static byte const vectors [] = {
0xC3, 0x01, 0x00, // $0093: WRTPSG vector
0xC3, 0x09, 0x00, // $0096: RDPSG vector
};
memcpy( ram + 0x01, bios, sizeof bios );
memcpy( ram + 0x93, vectors, sizeof vectors );
// copy non-banked data into RAM
int load_addr = get_le16( header_.load_addr );
int orig_load_size = get_le16( header_.load_size );
int load_size = min( orig_load_size, rom.file_size() );
load_size = min( load_size, (int) mem_size - load_addr );
if ( load_size != orig_load_size )
set_warning( "Excessive data size" );
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
rom.set_addr( -load_size - header_.extra_header );
// check available bank data
int const bank_size = this->bank_size();
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
bank_count = header_.bank_mode & 0x7F;
if ( bank_count > max_banks )
{
bank_count = max_banks;
set_warning( "Bank data missing" );
}
//dprintf( "load_size : $%X\n", load_size );
//dprintf( "bank_size : $%X\n", bank_size );
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
ram [idle_addr] = 0xFF;
cpu.reset( unmapped_write, unmapped_read );
cpu.map_mem( 0, mem_size, ram, ram );
cpu.r.sp = 0xF380;
cpu.r.b.a = track;
cpu.r.b.h = 0;
next_play = play_period;
gain_updated = false;
jsr( header_.init_addr );
return blargg_ok;
}
void Kss_Core::set_bank( int logical, int physical )
{
int const bank_size = this->bank_size();
int addr = 0x8000;
if ( logical && bank_size == 8 * 1024 )
addr = 0xA000;
physical -= header_.first_bank;
if ( (unsigned) physical >= (unsigned) bank_count )
{
byte* data = ram + addr;
cpu.map_mem( addr, bank_size, data, data );
}
else
{
int phys = physical * bank_size;
for ( int offset = 0; offset < bank_size; offset += cpu.page_size )
cpu.map_mem( addr + offset, cpu.page_size,
unmapped_write, rom.at_addr( phys + offset ) );
}
}
void Kss_Core::cpu_out( time_t, addr_t addr, int data )
{
dprintf( "OUT $%04X,$%02X\n", addr, data );
}
int Kss_Core::cpu_in( time_t, addr_t addr )
{
dprintf( "IN $%04X\n", addr );
return 0xFF;
}
blargg_err_t Kss_Core::end_frame( time_t end )
{
while ( cpu.time() < end )
{
time_t next = min( end, next_play );
run_cpu( next );
if ( cpu.r.pc == idle_addr )
cpu.set_time( next );
if ( cpu.time() >= next_play )
{
next_play += play_period;
if ( cpu.r.pc == idle_addr )
{
if ( !gain_updated )
{
gain_updated = true;
update_gain();
}
jsr( header_.play_addr );
}
}
}
next_play -= end;
check( next_play >= 0 );
cpu.adjust_time( -end );
return blargg_ok;
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Kss_Core.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size )
{
memset( unmapped_read, 0xFF, sizeof unmapped_read );
}
Kss_Core::~Kss_Core() { }
void Kss_Core::unload()
{
rom.clear();
}
static blargg_err_t check_kss_header( void const* header )
{
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
return blargg_err_file_type;
return blargg_ok;
}
blargg_err_t Kss_Core::load_( Data_Reader& in )
{
memset( &header_, 0, sizeof header_ );
assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 );
RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) );
RETURN_ERR( check_kss_header( header_.tag ) );
header_.last_track [0] = 255;
if ( header_.tag [3] == 'C' )
{
if ( header_.extra_header )
{
header_.extra_header = 0;
set_warning( "Unknown data in header" );
}
if ( header_.device_flags & ~0x0F )
{
header_.device_flags &= 0x0F;
set_warning( "Unknown data in header" );
}
}
else if ( header_.extra_header )
{
if ( header_.extra_header != header_.ext_size )
{
header_.extra_header = 0;
set_warning( "Invalid extra_header_size" );
}
else
{
memcpy( header_.data_size, rom.begin(), header_.ext_size );
}
}
#ifndef NDEBUG
{
int ram_mode = header_.device_flags & 0x84; // MSX
if ( header_.device_flags & 0x02 ) // SMS
ram_mode = (header_.device_flags & 0x88);
if ( ram_mode )
dprintf( "RAM not supported\n" ); // TODO: support
}
#endif
return blargg_ok;
}
void Kss_Core::jsr( byte const (&addr) [2] )
{
ram [--cpu.r.sp] = idle_addr >> 8;
ram [--cpu.r.sp] = idle_addr & 0xFF;
cpu.r.pc = get_le16( addr );
}
blargg_err_t Kss_Core::start_track( int track )
{
memset( ram, 0xC9, 0x4000 );
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
// copy driver code to lo RAM
static byte const bios [] = {
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
};
static byte const vectors [] = {
0xC3, 0x01, 0x00, // $0093: WRTPSG vector
0xC3, 0x09, 0x00, // $0096: RDPSG vector
};
memcpy( ram + 0x01, bios, sizeof bios );
memcpy( ram + 0x93, vectors, sizeof vectors );
// copy non-banked data into RAM
int load_addr = get_le16( header_.load_addr );
int orig_load_size = get_le16( header_.load_size );
int load_size = min( orig_load_size, rom.file_size() );
load_size = min( load_size, (int) mem_size - load_addr );
if ( load_size != orig_load_size )
set_warning( "Excessive data size" );
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
rom.set_addr( -load_size - header_.extra_header );
// check available bank data
int const bank_size = this->bank_size();
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
bank_count = header_.bank_mode & 0x7F;
if ( bank_count > max_banks )
{
bank_count = max_banks;
set_warning( "Bank data missing" );
}
//dprintf( "load_size : $%X\n", load_size );
//dprintf( "bank_size : $%X\n", bank_size );
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
ram [idle_addr] = 0xFF;
cpu.reset( unmapped_write, unmapped_read );
cpu.map_mem( 0, mem_size, ram, ram );
cpu.r.sp = 0xF380;
cpu.r.b.a = track;
cpu.r.b.h = 0;
next_play = play_period;
gain_updated = false;
jsr( header_.init_addr );
return blargg_ok;
}
void Kss_Core::set_bank( int logical, int physical )
{
int const bank_size = this->bank_size();
int addr = 0x8000;
if ( logical && bank_size == 8 * 1024 )
addr = 0xA000;
physical -= header_.first_bank;
if ( (unsigned) physical >= (unsigned) bank_count )
{
byte* data = ram + addr;
cpu.map_mem( addr, bank_size, data, data );
}
else
{
int phys = physical * bank_size;
for ( int offset = 0; offset < bank_size; offset += cpu.page_size )
cpu.map_mem( addr + offset, cpu.page_size,
unmapped_write, rom.at_addr( phys + offset ) );
}
}
void Kss_Core::cpu_out( time_t, addr_t addr, int data )
{
dprintf( "OUT $%04X,$%02X\n", addr, data );
}
int Kss_Core::cpu_in( time_t, addr_t addr )
{
dprintf( "IN $%04X\n", addr );
return 0xFF;
}
blargg_err_t Kss_Core::end_frame( time_t end )
{
while ( cpu.time() < end )
{
time_t next = min( end, next_play );
run_cpu( next );
if ( cpu.r.pc == idle_addr )
cpu.set_time( next );
if ( cpu.time() >= next_play )
{
next_play += play_period;
if ( cpu.r.pc == idle_addr )
{
if ( !gain_updated )
{
gain_updated = true;
update_gain();
}
jsr( header_.play_addr );
}
}
}
next_play -= end;
check( next_play >= 0 );
cpu.adjust_time( -end );
return blargg_ok;
}

View file

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

View file

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

View file

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

View file

@ -1,124 +1,124 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Kss_Scc_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division).
int const inaudible_freq = 16384;
int const wave_size = 0x20;
void Scc_Apu::set_output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, buf );
}
void Scc_Apu::volume( double v )
{
synth.volume( 0.43 / osc_count / amp_range * v );
}
void Scc_Apu::reset()
{
last_time = 0;
for ( int i = osc_count; --i >= 0; )
memset( &oscs [i], 0, offsetof (osc_t,output) );
memset( regs, 0, sizeof regs );
}
Scc_Apu::Scc_Apu()
{
set_output( NULL );
volume( 1.0 );
reset();
}
void Scc_Apu::run_until( blip_time_t end_time )
{
for ( int index = 0; index < osc_count; index++ )
{
osc_t& osc = oscs [index];
Blip_Buffer* const output = osc.output;
if ( !output )
continue;
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 +
regs [0xA0 + index * 2] + 1;
int volume = 0;
if ( regs [0xAF] & (1 << index) )
{
blip_time_t inaudible_period = (unsigned) (output->clock_rate() +
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16);
if ( period > inaudible_period )
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15);
}
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size;
/*if ( index == osc_count - 1 )
wave -= wave_size; // last two oscs share same wave RAM*/
{
int delta = wave [osc.phase] * volume - osc.last_amp;
if ( delta )
{
osc.last_amp += delta;
output->set_modified();
synth.offset( last_time, delta, output );
}
}
blip_time_t time = last_time + osc.delay;
if ( time < end_time )
{
int phase = osc.phase;
if ( !volume )
{
// maintain phase
int count = (end_time - time + period - 1) / period;
phase += count; // will be masked below
time += count * period;
}
else
{
int last_wave = wave [phase];
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
do
{
int delta = wave [phase] - last_wave;
phase = (phase + 1) & (wave_size - 1);
if ( delta )
{
last_wave += delta;
synth.offset_inline( time, delta * volume, output );
}
time += period;
}
while ( time < end_time );
osc.last_amp = last_wave * volume;
output->set_modified();
phase--; // undo pre-advance
}
osc.phase = phase & (wave_size - 1);
}
osc.delay = time - end_time;
}
last_time = end_time;
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Kss_Scc_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division).
int const inaudible_freq = 16384;
int const wave_size = 0x20;
void Scc_Apu::set_output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, buf );
}
void Scc_Apu::volume( double v )
{
synth.volume( 0.43 / osc_count / amp_range * v );
}
void Scc_Apu::reset()
{
last_time = 0;
for ( int i = osc_count; --i >= 0; )
memset( &oscs [i], 0, offsetof (osc_t,output) );
memset( regs, 0, sizeof regs );
}
Scc_Apu::Scc_Apu()
{
set_output( NULL );
volume( 1.0 );
reset();
}
void Scc_Apu::run_until( blip_time_t end_time )
{
for ( int index = 0; index < osc_count; index++ )
{
osc_t& osc = oscs [index];
Blip_Buffer* const output = osc.output;
if ( !output )
continue;
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 +
regs [0xA0 + index * 2] + 1;
int volume = 0;
if ( regs [0xAF] & (1 << index) )
{
blip_time_t inaudible_period = (unsigned) (output->clock_rate() +
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16);
if ( period > inaudible_period )
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15);
}
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size;
/*if ( index == osc_count - 1 )
wave -= wave_size; // last two oscs share same wave RAM*/
{
int delta = wave [osc.phase] * volume - osc.last_amp;
if ( delta )
{
osc.last_amp += delta;
output->set_modified();
synth.offset( last_time, delta, output );
}
}
blip_time_t time = last_time + osc.delay;
if ( time < end_time )
{
int phase = osc.phase;
if ( !volume )
{
// maintain phase
int count = (end_time - time + period - 1) / period;
phase += count; // will be masked below
time += count * period;
}
else
{
int last_wave = wave [phase];
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
do
{
int delta = wave [phase] - last_wave;
phase = (phase + 1) & (wave_size - 1);
if ( delta )
{
last_wave += delta;
synth.offset_inline( time, delta * volume, output );
}
time += period;
}
while ( time < end_time );
osc.last_amp = last_wave * volume;
output->set_modified();
phase--; // undo pre-advance
}
osc.phase = phase & (wave_size - 1);
}
osc.delay = time - end_time;
}
last_time = end_time;
}

View file

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

View file

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

View file

@ -220,7 +220,7 @@ void Music_Emu::set_fade( int start_msec, int length_msec )
fade_set = true;
this->length_msec = start_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) );
}

View file

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

View file

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

View file

@ -1,62 +1,62 @@
// $package. http://www.slack.net/~ant/
#include "Nes_Cpu.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
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 );
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 )
{
// address range must begin and end on page boundaries
require( start % page_size == 0 );
require( size % page_size == 0 );
require( start + size <= 0x10000 );
require( mirror_size % page_size == 0 );
for ( int offset = 0; offset < size; offset += page_size )
set_code_page( NES_CPU_PAGE( start + offset ),
STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) );
}
void Nes_Cpu::reset( void const* unmapped_page )
{
check( cpu_state == &cpu_state_ );
cpu_state = &cpu_state_;
r.flags = irq_inhibit_mask;
r.sp = 0xFF;
r.pc = 0;
r.a = 0;
r.x = 0;
r.y = 0;
cpu_state_.time = 0;
cpu_state_.base = 0;
irq_time_ = future_time;
end_time_ = future_time;
error_count_ = 0;
set_code_page( page_count, unmapped_page );
map_code( 0, 0x10000, unmapped_page, page_size );
blargg_verify_byte_order();
}
// $package. http://www.slack.net/~ant/
#include "Nes_Cpu.h"
#include "blargg_endian.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
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 );
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 )
{
// address range must begin and end on page boundaries
require( start % page_size == 0 );
require( size % page_size == 0 );
require( start + size <= 0x10000 );
require( mirror_size % page_size == 0 );
for ( int offset = 0; offset < size; offset += page_size )
set_code_page( NES_CPU_PAGE( start + offset ),
STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) );
}
void Nes_Cpu::reset( void const* unmapped_page )
{
check( cpu_state == &cpu_state_ );
cpu_state = &cpu_state_;
r.flags = irq_inhibit_mask;
r.sp = 0xFF;
r.pc = 0;
r.a = 0;
r.x = 0;
r.y = 0;
cpu_state_.time = 0;
cpu_state_.base = 0;
irq_time_ = future_time;
end_time_ = future_time;
error_count_ = 0;
set_code_page( page_count, unmapped_page );
map_code( 0, 0x10000, unmapped_page, page_size );
blargg_verify_byte_order();
}

View file

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

View file

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

View file

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

View file

@ -1,121 +1,121 @@
// $package. http://www.slack.net/~ant/
#include "Nes_Fme7_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Nes_Fme7_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
oscs [i].last_amp = 0;
fme7_apu_state_t* state = this;
memset( state, 0, sizeof *state );
}
unsigned char const Nes_Fme7_Apu::amp_table [16] =
{
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
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.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
#undef ENTRY
};
void Nes_Fme7_Apu::run_until( blip_time_t end_time )
{
require( end_time >= last_time );
for ( int index = 0; index < osc_count; index++ )
{
int mode = regs [7] >> index;
int vol_mode = regs [010 + index];
int volume = amp_table [vol_mode & 0x0F];
Blip_Buffer* const osc_output = oscs [index].output;
if ( !osc_output )
continue;
// check for unsupported mode
#ifndef NDEBUG
if ( (mode & 011) <= 001 && vol_mode & 0x1F )
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
mode, vol_mode & 0x1F );
#endif
if ( (mode & 001) | (vol_mode & 0x10) )
volume = 0; // noise and envelope aren't supported
// period
int const period_factor = 16;
unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor +
regs [index * 2] * period_factor;
if ( period < 50 ) // around 22 kHz
{
volume = 0;
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
period = period_factor;
}
// current amplitude
int amp = volume;
if ( !phases [index] )
amp = 0;
{
int delta = amp - oscs [index].last_amp;
if ( delta )
{
oscs [index].last_amp = amp;
osc_output->set_modified();
synth.offset( last_time, delta, osc_output );
}
}
blip_time_t time = last_time + delays [index];
if ( time < end_time )
{
int delta = amp * 2 - volume;
osc_output->set_modified();
if ( volume )
{
do
{
delta = -delta;
synth.offset_inline( time, delta, osc_output );
time += period;
}
while ( time < end_time );
oscs [index].last_amp = (delta + volume) >> 1;
phases [index] = (delta > 0);
}
else
{
// maintain phase when silent
int count = (end_time - time + period - 1) / period;
phases [index] ^= count & 1;
time += count * period;
}
}
delays [index] = time - end_time;
}
last_time = end_time;
}
// $package. http://www.slack.net/~ant/
#include "Nes_Fme7_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Nes_Fme7_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
oscs [i].last_amp = 0;
fme7_apu_state_t* state = this;
memset( state, 0, sizeof *state );
}
unsigned char const Nes_Fme7_Apu::amp_table [16] =
{
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
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.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
#undef ENTRY
};
void Nes_Fme7_Apu::run_until( blip_time_t end_time )
{
require( end_time >= last_time );
for ( int index = 0; index < osc_count; index++ )
{
int mode = regs [7] >> index;
int vol_mode = regs [010 + index];
int volume = amp_table [vol_mode & 0x0F];
Blip_Buffer* const osc_output = oscs [index].output;
if ( !osc_output )
continue;
// check for unsupported mode
#ifndef NDEBUG
if ( (mode & 011) <= 001 && vol_mode & 0x1F )
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
mode, vol_mode & 0x1F );
#endif
if ( (mode & 001) | (vol_mode & 0x10) )
volume = 0; // noise and envelope aren't supported
// period
int const period_factor = 16;
unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor +
regs [index * 2] * period_factor;
if ( period < 50 ) // around 22 kHz
{
volume = 0;
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
period = period_factor;
}
// current amplitude
int amp = volume;
if ( !phases [index] )
amp = 0;
{
int delta = amp - oscs [index].last_amp;
if ( delta )
{
oscs [index].last_amp = amp;
osc_output->set_modified();
synth.offset( last_time, delta, osc_output );
}
}
blip_time_t time = last_time + delays [index];
if ( time < end_time )
{
int delta = amp * 2 - volume;
osc_output->set_modified();
if ( volume )
{
do
{
delta = -delta;
synth.offset_inline( time, delta, osc_output );
time += period;
}
while ( time < end_time );
oscs [index].last_amp = (delta + volume) >> 1;
phases [index] = (delta > 0);
}
else
{
// maintain phase when silent
int count = (end_time - time + period - 1) / period;
phases [index] ^= count & 1;
time += count * period;
}
}
delays [index] = time - end_time;
}
last_time = end_time;
}

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,216 +1,216 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Vrc6_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, buf );
}
void Nes_Vrc6_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int j = 0; j < reg_count; j++ )
osc.regs [j] = 0;
osc.delay = 0;
osc.last_amp = 0;
osc.phase = 1;
osc.amp = 0;
}
}
Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{
set_output( NULL );
volume( 1.0 );
reset();
}
void Nes_Vrc6_Apu::run_until( blip_time_t time )
{
require( time >= last_time );
run_square( oscs [0], time );
run_square( oscs [1], time );
run_saw( time );
last_time = time;
}
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) reg < reg_count );
run_until( time );
oscs [osc_index].regs [reg] = data;
}
void Nes_Vrc6_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time;
}
void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
{
assert( sizeof (vrc6_apu_state_t) == 20 );
out->saw_amp = oscs [2].amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc const& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
out->regs [i] [r] = osc.regs [r];
out->delays [i] = osc.delay;
out->phases [i] = osc.phase;
}
}
void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in )
{
reset();
oscs [2].amp = in.saw_amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
osc.regs [r] = in.regs [i] [r];
osc.delay = in.delays [i];
osc.phase = in.phases [i];
}
if ( !oscs [2].phase )
oscs [2].phase = 1;
}
void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
{
Blip_Buffer* output = osc.output;
if ( !output )
return;
int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) )
volume = 0;
int gate = osc.regs [0] & 0x80;
int duty = ((osc.regs [0] >> 4) & 7) + 1;
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
blip_time_t time = last_time;
if ( delta )
{
osc.last_amp += delta;
output->set_modified();
square_synth.offset( time, delta, output );
}
time += osc.delay;
osc.delay = 0;
int period = osc.period();
if ( volume && !gate && period > 4 )
{
if ( time < end_time )
{
int phase = osc.phase;
output->set_modified();
do
{
phase++;
if ( phase == 16 )
{
phase = 0;
osc.last_amp = volume;
square_synth.offset( time, volume, output );
}
if ( phase == duty )
{
osc.last_amp = 0;
square_synth.offset( time, -volume, output );
}
time += period;
}
while ( time < end_time );
osc.phase = phase;
}
osc.delay = time - end_time;
}
}
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
{
Vrc6_Osc& osc = oscs [2];
Blip_Buffer* output = osc.output;
if ( !output )
return;
output->set_modified();
int amp = osc.amp;
int amp_step = osc.regs [0] & 0x3F;
blip_time_t time = last_time;
int last_amp = osc.last_amp;
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
{
osc.delay = 0;
int delta = (amp >> 3) - last_amp;
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
else
{
time += osc.delay;
if ( time < end_time )
{
int period = osc.period() * 2;
int phase = osc.phase;
do
{
if ( --phase == 0 )
{
phase = 7;
amp = 0;
}
int delta = (amp >> 3) - last_amp;
if ( delta )
{
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
time += period;
amp = (amp + amp_step) & 0xFF;
}
while ( time < end_time );
osc.phase = phase;
osc.amp = amp;
}
osc.delay = time - end_time;
}
osc.last_amp = last_amp;
}
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Vrc6_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, buf );
}
void Nes_Vrc6_Apu::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int j = 0; j < reg_count; j++ )
osc.regs [j] = 0;
osc.delay = 0;
osc.last_amp = 0;
osc.phase = 1;
osc.amp = 0;
}
}
Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{
set_output( NULL );
volume( 1.0 );
reset();
}
void Nes_Vrc6_Apu::run_until( blip_time_t time )
{
require( time >= last_time );
run_square( oscs [0], time );
run_square( oscs [1], time );
run_saw( time );
last_time = time;
}
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) reg < reg_count );
run_until( time );
oscs [osc_index].regs [reg] = data;
}
void Nes_Vrc6_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time;
}
void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
{
assert( sizeof (vrc6_apu_state_t) == 20 );
out->saw_amp = oscs [2].amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc const& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
out->regs [i] [r] = osc.regs [r];
out->delays [i] = osc.delay;
out->phases [i] = osc.phase;
}
}
void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in )
{
reset();
oscs [2].amp = in.saw_amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
osc.regs [r] = in.regs [i] [r];
osc.delay = in.delays [i];
osc.phase = in.phases [i];
}
if ( !oscs [2].phase )
oscs [2].phase = 1;
}
void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
{
Blip_Buffer* output = osc.output;
if ( !output )
return;
int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) )
volume = 0;
int gate = osc.regs [0] & 0x80;
int duty = ((osc.regs [0] >> 4) & 7) + 1;
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
blip_time_t time = last_time;
if ( delta )
{
osc.last_amp += delta;
output->set_modified();
square_synth.offset( time, delta, output );
}
time += osc.delay;
osc.delay = 0;
int period = osc.period();
if ( volume && !gate && period > 4 )
{
if ( time < end_time )
{
int phase = osc.phase;
output->set_modified();
do
{
phase++;
if ( phase == 16 )
{
phase = 0;
osc.last_amp = volume;
square_synth.offset( time, volume, output );
}
if ( phase == duty )
{
osc.last_amp = 0;
square_synth.offset( time, -volume, output );
}
time += period;
}
while ( time < end_time );
osc.phase = phase;
}
osc.delay = time - end_time;
}
}
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
{
Vrc6_Osc& osc = oscs [2];
Blip_Buffer* output = osc.output;
if ( !output )
return;
output->set_modified();
int amp = osc.amp;
int amp_step = osc.regs [0] & 0x3F;
blip_time_t time = last_time;
int last_amp = osc.last_amp;
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
{
osc.delay = 0;
int delta = (amp >> 3) - last_amp;
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
else
{
time += osc.delay;
if ( time < end_time )
{
int period = osc.period() * 2;
int phase = osc.phase;
do
{
if ( --phase == 0 )
{
phase = 7;
amp = 0;
}
int delta = (amp >> 3) - last_amp;
if ( delta )
{
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
time += period;
amp = (amp + amp_step) & 0xFF;
}
while ( time < end_time );
osc.phase = phase;
osc.amp = amp;
}
osc.delay = time - end_time;
}
osc.last_amp = last_amp;
}

View file

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

View file

@ -1,10 +1,18 @@
#include "Nes_Vrc7_Apu.h"
#include "ym2413.h"
extern "C" {
#include "../vgmplay/chips/emu2413.h"
}
#include <string.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
Nes_Vrc7_Apu::Nes_Vrc7_Apu()
@ -14,8 +22,10 @@ Nes_Vrc7_Apu::Nes_Vrc7_Apu()
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 );
volume( 1.0 );
reset();
@ -25,7 +35,7 @@ blargg_err_t Nes_Vrc7_Apu::init()
Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
{
if ( opll )
ym2413_shutdown( opll );
OPLL_delete( (OPLL *) opll );
}
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
@ -46,7 +56,7 @@ void Nes_Vrc7_Apu::output_changed()
break;
}
}
if ( mono.output )
{
for ( int i = osc_count; --i; )
@ -62,7 +72,7 @@ void Nes_Vrc7_Apu::reset()
addr = 0;
next_time = 0;
mono.last_amp = 0;
for ( int i = osc_count; --i >= 0; )
{
Vrc7_Osc& osc = oscs [i];
@ -71,7 +81,7 @@ void Nes_Vrc7_Apu::reset()
osc.regs [j] = 0;
}
ym2413_reset_chip( opll );
OPLL_reset( (OPLL *) opll );
}
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;
if ( (unsigned) type < 3 && chan < osc_count )
oscs [chan].regs [type] = data;
if ( addr < 0x08 )
inst [addr] = data;
if ( time > next_time )
run_until( time );
ym2413_write( opll, 0, addr );
ym2413_write( opll, 1, data );
OPLL_writeIO( (OPLL *) opll, 0, addr );
OPLL_writeIO( (OPLL *) opll, 1, data );
}
void Nes_Vrc7_Apu::end_frame( blip_time_t time )
{
if ( time > next_time )
run_until( time );
next_time -= time;
assert( next_time >= 0 );
for ( int i = osc_count; --i >= 0; )
{
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 )
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 )
{
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
reset();
next_time = in.delay;
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];
}
memcpy( inst, in.inst, 8 );
for ( i = 0; i < 8; ++i )
{
ym2413_write( opll, 0, i );
ym2413_write( opll, 1, in.inst [i] );
OPLL_writeIO( (OPLL *) opll, 0, i );
OPLL_writeIO( (OPLL *) opll, 1, in.inst [i] );
}
for ( i = 0; i < 3; ++i )
{
for ( int j = 0; j < 6; ++j )
{
ym2413_write( opll, 0, 0x10 + i * 0x10 + j );
ym2413_write( opll, 1, oscs [j].regs [i] );
OPLL_writeIO( (OPLL *) opll, 0, 0x10 + i * 0x10 + j );
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;
void* opll = this->opll; // cache
Blip_Buffer* const mono_output = mono.output;
e_int32 buffer [2];
e_int32* buffers[2] = {&buffer[0], &buffer[1]};
if ( mono_output )
{
// optimal case
do
{
ym2413_advance_lfo( opll );
int amp = 0;
for ( int i = 0; i < osc_count; i++ )
amp += ym2413_calcch( opll, i );
ym2413_advance( opll );
OPLL_calc_stereo( (OPLL *) opll, buffers, 1, -1 );
int amp = buffer [0] + buffer [1];
int delta = amp - mono.last_amp;
if ( delta )
{
@ -182,13 +194,14 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
mono.last_amp = 0;
do
{
ym2413_advance_lfo( opll );
OPLL_advance( (OPLL *) opll );
for ( int i = 0; i < osc_count; ++i )
{
Vrc7_Osc& osc = oscs [i];
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;
if ( delta )
{
@ -197,7 +210,6 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
}
}
}
ym2413_advance( opll );
time += period;
}
while ( time < end_time );

View file

@ -1,80 +1,82 @@
// Konami VRC7 sound chip emulator
#ifndef NES_VRC7_APU_H
#define NES_VRC7_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct vrc7_snapshot_t;
class Nes_Vrc7_Apu {
public:
blargg_err_t init();
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* );
enum { osc_count = 6 };
void set_output( int index, Blip_Buffer* );
void end_frame( blip_time_t );
void save_snapshot( vrc7_snapshot_t* ) const;
void load_snapshot( vrc7_snapshot_t const& );
void write_reg( int reg );
void write_data( blip_time_t, int data );
public:
Nes_Vrc7_Apu();
~Nes_Vrc7_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
struct Vrc7_Osc
{
BOOST::uint8_t regs [3];
Blip_Buffer* output;
int last_amp;
};
Vrc7_Osc oscs [osc_count];
void* opll;
int addr;
blip_time_t next_time;
struct {
Blip_Buffer* output;
int last_amp;
} mono;
Blip_Synth_Fast synth;
void run_until( blip_time_t );
void output_changed();
};
struct vrc7_snapshot_t
{
BOOST::uint8_t latch;
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 );
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 ); }
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
#endif
// Konami VRC7 sound chip emulator
#ifndef NES_VRC7_APU_H
#define NES_VRC7_APU_H
#include "blargg_common.h"
#include "Blip_Buffer.h"
struct vrc7_snapshot_t;
class Nes_Vrc7_Apu {
public:
blargg_err_t init();
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* );
enum { osc_count = 6 };
void set_output( int index, Blip_Buffer* );
void end_frame( blip_time_t );
void save_snapshot( vrc7_snapshot_t* ) const;
void load_snapshot( vrc7_snapshot_t const& );
void write_reg( int reg );
void write_data( blip_time_t, int data );
public:
Nes_Vrc7_Apu();
~Nes_Vrc7_Apu();
BLARGG_DISABLE_NOTHROW
private:
// noncopyable
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
struct Vrc7_Osc
{
BOOST::uint8_t regs [3];
Blip_Buffer* output;
int last_amp;
};
Vrc7_Osc oscs [osc_count];
BOOST::uint8_t kon;
BOOST::uint8_t inst [8];
void* opll;
int addr;
blip_time_t next_time;
struct {
Blip_Buffer* output;
int last_amp;
} mono;
Blip_Synth_Fast synth;
void run_until( blip_time_t );
void output_changed();
};
struct vrc7_snapshot_t
{
BOOST::uint8_t latch;
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 );
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 ); }
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/
#include "Nsf_Core.h"
#include "blargg_endian.h"
#if !NSF_EMU_APU_ONLY
#include "Nes_Namco_Apu.h"
#include "Nes_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h"
#include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h"
#endif
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Nsf_Core::Nsf_Core()
{
fds = NULL;
fme7 = NULL;
mmc5 = NULL;
namco = NULL;
vrc6 = NULL;
vrc7 = NULL;
}
Nsf_Core::~Nsf_Core()
{
unload();
}
void Nsf_Core::unload()
{
#if !NSF_EMU_APU_ONLY
delete fds;
fds = NULL;
delete fme7;
fme7 = NULL;
delete namco;
namco = NULL;
delete mmc5;
mmc5 = NULL;
delete vrc6;
vrc6 = NULL;
delete vrc7;
vrc7 = NULL;
#endif
Nsf_Impl::unload();
}
void Nsf_Core::set_tempo( double t )
{
set_play_period( (int) (header().play_period() / t) );
nes_apu()->set_tempo( t );
#if !NSF_EMU_APU_ONLY
if ( fds )
fds->set_tempo( t );
#endif
}
blargg_err_t Nsf_Core::post_load()
{
int chip_flags = header().chip_flags;
#if !NSF_EMU_APU_ONLY
if ( chip_flags & header_t::fds_mask )
CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
if ( chip_flags & header_t::fme7_mask )
CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
if ( chip_flags & header_t::mmc5_mask )
CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
if ( chip_flags & header_t::namco_mask )
CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
if ( chip_flags & header_t::vrc6_mask )
CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
if ( chip_flags & header_t::vrc7_mask )
{
#if NSF_EMU_NO_VRC7
chip_flags = ~chips_mask; // give warning rather than error
#else
CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
RETURN_ERR( vrc7->init() );
#endif
}
#endif
set_tempo( 1.0 );
if ( chip_flags & ~chips_mask )
set_warning( "Uses unsupported audio expansion hardware" );
return Nsf_Impl::post_load();
}
int Nsf_Core::cpu_read( addr_t addr )
{
#if !NSF_EMU_APU_ONLY
{
if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
return namco->read_data();
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
return fds->read( time(), addr );
int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size && mmc5 )
return mmc5->exram [i];
int m = addr - 0x5205;
if ( (unsigned) m < 2 && mmc5 )
return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
}
#endif
return Nsf_Impl::cpu_read( addr );
}
int Nsf_Core::unmapped_read( addr_t addr )
{
switch ( addr )
{
case 0x2002:
case 0x4016:
case 0x4017:
return addr >> 8;
}
return Nsf_Impl::unmapped_read( addr );
}
void Nsf_Core::cpu_write( addr_t addr, int data )
{
#if !NSF_EMU_APU_ONLY
{
if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
{
fds->write( time(), addr, data );
return;
}
if ( namco )
{
if ( addr == namco->addr_reg_addr )
{
namco->write_addr( data );
return;
}
if ( addr == namco->data_reg_addr )
{
namco->write_data( time(), data );
return;
}
}
if ( vrc6 )
{
int reg = addr & (vrc6->addr_step - 1);
int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
{
vrc6->write_osc( time(), osc, reg, data );
return;
}
}
if ( addr >= fme7->latch_addr && fme7 )
{
switch ( addr & fme7->addr_mask )
{
case Nes_Fme7_Apu::latch_addr:
fme7->write_latch( data );
return;
case Nes_Fme7_Apu::data_addr:
fme7->write_data( time(), data );
return;
}
}
if ( mmc5 )
{
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
{
mmc5->write_register( time(), addr, data );
return;
}
int m = addr - 0x5205;
if ( (unsigned) m < 2 )
{
mmc5_mul [m] = data;
return;
}
int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size )
{
mmc5->exram [i] = data;
return;
}
}
if ( vrc7 )
{
if ( addr == 0x9010 )
{
vrc7->write_reg( data );
return;
}
if ( (unsigned) (addr - 0x9028) <= 0x08 )
{
vrc7->write_data( time(), data );
return;
}
}
}
#endif
return Nsf_Impl::cpu_write( addr, data );
}
void Nsf_Core::unmapped_write( addr_t addr, int data )
{
switch ( addr )
{
case 0x8000: // some write to $8000 and $8001 repeatedly
case 0x8001:
case 0x4800: // probably namco sound mistakenly turned on in MCK
case 0xF800:
case 0xFFF8: // memory mapper?
return;
}
if ( mmc5 && addr == 0x5115 ) return;
// FDS memory
if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;
Nsf_Impl::unmapped_write( addr, data );
}
blargg_err_t Nsf_Core::start_track( int track )
{
#if !NSF_EMU_APU_ONLY
if ( mmc5 )
{
mmc5_mul [0] = 0;
mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size );
}
#endif
#if !NSF_EMU_APU_ONLY
if ( fds ) fds ->reset();
if ( fme7 ) fme7 ->reset();
if ( mmc5 ) mmc5 ->reset();
if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset();
if ( vrc7 ) vrc7 ->reset();
#endif
return Nsf_Impl::start_track( track );
}
void Nsf_Core::end_frame( time_t end )
{
Nsf_Impl::end_frame( end );
#if !NSF_EMU_APU_ONLY
if ( fds ) fds ->end_frame( end );
if ( fme7 ) fme7 ->end_frame( end );
if ( mmc5 ) mmc5 ->end_frame( end );
if ( namco ) namco->end_frame( end );
if ( vrc6 ) vrc6 ->end_frame( end );
if ( vrc7 ) vrc7 ->end_frame( end );
#endif
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Nsf_Core.h"
#include "blargg_endian.h"
#if !NSF_EMU_APU_ONLY
#include "Nes_Namco_Apu.h"
#include "Nes_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h"
#include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h"
#endif
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Nsf_Core::Nsf_Core()
{
fds = NULL;
fme7 = NULL;
mmc5 = NULL;
namco = NULL;
vrc6 = NULL;
vrc7 = NULL;
}
Nsf_Core::~Nsf_Core()
{
unload();
}
void Nsf_Core::unload()
{
#if !NSF_EMU_APU_ONLY
delete fds;
fds = NULL;
delete fme7;
fme7 = NULL;
delete namco;
namco = NULL;
delete mmc5;
mmc5 = NULL;
delete vrc6;
vrc6 = NULL;
delete vrc7;
vrc7 = NULL;
#endif
Nsf_Impl::unload();
}
void Nsf_Core::set_tempo( double t )
{
set_play_period( (int) (header().play_period() / t) );
nes_apu()->set_tempo( t );
#if !NSF_EMU_APU_ONLY
if ( fds )
fds->set_tempo( t );
#endif
}
blargg_err_t Nsf_Core::post_load()
{
int chip_flags = header().chip_flags;
#if !NSF_EMU_APU_ONLY
if ( chip_flags & header_t::fds_mask )
CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
if ( chip_flags & header_t::fme7_mask )
CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
if ( chip_flags & header_t::mmc5_mask )
CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
if ( chip_flags & header_t::namco_mask )
CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
if ( chip_flags & header_t::vrc6_mask )
CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
if ( chip_flags & header_t::vrc7_mask )
{
#if NSF_EMU_NO_VRC7
chip_flags = ~chips_mask; // give warning rather than error
#else
CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
RETURN_ERR( vrc7->init() );
#endif
}
#endif
set_tempo( 1.0 );
if ( chip_flags & ~chips_mask )
set_warning( "Uses unsupported audio expansion hardware" );
return Nsf_Impl::post_load();
}
int Nsf_Core::cpu_read( addr_t addr )
{
#if !NSF_EMU_APU_ONLY
{
if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
return namco->read_data();
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
return fds->read( time(), addr );
int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size && mmc5 )
return mmc5->exram [i];
int m = addr - 0x5205;
if ( (unsigned) m < 2 && mmc5 )
return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
}
#endif
return Nsf_Impl::cpu_read( addr );
}
int Nsf_Core::unmapped_read( addr_t addr )
{
switch ( addr )
{
case 0x2002:
case 0x4016:
case 0x4017:
return addr >> 8;
}
return Nsf_Impl::unmapped_read( addr );
}
void Nsf_Core::cpu_write( addr_t addr, int data )
{
#if !NSF_EMU_APU_ONLY
{
if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
{
fds->write( time(), addr, data );
return;
}
if ( namco )
{
if ( addr == namco->addr_reg_addr )
{
namco->write_addr( data );
return;
}
if ( addr == namco->data_reg_addr )
{
namco->write_data( time(), data );
return;
}
}
if ( vrc6 )
{
int reg = addr & (vrc6->addr_step - 1);
int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
{
vrc6->write_osc( time(), osc, reg, data );
return;
}
}
if ( addr >= fme7->latch_addr && fme7 )
{
switch ( addr & fme7->addr_mask )
{
case Nes_Fme7_Apu::latch_addr:
fme7->write_latch( data );
return;
case Nes_Fme7_Apu::data_addr:
fme7->write_data( time(), data );
return;
}
}
if ( mmc5 )
{
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
{
mmc5->write_register( time(), addr, data );
return;
}
int m = addr - 0x5205;
if ( (unsigned) m < 2 )
{
mmc5_mul [m] = data;
return;
}
int i = addr - 0x5C00;
if ( (unsigned) i < mmc5->exram_size )
{
mmc5->exram [i] = data;
return;
}
}
if ( vrc7 )
{
if ( addr == 0x9010 )
{
vrc7->write_reg( data );
return;
}
if ( (unsigned) (addr - 0x9028) <= 0x08 )
{
vrc7->write_data( time(), data );
return;
}
}
}
#endif
return Nsf_Impl::cpu_write( addr, data );
}
void Nsf_Core::unmapped_write( addr_t addr, int data )
{
switch ( addr )
{
case 0x8000: // some write to $8000 and $8001 repeatedly
case 0x8001:
case 0x4800: // probably namco sound mistakenly turned on in MCK
case 0xF800:
case 0xFFF8: // memory mapper?
return;
}
if ( mmc5 && addr == 0x5115 ) return;
// FDS memory
if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;
Nsf_Impl::unmapped_write( addr, data );
}
blargg_err_t Nsf_Core::start_track( int track )
{
#if !NSF_EMU_APU_ONLY
if ( mmc5 )
{
mmc5_mul [0] = 0;
mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size );
}
#endif
#if !NSF_EMU_APU_ONLY
if ( fds ) fds ->reset();
if ( fme7 ) fme7 ->reset();
if ( mmc5 ) mmc5 ->reset();
if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset();
if ( vrc7 ) vrc7 ->reset();
#endif
return Nsf_Impl::start_track( track );
}
void Nsf_Core::end_frame( time_t end )
{
Nsf_Impl::end_frame( end );
#if !NSF_EMU_APU_ONLY
if ( fds ) fds ->end_frame( end );
if ( fme7 ) fme7 ->end_frame( end );
if ( mmc5 ) mmc5 ->end_frame( end );
if ( namco ) namco->end_frame( end );
if ( vrc6 ) vrc6 ->end_frame( end );
if ( vrc7 ) vrc7 ->end_frame( end );
#endif
}

View file

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

View file

@ -1,116 +1,116 @@
// Normal CPU for NSF emulator
// $package. http://www.slack.net/~ant/
#include "Nsf_Impl.h"
#include "blargg_endian.h"
#ifdef BLARGG_DEBUG_H
//#define CPU_LOG_START 1000000
//#include "nes_cpu_log.h"
#undef LOG_MEM
#endif
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef LOG_MEM
#define LOG_MEM( addr, str, data ) data
#endif
int Nsf_Impl::read_mem( addr_t addr )
{
int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around
if ( addr & 0xE000 )
{
result = *cpu.get_code( addr );
if ( addr < sram_addr )
{
if ( addr == apu.status_addr )
result = apu.read_status( time() );
else
result = cpu_read( addr );
}
}
return LOG_MEM( addr, ">", result );
}
void Nsf_Impl::write_mem( addr_t addr, int data )
{
(void) LOG_MEM( addr, "<", data );
int offset = addr - sram_addr;
if ( (unsigned) offset < sram_size )
{
sram() [offset] = data;
}
else
{
// after sram because CPU handles most low_ram accesses internally already
int temp = addr & (low_ram_size-1); // also handles wrap-around
if ( !(addr & 0xE000) )
{
low_ram [temp] = data;
}
else
{
int bank = addr - banks_addr;
if ( (unsigned) bank < bank_count )
{
write_bank( bank, data );
}
else if ( (unsigned) (addr - apu.io_addr) < apu.io_size )
{
apu.write_register( time(), addr, data );
}
else
{
#if !NSF_EMU_APU_ONLY
// 0x8000-0xDFFF is writable
int i = addr - 0x8000;
if ( (unsigned) i < fdsram_size && fds_enabled() )
fdsram() [i] = data;
else
#endif
cpu_write( addr, data );
}
}
}
}
#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] ))
#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data ))
#define CAN_WRITE_FAST( addr ) (addr < low_ram_size)
#define WRITE_FAST WRITE_LOW
// addr < 0x2000 || addr >= 0x8000
#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000)
#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) ))
#define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data )
#define CPU cpu
#define CPU_BEGIN \
bool Nsf_Impl::run_cpu_until( time_t end )\
{\
cpu.set_end_time( end );\
if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\
{
#include "Nes_Cpu_run.h"
}
return cpu.time_past_end() < 0;
}
// Normal CPU for NSF emulator
// $package. http://www.slack.net/~ant/
#include "Nsf_Impl.h"
#include "blargg_endian.h"
#ifdef BLARGG_DEBUG_H
//#define CPU_LOG_START 1000000
//#include "nes_cpu_log.h"
#undef LOG_MEM
#endif
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#ifndef LOG_MEM
#define LOG_MEM( addr, str, data ) data
#endif
int Nsf_Impl::read_mem( addr_t addr )
{
int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around
if ( addr & 0xE000 )
{
result = *cpu.get_code( addr );
if ( addr < sram_addr )
{
if ( addr == apu.status_addr )
result = apu.read_status( time() );
else
result = cpu_read( addr );
}
}
return LOG_MEM( addr, ">", result );
}
void Nsf_Impl::write_mem( addr_t addr, int data )
{
(void) LOG_MEM( addr, "<", data );
int offset = addr - sram_addr;
if ( (unsigned) offset < sram_size )
{
sram() [offset] = data;
}
else
{
// after sram because CPU handles most low_ram accesses internally already
int temp = addr & (low_ram_size-1); // also handles wrap-around
if ( !(addr & 0xE000) )
{
low_ram [temp] = data;
}
else
{
int bank = addr - banks_addr;
if ( (unsigned) bank < bank_count )
{
write_bank( bank, data );
}
else if ( (unsigned) (addr - apu.io_addr) < apu.io_size )
{
apu.write_register( time(), addr, data );
}
else
{
#if !NSF_EMU_APU_ONLY
// 0x8000-0xDFFF is writable
int i = addr - 0x8000;
if ( (unsigned) i < fdsram_size && fds_enabled() )
fdsram() [i] = data;
else
#endif
cpu_write( addr, data );
}
}
}
}
#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] ))
#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data ))
#define CAN_WRITE_FAST( addr ) (addr < low_ram_size)
#define WRITE_FAST WRITE_LOW
// addr < 0x2000 || addr >= 0x8000
#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000)
#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) ))
#define READ_MEM( addr ) read_mem( addr )
#define WRITE_MEM( addr, data ) write_mem( addr, data )
#define CPU cpu
#define CPU_BEGIN \
bool Nsf_Impl::run_cpu_until( time_t end )\
{\
cpu.set_end_time( end );\
if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\
{
#include "Nes_Cpu_run.h"
}
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#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::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 };
Nsf_Emu::Nsf_Emu()
{
#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::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 };
Nsf_Emu::Nsf_Emu()
{
set_type( gme_nsf_type );
set_silence_lookahead( 6 );
set_gain( 1.4 );

View file

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

View file

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

View file

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

View file

@ -1,74 +1,74 @@
// Nintendo NES/Famicom NSFE music file emulator
// Game_Music_Emu $vers
#ifndef NSFE_EMU_H
#define NSFE_EMU_H
#include "blargg_common.h"
#include "Nsf_Emu.h"
class Nsfe_Emu;
// Allows reading info from NSFE file without creating emulator
class Nsfe_Info {
public:
blargg_err_t load( Data_Reader&, Nsfe_Emu* );
struct info_t : Nsf_Emu::header_t
{
char game [256];
char author [256];
char copyright [256];
char dumper [256];
} info;
blargg_vector<unsigned char> data;
void disable_playlist( bool = true );
blargg_err_t track_info_( track_info_t* out, int track ) const;
int remap_track( int i ) const;
void unload();
// Implementation
public:
Nsfe_Info();
~Nsfe_Info();
BLARGG_DISABLE_NOTHROW
private:
blargg_vector<char> track_name_data;
blargg_vector<const char*> track_names;
blargg_vector<unsigned char> playlist;
blargg_vector<char [4]> track_times;
int actual_track_count_;
bool playlist_disabled;
};
class Nsfe_Emu : public Nsf_Emu {
public:
static gme_type_t static_type() { return gme_nsfe_type; }
struct header_t { char tag [4]; };
// Implementation
public:
Nsfe_Emu();
~Nsfe_Emu();
virtual void unload();
protected:
virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t start_track_( int );
virtual void clear_playlist_();
private:
Nsfe_Info info;
void disable_playlist_( bool b );
friend class Nsfe_Info;
};
#endif
// Nintendo NES/Famicom NSFE music file emulator
// Game_Music_Emu $vers
#ifndef NSFE_EMU_H
#define NSFE_EMU_H
#include "blargg_common.h"
#include "Nsf_Emu.h"
class Nsfe_Emu;
// Allows reading info from NSFE file without creating emulator
class Nsfe_Info {
public:
blargg_err_t load( Data_Reader&, Nsfe_Emu* );
struct info_t : Nsf_Emu::header_t
{
char game [256];
char author [256];
char copyright [256];
char dumper [256];
} info;
blargg_vector<unsigned char> data;
void disable_playlist( bool = true );
blargg_err_t track_info_( track_info_t* out, int track ) const;
int remap_track( int i ) const;
void unload();
// Implementation
public:
Nsfe_Info();
~Nsfe_Info();
BLARGG_DISABLE_NOTHROW
private:
blargg_vector<char> track_name_data;
blargg_vector<const char*> track_names;
blargg_vector<unsigned char> playlist;
blargg_vector<char [4]> track_times;
int actual_track_count_;
bool playlist_disabled;
};
class Nsfe_Emu : public Nsf_Emu {
public:
static gme_type_t static_type() { return gme_nsfe_type; }
struct header_t { char tag [4]; };
// Implementation
public:
Nsfe_Emu();
~Nsfe_Emu();
virtual void unload();
protected:
virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t start_track_( int );
virtual void clear_playlist_();
private:
Nsfe_Info info;
void disable_playlist_( bool b );
friend class Nsfe_Info;
};
#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 "ym2413.h"
#include "fmopl.h"
extern "C" {
#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; }
@ -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_msxmusic:
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;
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;
case type_opl:
opl = ym3526_init( clock, rate );
opl = ym3526_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
break;
case type_msxaudio:
//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 );
y8950_set_delta_t_memory( opl, opl_memory, 32768 );
break;
case type_opl2:
opl = ym3812_init( clock, rate );
opl = ym3812_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
break;
}
reset();
@ -56,7 +67,7 @@ Opl_Apu::~Opl_Apu()
case type_msxmusic:
case type_smsfmunit:
case type_vrc7:
ym2413_shutdown( opl );
OPLL_delete( (OPLL *) opl );
break;
case type_opl:
@ -88,7 +99,7 @@ void Opl_Apu::reset()
case type_msxmusic:
case type_smsfmunit:
case type_vrc7:
ym2413_reset_chip( opl );
OPLL_reset( (OPLL *) opl );
break;
case type_opl:
@ -114,8 +125,8 @@ void Opl_Apu::write_data( blip_time_t time, int data )
case type_msxmusic:
case type_smsfmunit:
case type_vrc7:
ym2413_write( opl, 0, addr );
ym2413_write( opl, 1, data );
OPLL_writeIO( (OPLL *) opl, 0, addr );
OPLL_writeIO( (OPLL *) opl, 1, data );
break;
case type_opl:
@ -149,7 +160,7 @@ int Opl_Apu::read( blip_time_t time, int port )
case type_msxmusic:
case type_smsfmunit:
case type_vrc7:
return ym2413_read( opl, port );
return port ? 0xFF : 0;
case type_opl:
return ym3526_read( opl, port );
@ -192,15 +203,15 @@ void Opl_Apu::run_until( blip_time_t end_time )
case type_smsfmunit:
case type_vrc7:
{
SAMP bufMO[ 1024 ];
SAMP bufRO[ 1024 ];
SAMP * buffers[2] = { bufMO, bufRO };
e_int32 bufMO[ 1024 ];
e_int32 bufRO[ 1024 ];
e_int32 * buffers[2] = { bufMO, bufRO };
while ( count > 0 )
{
unsigned todo = count;
if ( todo > 1024 ) todo = 1024;
ym2413_update_one( opl, buffers, todo );
OPLL_calc_stereo( (OPLL *) opl, buffers, todo, -1 );
if ( output_ )
{
@ -229,7 +240,9 @@ void Opl_Apu::run_until( blip_time_t end_time )
case type_msxaudio:
case type_opl2:
{
OPLSAMPLE buffer[ 1024 ];
OPLSAMPLE bufL[ 1024 ];
OPLSAMPLE bufR[ 1024 ];
OPLSAMPLE* buffers[2] = {bufL, bufR};
while ( count > 0 )
{
@ -237,18 +250,18 @@ void Opl_Apu::run_until( blip_time_t end_time )
if ( todo > 1024 ) todo = 1024;
switch (type_)
{
case type_opl: ym3526_update_one( opl, buffer, todo ); break;
case type_msxaudio: y8950_update_one( opl, buffer, todo ); break;
case type_opl2: ym3812_update_one( opl, buffer, todo ); break;
default: break;
}
if ( output_ )
case type_opl: ym3526_update_one( opl, buffers, todo ); break;
case type_msxaudio: y8950_update_one( opl, buffers, todo ); break;
case type_opl2: ym3812_update_one( opl, buffers, todo ); break;
default: break;
}
if ( output_ )
{
int last_amp = this->last_amp;
for ( unsigned i = 0; i < todo; i++ )
{
int amp = buffer [i];
int amp = bufL [i] + bufR [i];
int delta = amp - last_amp;
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/
#include "Resampler.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Resampler::Resampler()
{
write_pos = 0;
rate_ = 0;
}
Resampler::~Resampler() { }
void Resampler::clear()
{
write_pos = 0;
clear_();
}
inline int Resampler::resample_wrapper( sample_t out [], int* out_size,
sample_t const in [], int in_size )
{
assert( rate() );
sample_t* out_ = out;
int result = resample_( &out_, out + *out_size, in, in_size ) - in;
assert( out_ <= out + *out_size );
assert( result <= in_size );
*out_size = out_ - out;
return result;
}
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 );
return out_size;
}
//// Buffering
blargg_err_t Resampler::resize_buffer( int new_size )
{
RETURN_ERR( buf.resize( new_size ) );
clear();
return blargg_ok;
}
int Resampler::skip_input( int count )
{
write_pos -= count;
if ( write_pos < 0 ) // occurs when downsampling
{
count += write_pos;
write_pos = 0;
}
memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] );
return count;
}
int Resampler::read( sample_t out [], int out_size )
{
if ( out_size )
skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) );
return out_size;
}
// $package. http://www.slack.net/~ant/
#include "Resampler.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Resampler::Resampler()
{
write_pos = 0;
rate_ = 0;
}
Resampler::~Resampler() { }
void Resampler::clear()
{
write_pos = 0;
clear_();
}
inline int Resampler::resample_wrapper( sample_t out [], int* out_size,
sample_t const in [], int in_size )
{
assert( rate() );
sample_t* out_ = out;
int result = resample_( &out_, out + *out_size, in, in_size ) - in;
assert( out_ <= out + *out_size );
assert( result <= in_size );
*out_size = out_ - out;
return result;
}
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 );
return out_size;
}
//// Buffering
blargg_err_t Resampler::resize_buffer( int new_size )
{
RETURN_ERR( buf.resize( new_size ) );
clear();
return blargg_ok;
}
int Resampler::skip_input( int count )
{
write_pos -= count;
if ( write_pos < 0 ) // occurs when downsampling
{
count += write_pos;
write_pos = 0;
}
memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] );
return count;
}
int Resampler::read( sample_t out [], int out_size )
{
if ( out_size )
skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) );
return out_size;
}

View file

@ -1,110 +1,110 @@
// Common interface for resamplers
// $package
#ifndef RESAMPLER_H
#define RESAMPLER_H
#include "blargg_common.h"
class Resampler {
public:
virtual ~Resampler();
// Sets input/output resampling ratio
blargg_err_t set_rate( double );
// Current input/output ratio
double rate() const { return rate_; }
// Samples are 16-bit signed
typedef short sample_t;
// One of two different buffering schemes can be used, as decided by the caller:
// External buffering (caller provides input buffer)
// 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
// and should be removed from input.
int resample( sample_t out [], int n, sample_t const in [], int* in_size );
// Internal buffering (resampler manages buffer)
// Resizes input buffer to n samples, then clears it
blargg_err_t resize_buffer( int n );
// Clears input buffer
void clear();
// 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.
int write( sample_t const in [], int n );
// Number of input samples in buffer
int written() const { return write_pos; }
// Removes first n input samples from buffer, fewer if there aren't that many.
// Returns number of samples actually removed.
int skip_input( int n );
// 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
// enough input samples in buffer.
int read( sample_t out [], int n );
// Direct writing to input buffer, instead of using write( in, n ) above
// Pointer to place to write input samples
sample_t* buffer() { return &buf [write_pos]; }
// Number of samples that can be written to buffer()
int buffer_free() const { return buf.size() - write_pos; }
// Notifies resampler that n input samples have been written to buffer().
// N must not be greater than buffer_free().
void write( int n );
// Derived interface
protected:
virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; )
virtual void clear_() { }
// 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
// the last output sample.
virtual sample_t const* resample_( sample_t** out, sample_t const* out_end,
sample_t const in [], int in_size ) BLARGG_PURE( { return in; } )
// Implementation
public:
Resampler();
private:
blargg_vector<sample_t> buf;
int write_pos;
double rate_;
int resample_wrapper( sample_t out [], int* out_size,
sample_t const in [], int in_size );
};
inline void Resampler::write( int count )
{
write_pos += count;
assert( (unsigned) write_pos <= buf.size() );
}
inline blargg_err_t Resampler::set_rate_( double r )
{
rate_ = r;
return blargg_ok;
}
inline blargg_err_t Resampler::set_rate( double r )
{
return set_rate_( r );
}
#endif
// Common interface for resamplers
// $package
#ifndef RESAMPLER_H
#define RESAMPLER_H
#include "blargg_common.h"
class Resampler {
public:
virtual ~Resampler();
// Sets input/output resampling ratio
blargg_err_t set_rate( double );
// Current input/output ratio
double rate() const { return rate_; }
// Samples are 16-bit signed
typedef short sample_t;
// One of two different buffering schemes can be used, as decided by the caller:
// External buffering (caller provides input buffer)
// 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
// and should be removed from input.
int resample( sample_t out [], int n, sample_t const in [], int* in_size );
// Internal buffering (resampler manages buffer)
// Resizes input buffer to n samples, then clears it
blargg_err_t resize_buffer( int n );
// Clears input buffer
void clear();
// 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.
int write( sample_t const in [], int n );
// Number of input samples in buffer
int written() const { return write_pos; }
// Removes first n input samples from buffer, fewer if there aren't that many.
// Returns number of samples actually removed.
int skip_input( int n );
// 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
// enough input samples in buffer.
int read( sample_t out [], int n );
// Direct writing to input buffer, instead of using write( in, n ) above
// Pointer to place to write input samples
sample_t* buffer() { return &buf [write_pos]; }
// Number of samples that can be written to buffer()
int buffer_free() const { return buf.size() - write_pos; }
// Notifies resampler that n input samples have been written to buffer().
// N must not be greater than buffer_free().
void write( int n );
// Derived interface
protected:
virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; )
virtual void clear_() { }
// 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
// the last output sample.
virtual sample_t const* resample_( sample_t** out, sample_t const* out_end,
sample_t const in [], int in_size ) BLARGG_PURE( { return in; } )
// Implementation
public:
Resampler();
private:
blargg_vector<sample_t> buf;
int write_pos;
double rate_;
int resample_wrapper( sample_t out [], int* out_size,
sample_t const in [], int in_size );
};
inline void Resampler::write( int count )
{
write_pos += count;
assert( (unsigned) write_pos <= buf.size() );
}
inline blargg_err_t Resampler::set_rate_( double r )
{
rate_ = r;
return blargg_ok;
}
inline blargg_err_t Resampler::set_rate( double r )
{
return set_rate_( r );
}
#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/
#include "Sap_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const max_frequency = 12000; // pure waves above this frequency are silenced
static void gen_poly( unsigned mask, int count, byte out [] )
{
unsigned n = 1;
do
{
int bits = 0;
int b = 0;
do
{
// implemented using "Galios configuration"
bits |= (n & 1) << b;
n = (n >> 1) ^ (mask * (n & 1));
}
while ( b++ < 7 );
*out++ = bits;
}
while ( --count );
}
// poly5
int const poly5_len = (1 << 5) - 1;
unsigned const poly5_mask = (1U << poly5_len) - 1;
unsigned const poly5 = 0x167C6EA1;
inline unsigned run_poly5( unsigned in, int shift )
{
return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
}
#define POLY_MASK( width, tap1, tap2 ) \
((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2)))
Sap_Apu_Impl::Sap_Apu_Impl()
{
gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
if ( 0 ) // comment out to recauculate poly5 constant
{
byte poly5 [4];
gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 +
poly5 [1] * 0x100 + poly5 [0];
unsigned rev = n & 1;
for ( int i = 1; i < poly5_len; i++ )
rev |= (n >> i & 1) << (poly5_len - i);
dprintf( "poly5: 0x%08lX\n", rev );
}
}
void Sap_Apu::set_output( Blip_Buffer* b )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, b );
}
Sap_Apu::Sap_Apu()
{
impl = NULL;
set_output( NULL );
}
void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
{
impl = new_impl;
last_time = 0;
poly5_pos = 0;
poly4_pos = 0;
polym_pos = 0;
control = 0;
for ( int i = 0; i < osc_count; i++ )
memset( &oscs [i], 0, offsetof (osc_t,output) );
}
inline void Sap_Apu::calc_periods()
{
// 15/64 kHz clock
int divider = 28;
if ( this->control & 1 )
divider = 114;
for ( int i = 0; i < osc_count; i++ )
{
osc_t* const osc = &oscs [i];
int const osc_reload = osc->regs [0]; // cache
int period = (osc_reload + 1) * divider;
static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
if ( this->control & fast_bits [i] )
{
period = osc_reload + 4;
if ( i & 1 )
{
period = osc_reload * 0x100 + osc [-1].regs [0] + 7;
if ( !(this->control & fast_bits [i - 1]) )
period = (period - 6) * divider;
if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
dprintf( "Use of slave channel in 16-bit mode not supported\n" );
}
}
osc->period = period;
}
}
void Sap_Apu::run_until( blip_time_t end_time )
{
calc_periods();
Sap_Apu_Impl* const impl = this->impl; // cache
// 17/9-bit poly selection
byte const* polym = impl->poly17;
int polym_len = poly17_len;
if ( this->control & 0x80 )
{
polym_len = poly9_len;
polym = impl->poly9;
}
polym_pos %= polym_len;
for ( int i = 0; i < osc_count; i++ )
{
osc_t* const osc = &oscs [i];
blip_time_t time = last_time + osc->delay;
blip_time_t const period = osc->period;
// output
Blip_Buffer* output = osc->output;
if ( output )
{
int const osc_control = osc->regs [1]; // cache
int volume = (osc_control & 0x0F) * 2;
if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
{
if ( !(osc_control & 0x10) )
volume >>= 1; // inaudible frequency = half volume
int delta = volume - osc->last_amp;
if ( delta )
{
osc->last_amp = volume;
output->set_modified();
impl->synth.offset( last_time, delta, output );
}
// TODO: doesn't maintain high pass flip-flop (very minor issue)
}
else
{
// high pass
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 time2 = end_time;
if ( this->control & hipass_bits [i] )
{
period2 = osc [2].period;
time2 = last_time + osc [2].delay;
if ( osc->invert )
{
// trick inner wave loop into inverting output
osc->last_amp -= volume;
volume = -volume;
}
}
if ( time < end_time || time2 < end_time )
{
// poly source
static byte const poly1 [] = { 0x55, 0x55 }; // square wave
byte const* poly = poly1;
int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
int poly_pos = osc->phase & 1;
int poly_inc = 1;
if ( !(osc_control & 0x20) )
{
poly = polym;
poly_len = polym_len;
poly_pos = polym_pos;
if ( osc_control & 0x40 )
{
poly = impl->poly4;
poly_len = poly4_len;
poly_pos = poly4_pos;
}
poly_inc = period % poly_len;
poly_pos = (poly_pos + osc->delay) % poly_len;
}
poly_inc -= poly_len; // allows more optimized inner loop below
// square/poly5 wave
unsigned wave = poly5;
check( poly5 & 1 ); // low bit is set for pure wave
int poly5_inc = 0;
if ( !(osc_control & 0x80) )
{
wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
poly5_inc = period % poly5_len;
}
output->set_modified();
// Run wave and high pass interleved with each catching up to the other.
// Disabled high pass has no performance effect since inner wave loop
// makes no compromise for high pass, and only runs once in that case.
int osc_last_amp = osc->last_amp;
do
{
// run high pass
if ( time2 < time )
{
int delta = -osc_last_amp;
if ( volume < 0 )
delta += volume;
if ( delta )
{
osc_last_amp += delta - volume;
volume = -volume;
impl->synth.offset( time2, delta, output );
}
}
while ( time2 <= time ) // must advance *past* time to avoid hang
time2 += period2;
// run wave
blip_time_t end = end_time;
if ( end > time2 )
end = time2;
while ( time < end )
{
if ( wave & 1 )
{
int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
if ( (poly_pos += poly_inc) < 0 )
poly_pos += poly_len;
int delta = amp - osc_last_amp;
if ( delta )
{
osc_last_amp = amp;
impl->synth.offset( time, delta, output );
}
}
wave = run_poly5( wave, poly5_inc );
time += period;
}
}
while ( time < end_time || time2 < end_time );
osc->phase = poly_pos;
osc->last_amp = osc_last_amp;
}
osc->invert = 0;
if ( volume < 0 )
{
// undo inversion trickery
osc->last_amp -= volume;
osc->invert = 1;
}
}
}
// maintain divider
blip_time_t remain = end_time - time;
if ( remain > 0 )
{
int count = (remain + period - 1) / period;
osc->phase ^= count;
time += count * period;
}
osc->delay = time - end_time;
}
// advance polies
blip_time_t duration = end_time - last_time;
last_time = end_time;
poly4_pos = (poly4_pos + duration) % poly4_len;
poly5_pos = (poly5_pos + duration) % poly5_len;
polym_pos += duration; // will get %'d on next call
}
void Sap_Apu::write_data( blip_time_t time, int addr, int data )
{
run_until( time );
int i = (addr - 0xD200) >> 1;
if ( (unsigned) i < osc_count )
{
oscs [i].regs [addr & 1] = data;
}
else if ( addr == 0xD208 )
{
control = data;
}
else if ( addr == 0xD209 )
{
oscs [0].delay = 0;
oscs [1].delay = 0;
oscs [2].delay = 0;
oscs [3].delay = 0;
}
/*
// TODO: are polynomials reset in this case?
else if ( addr == 0xD20F )
{
if ( (data & 3) == 0 )
polym_pos = 0;
}
*/
}
void Sap_Apu::end_frame( blip_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
last_time -= end_time;
assert( last_time >= 0 );
}
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Sap_Apu.h"
/* 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
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
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
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
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const max_frequency = 12000; // pure waves above this frequency are silenced
static void gen_poly( unsigned mask, int count, byte out [] )
{
unsigned n = 1;
do
{
int bits = 0;
int b = 0;
do
{
// implemented using "Galios configuration"
bits |= (n & 1) << b;
n = (n >> 1) ^ (mask * (n & 1));
}
while ( b++ < 7 );
*out++ = bits;
}
while ( --count );
}
// poly5
int const poly5_len = (1 << 5) - 1;
unsigned const poly5_mask = (1U << poly5_len) - 1;
unsigned const poly5 = 0x167C6EA1;
inline unsigned run_poly5( unsigned in, int shift )
{
return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
}
#define POLY_MASK( width, tap1, tap2 ) \
((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2)))
Sap_Apu_Impl::Sap_Apu_Impl()
{
gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
if ( 0 ) // comment out to recauculate poly5 constant
{
byte poly5 [4];
gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 +
poly5 [1] * 0x100 + poly5 [0];
unsigned rev = n & 1;
for ( int i = 1; i < poly5_len; i++ )
rev |= (n >> i & 1) << (poly5_len - i);
dprintf( "poly5: 0x%08lX\n", rev );
}
}
void Sap_Apu::set_output( Blip_Buffer* b )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, b );
}
Sap_Apu::Sap_Apu()
{
impl = NULL;
set_output( NULL );
}
void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
{
impl = new_impl;
last_time = 0;
poly5_pos = 0;
poly4_pos = 0;
polym_pos = 0;
control = 0;
for ( int i = 0; i < osc_count; i++ )
memset( &oscs [i], 0, offsetof (osc_t,output) );
}
inline void Sap_Apu::calc_periods()
{
// 15/64 kHz clock
int divider = 28;
if ( this->control & 1 )
divider = 114;
for ( int i = 0; i < osc_count; i++ )
{
osc_t* const osc = &oscs [i];
int const osc_reload = osc->regs [0]; // cache
int period = (osc_reload + 1) * divider;
static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
if ( this->control & fast_bits [i] )
{
period = osc_reload + 4;
if ( i & 1 )
{
period = osc_reload * 0x100 + osc [-1].regs [0] + 7;
if ( !(this->control & fast_bits [i - 1]) )
period = (period - 6) * divider;
if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
dprintf( "Use of slave channel in 16-bit mode not supported\n" );
}
}
osc->period = period;
}
}
void Sap_Apu::run_until( blip_time_t end_time )
{
calc_periods();
Sap_Apu_Impl* const impl = this->impl; // cache
// 17/9-bit poly selection
byte const* polym = impl->poly17;
int polym_len = poly17_len;
if ( this->control & 0x80 )
{
polym_len = poly9_len;
polym = impl->poly9;
}
polym_pos %= polym_len;
for ( int i = 0; i < osc_count; i++ )
{
osc_t* const osc = &oscs [i];
blip_time_t time = last_time + osc->delay;
blip_time_t const period = osc->period;
// output
Blip_Buffer* output = osc->output;
if ( output )
{
int const osc_control = osc->regs [1]; // cache
int volume = (osc_control & 0x0F) * 2;
if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
{
if ( !(osc_control & 0x10) )
volume >>= 1; // inaudible frequency = half volume
int delta = volume - osc->last_amp;
if ( delta )
{
osc->last_amp = volume;
output->set_modified();
impl->synth.offset( last_time, delta, output );
}
// TODO: doesn't maintain high pass flip-flop (very minor issue)
}
else
{
// high pass
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 time2 = end_time;
if ( this->control & hipass_bits [i] )
{
period2 = osc [2].period;
time2 = last_time + osc [2].delay;
if ( osc->invert )
{
// trick inner wave loop into inverting output
osc->last_amp -= volume;
volume = -volume;
}
}
if ( time < end_time || time2 < end_time )
{
// poly source
static byte const poly1 [] = { 0x55, 0x55 }; // square wave
byte const* poly = poly1;
int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
int poly_pos = osc->phase & 1;
int poly_inc = 1;
if ( !(osc_control & 0x20) )
{
poly = polym;
poly_len = polym_len;
poly_pos = polym_pos;
if ( osc_control & 0x40 )
{
poly = impl->poly4;
poly_len = poly4_len;
poly_pos = poly4_pos;
}
poly_inc = period % poly_len;
poly_pos = (poly_pos + osc->delay) % poly_len;
}
poly_inc -= poly_len; // allows more optimized inner loop below
// square/poly5 wave
unsigned wave = poly5;
check( poly5 & 1 ); // low bit is set for pure wave
int poly5_inc = 0;
if ( !(osc_control & 0x80) )
{
wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
poly5_inc = period % poly5_len;
}
output->set_modified();
// Run wave and high pass interleved with each catching up to the other.
// Disabled high pass has no performance effect since inner wave loop
// makes no compromise for high pass, and only runs once in that case.
int osc_last_amp = osc->last_amp;
do
{
// run high pass
if ( time2 < time )
{
int delta = -osc_last_amp;
if ( volume < 0 )
delta += volume;
if ( delta )
{
osc_last_amp += delta - volume;
volume = -volume;
impl->synth.offset( time2, delta, output );
}
}
while ( time2 <= time ) // must advance *past* time to avoid hang
time2 += period2;
// run wave
blip_time_t end = end_time;
if ( end > time2 )
end = time2;
while ( time < end )
{
if ( wave & 1 )
{
int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
if ( (poly_pos += poly_inc) < 0 )
poly_pos += poly_len;
int delta = amp - osc_last_amp;
if ( delta )
{
osc_last_amp = amp;
impl->synth.offset( time, delta, output );
}
}
wave = run_poly5( wave, poly5_inc );
time += period;
}
}
while ( time < end_time || time2 < end_time );
osc->phase = poly_pos;
osc->last_amp = osc_last_amp;
}
osc->invert = 0;
if ( volume < 0 )
{
// undo inversion trickery
osc->last_amp -= volume;
osc->invert = 1;
}
}
}
// maintain divider
blip_time_t remain = end_time - time;
if ( remain > 0 )
{
int count = (remain + period - 1) / period;
osc->phase ^= count;
time += count * period;
}
osc->delay = time - end_time;
}
// advance polies
blip_time_t duration = end_time - last_time;
last_time = end_time;
poly4_pos = (poly4_pos + duration) % poly4_len;
poly5_pos = (poly5_pos + duration) % poly5_len;
polym_pos += duration; // will get %'d on next call
}
void Sap_Apu::write_data( blip_time_t time, int addr, int data )
{
run_until( time );
int i = (addr - 0xD200) >> 1;
if ( (unsigned) i < osc_count )
{
oscs [i].regs [addr & 1] = data;
}
else if ( addr == 0xD208 )
{
control = data;
}
else if ( addr == 0xD209 )
{
oscs [0].delay = 0;
oscs [1].delay = 0;
oscs [2].delay = 0;
oscs [3].delay = 0;
}
/*
// TODO: are polynomials reset in this case?
else if ( addr == 0xD20F )
{
if ( (data & 3) == 0 )
polym_pos = 0;
}
*/
}
void Sap_Apu::end_frame( blip_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
last_time -= end_time;
assert( last_time >= 0 );
}

View file

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

View file

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

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