Updated Game_Music_Emu to latest VGMPlay branch
This commit is contained in:
parent
9020667054
commit
09e546591a
342 changed files with 90075 additions and 39784 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,412 +1,412 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Ay_Apu.h"
|
#include "Ay_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
// Emulation inaccuracies:
|
// Emulation inaccuracies:
|
||||||
// * Noise isn't run when not in use
|
// * Noise isn't run when not in use
|
||||||
// * Changes to envelope and noise periods are delayed until next reload
|
// * Changes to envelope and noise periods are delayed until next reload
|
||||||
// * Super-sonic tone should attenuate output to about 60%, not 50%
|
// * Super-sonic tone should attenuate output to about 60%, not 50%
|
||||||
|
|
||||||
// Tones above this frequency are treated as disabled tone at half volume.
|
// Tones above this frequency are treated as disabled tone at half volume.
|
||||||
// Power of two is more efficient (avoids division).
|
// Power of two is more efficient (avoids division).
|
||||||
int const inaudible_freq = 16384;
|
int const inaudible_freq = 16384;
|
||||||
|
|
||||||
int const period_factor = 16;
|
int const period_factor = 16;
|
||||||
|
|
||||||
static byte const amp_table [16] =
|
static byte const amp_table [16] =
|
||||||
{
|
{
|
||||||
#define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5)
|
#define ENTRY( n ) byte (n * Ay_Apu::amp_range + 0.5)
|
||||||
// With channels tied together and 1K resistor to ground (as datasheet recommends),
|
// With channels tied together and 1K resistor to ground (as datasheet recommends),
|
||||||
// output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step.
|
// output nearly matches logarithmic curve as claimed. Approx. 1.5 dB per step.
|
||||||
ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625),
|
ENTRY(0.000000),ENTRY(0.007813),ENTRY(0.011049),ENTRY(0.015625),
|
||||||
ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500),
|
ENTRY(0.022097),ENTRY(0.031250),ENTRY(0.044194),ENTRY(0.062500),
|
||||||
ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000),
|
ENTRY(0.088388),ENTRY(0.125000),ENTRY(0.176777),ENTRY(0.250000),
|
||||||
ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000),
|
ENTRY(0.353553),ENTRY(0.500000),ENTRY(0.707107),ENTRY(1.000000),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Measured from an AY-3-8910A chip with date code 8611.
|
// Measured from an AY-3-8910A chip with date code 8611.
|
||||||
|
|
||||||
// Direct voltages without any load (very linear)
|
// Direct voltages without any load (very linear)
|
||||||
ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785),
|
ENTRY(0.000000),ENTRY(0.046237),ENTRY(0.064516),ENTRY(0.089785),
|
||||||
ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032),
|
ENTRY(0.124731),ENTRY(0.173118),ENTRY(0.225806),ENTRY(0.329032),
|
||||||
ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043),
|
ENTRY(0.360215),ENTRY(0.494624),ENTRY(0.594624),ENTRY(0.672043),
|
||||||
ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000),
|
ENTRY(0.766129),ENTRY(0.841935),ENTRY(0.926882),ENTRY(1.000000),
|
||||||
// With only some load
|
// With only some load
|
||||||
ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876),
|
ENTRY(0.000000),ENTRY(0.011940),ENTRY(0.017413),ENTRY(0.024876),
|
||||||
ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388),
|
ENTRY(0.036318),ENTRY(0.054229),ENTRY(0.072637),ENTRY(0.122388),
|
||||||
ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945),
|
ENTRY(0.174129),ENTRY(0.239303),ENTRY(0.323881),ENTRY(0.410945),
|
||||||
ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000),
|
ENTRY(0.527363),ENTRY(0.651741),ENTRY(0.832338),ENTRY(1.000000),
|
||||||
*/
|
*/
|
||||||
#undef ENTRY
|
#undef ENTRY
|
||||||
};
|
};
|
||||||
|
|
||||||
static byte const modes [8] =
|
static byte const modes [8] =
|
||||||
{
|
{
|
||||||
#define MODE( a0,a1, b0,b1, c0,c1 ) \
|
#define MODE( a0,a1, b0,b1, c0,c1 ) \
|
||||||
(a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5)
|
(a0 | a1<<1 | b0<<2 | b1<<3 | c0<<4 | c1<<5)
|
||||||
MODE( 1,0, 1,0, 1,0 ),
|
MODE( 1,0, 1,0, 1,0 ),
|
||||||
MODE( 1,0, 0,0, 0,0 ),
|
MODE( 1,0, 0,0, 0,0 ),
|
||||||
MODE( 1,0, 0,1, 1,0 ),
|
MODE( 1,0, 0,1, 1,0 ),
|
||||||
MODE( 1,0, 1,1, 1,1 ),
|
MODE( 1,0, 1,1, 1,1 ),
|
||||||
MODE( 0,1, 0,1, 0,1 ),
|
MODE( 0,1, 0,1, 0,1 ),
|
||||||
MODE( 0,1, 1,1, 1,1 ),
|
MODE( 0,1, 1,1, 1,1 ),
|
||||||
MODE( 0,1, 1,0, 0,1 ),
|
MODE( 0,1, 1,0, 0,1 ),
|
||||||
MODE( 0,1, 0,0, 0,0 ),
|
MODE( 0,1, 0,0, 0,0 ),
|
||||||
};
|
};
|
||||||
|
|
||||||
void Ay_Apu::set_output( Blip_Buffer* b )
|
void Ay_Apu::set_output( Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
set_output( i, b );
|
set_output( i, b );
|
||||||
}
|
}
|
||||||
|
|
||||||
Ay_Apu::Ay_Apu()
|
Ay_Apu::Ay_Apu()
|
||||||
{
|
{
|
||||||
// build full table of the upper 8 envelope waveforms
|
// build full table of the upper 8 envelope waveforms
|
||||||
for ( int m = 8; m--; )
|
for ( int m = 8; m--; )
|
||||||
{
|
{
|
||||||
byte* out = env_modes [m];
|
byte* out = env_modes [m];
|
||||||
int flags = modes [m];
|
int flags = modes [m];
|
||||||
for ( int x = 3; --x >= 0; )
|
for ( int x = 3; --x >= 0; )
|
||||||
{
|
{
|
||||||
int amp = flags & 1;
|
int amp = flags & 1;
|
||||||
int end = flags >> 1 & 1;
|
int end = flags >> 1 & 1;
|
||||||
int step = end - amp;
|
int step = end - amp;
|
||||||
amp *= 15;
|
amp *= 15;
|
||||||
for ( int y = 16; --y >= 0; )
|
for ( int y = 16; --y >= 0; )
|
||||||
{
|
{
|
||||||
*out++ = amp_table [amp];
|
*out++ = amp_table [amp];
|
||||||
amp += step;
|
amp += step;
|
||||||
}
|
}
|
||||||
flags >>= 2;
|
flags >>= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type_ = Ay8910;
|
type_ = Ay8910;
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ay_Apu::reset()
|
void Ay_Apu::reset()
|
||||||
{
|
{
|
||||||
addr_ = 0;
|
addr_ = 0;
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
noise_delay = 0;
|
noise_delay = 0;
|
||||||
noise_lfsr = 1;
|
noise_lfsr = 1;
|
||||||
|
|
||||||
for ( osc_t* osc = &oscs [osc_count]; osc != oscs; )
|
for ( osc_t* osc = &oscs [osc_count]; osc != oscs; )
|
||||||
{
|
{
|
||||||
osc--;
|
osc--;
|
||||||
osc->period = period_factor;
|
osc->period = period_factor;
|
||||||
osc->delay = 0;
|
osc->delay = 0;
|
||||||
osc->last_amp = 0;
|
osc->last_amp = 0;
|
||||||
osc->phase = 0;
|
osc->phase = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( int i = sizeof regs; --i >= 0; )
|
for ( int i = sizeof regs; --i >= 0; )
|
||||||
regs [i] = 0;
|
regs [i] = 0;
|
||||||
regs [7] = 0xFF;
|
regs [7] = 0xFF;
|
||||||
write_data_( 13, 0 );
|
write_data_( 13, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Ay_Apu::read()
|
int Ay_Apu::read()
|
||||||
{
|
{
|
||||||
static byte const masks [reg_count] = {
|
static byte const masks [reg_count] = {
|
||||||
0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F,
|
0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F,
|
||||||
0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00
|
0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00
|
||||||
};
|
};
|
||||||
if (!(type_ & 0x10)) return regs [addr_] & masks [addr_];
|
if (!(type_ & 0x10)) return regs [addr_] & masks [addr_];
|
||||||
else return regs [addr_];
|
else return regs [addr_];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ay_Apu::write_data_( int addr, int data )
|
void Ay_Apu::write_data_( int addr, int data )
|
||||||
{
|
{
|
||||||
assert( (unsigned) addr < reg_count );
|
assert( (unsigned) addr < reg_count );
|
||||||
|
|
||||||
if ( (unsigned) addr >= 14 )
|
if ( (unsigned) addr >= 14 )
|
||||||
dprintf( "Wrote to I/O port %02X\n", (int) addr );
|
dprintf( "Wrote to I/O port %02X\n", (int) addr );
|
||||||
|
|
||||||
// envelope mode
|
// envelope mode
|
||||||
if ( addr == 13 )
|
if ( addr == 13 )
|
||||||
{
|
{
|
||||||
if ( !(data & 8) ) // convert modes 0-7 to proper equivalents
|
if ( !(data & 8) ) // convert modes 0-7 to proper equivalents
|
||||||
data = (data & 4) ? 15 : 9;
|
data = (data & 4) ? 15 : 9;
|
||||||
env_wave = env_modes [data - 7];
|
env_wave = env_modes [data - 7];
|
||||||
env_pos = -48;
|
env_pos = -48;
|
||||||
env_delay = 0; // will get set to envelope period in run_until()
|
env_delay = 0; // will get set to envelope period in run_until()
|
||||||
}
|
}
|
||||||
regs [addr] = data;
|
regs [addr] = data;
|
||||||
|
|
||||||
// handle period changes accurately
|
// handle period changes accurately
|
||||||
int i = addr >> 1;
|
int i = addr >> 1;
|
||||||
if ( i < osc_count )
|
if ( i < osc_count )
|
||||||
{
|
{
|
||||||
blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) +
|
blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) +
|
||||||
regs [i * 2] * period_factor;
|
regs [i * 2] * period_factor;
|
||||||
if ( !period )
|
if ( !period )
|
||||||
period = period_factor;
|
period = period_factor;
|
||||||
|
|
||||||
// adjust time of next timer expiration based on change in period
|
// adjust time of next timer expiration based on change in period
|
||||||
osc_t& osc = oscs [i];
|
osc_t& osc = oscs [i];
|
||||||
if ( (osc.delay += period - osc.period) < 0 )
|
if ( (osc.delay += period - osc.period) < 0 )
|
||||||
osc.delay = 0;
|
osc.delay = 0;
|
||||||
osc.period = period;
|
osc.period = period;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: same as above for envelope timer, and it also has a divide by two after it
|
// TODO: same as above for envelope timer, and it also has a divide by two after it
|
||||||
}
|
}
|
||||||
|
|
||||||
int const noise_off = 0x08;
|
int const noise_off = 0x08;
|
||||||
int const tone_off = 0x01;
|
int const tone_off = 0x01;
|
||||||
|
|
||||||
void Ay_Apu::run_until( blip_time_t final_end_time )
|
void Ay_Apu::run_until( blip_time_t final_end_time )
|
||||||
{
|
{
|
||||||
require( final_end_time >= last_time );
|
require( final_end_time >= last_time );
|
||||||
|
|
||||||
// noise period and initial values
|
// noise period and initial values
|
||||||
blip_time_t const noise_period_factor = period_factor * 2; // verified
|
blip_time_t const noise_period_factor = period_factor * 2; // verified
|
||||||
blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor;
|
blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor;
|
||||||
if ( !noise_period )
|
if ( !noise_period )
|
||||||
noise_period = noise_period_factor;
|
noise_period = noise_period_factor;
|
||||||
blip_time_t const old_noise_delay = noise_delay;
|
blip_time_t const old_noise_delay = noise_delay;
|
||||||
unsigned const old_noise_lfsr = noise_lfsr;
|
unsigned const old_noise_lfsr = noise_lfsr;
|
||||||
|
|
||||||
// envelope period
|
// envelope period
|
||||||
int env_step_scale = ((type_ & 0xF0) == 0x00) ? 1 : 0;
|
int env_step_scale = ((type_ & 0xF0) == 0x00) ? 1 : 0;
|
||||||
blip_time_t const env_period_factor = period_factor << env_step_scale; // verified
|
blip_time_t const env_period_factor = period_factor << env_step_scale; // verified
|
||||||
blip_time_t env_period = (regs [12] * 0x100 + regs [11]) * env_period_factor;
|
blip_time_t env_period = (regs [12] * 0x100 + regs [11]) * env_period_factor;
|
||||||
if ( !env_period )
|
if ( !env_period )
|
||||||
env_period = env_period_factor; // same as period 1 on my AY chip
|
env_period = env_period_factor; // same as period 1 on my AY chip
|
||||||
if ( !env_delay )
|
if ( !env_delay )
|
||||||
env_delay = env_period;
|
env_delay = env_period;
|
||||||
|
|
||||||
// run each osc separately
|
// run each osc separately
|
||||||
for ( int index = 0; index < osc_count; index++ )
|
for ( int index = 0; index < osc_count; index++ )
|
||||||
{
|
{
|
||||||
osc_t* const osc = &oscs [index];
|
osc_t* const osc = &oscs [index];
|
||||||
int osc_mode = regs [7] >> index;
|
int osc_mode = regs [7] >> index;
|
||||||
|
|
||||||
// output
|
// output
|
||||||
Blip_Buffer* const osc_output = osc->output;
|
Blip_Buffer* const osc_output = osc->output;
|
||||||
if ( !osc_output )
|
if ( !osc_output )
|
||||||
continue;
|
continue;
|
||||||
osc_output->set_modified();
|
osc_output->set_modified();
|
||||||
|
|
||||||
// period
|
// period
|
||||||
int half_vol = 0;
|
int half_vol = 0;
|
||||||
blip_time_t inaudible_period = (unsigned) (osc_output->clock_rate() +
|
blip_time_t inaudible_period = (unsigned) (osc_output->clock_rate() +
|
||||||
inaudible_freq) / (unsigned) (inaudible_freq * 2);
|
inaudible_freq) / (unsigned) (inaudible_freq * 2);
|
||||||
if ( osc->period <= inaudible_period && !(osc_mode & tone_off) )
|
if ( osc->period <= inaudible_period && !(osc_mode & tone_off) )
|
||||||
{
|
{
|
||||||
half_vol = 1; // Actually around 60%, but 50% is close enough
|
half_vol = 1; // Actually around 60%, but 50% is close enough
|
||||||
osc_mode |= tone_off;
|
osc_mode |= tone_off;
|
||||||
}
|
}
|
||||||
|
|
||||||
// envelope
|
// envelope
|
||||||
blip_time_t start_time = last_time;
|
blip_time_t start_time = last_time;
|
||||||
blip_time_t end_time = final_end_time;
|
blip_time_t end_time = final_end_time;
|
||||||
int const vol_mode = regs [0x08 + index];
|
int const vol_mode = regs [0x08 + index];
|
||||||
int const vol_mode_mask = type_ == Ay8914 ? 0x30 : 0x10;
|
int const vol_mode_mask = type_ == Ay8914 ? 0x30 : 0x10;
|
||||||
int volume = amp_table [vol_mode & 0x0F] >> (half_vol + env_step_scale);
|
int volume = amp_table [vol_mode & 0x0F] >> (half_vol + env_step_scale);
|
||||||
int osc_env_pos = env_pos;
|
int osc_env_pos = env_pos;
|
||||||
if ( vol_mode & vol_mode_mask )
|
if ( vol_mode & vol_mode_mask )
|
||||||
{
|
{
|
||||||
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale);
|
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale);
|
||||||
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
|
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
|
||||||
// use envelope only if it's a repeating wave or a ramp that hasn't finished
|
// use envelope only if it's a repeating wave or a ramp that hasn't finished
|
||||||
if ( !(regs [13] & 1) || osc_env_pos < -32 )
|
if ( !(regs [13] & 1) || osc_env_pos < -32 )
|
||||||
{
|
{
|
||||||
end_time = start_time + env_delay;
|
end_time = start_time + env_delay;
|
||||||
if ( end_time >= final_end_time )
|
if ( end_time >= final_end_time )
|
||||||
end_time = final_end_time;
|
end_time = final_end_time;
|
||||||
|
|
||||||
//if ( !(regs [12] | regs [11]) )
|
//if ( !(regs [12] | regs [11]) )
|
||||||
// dprintf( "Used envelope period 0\n" );
|
// dprintf( "Used envelope period 0\n" );
|
||||||
}
|
}
|
||||||
else if ( !volume )
|
else if ( !volume )
|
||||||
{
|
{
|
||||||
osc_mode = noise_off | tone_off;
|
osc_mode = noise_off | tone_off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( !volume )
|
else if ( !volume )
|
||||||
{
|
{
|
||||||
osc_mode = noise_off | tone_off;
|
osc_mode = noise_off | tone_off;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tone time
|
// tone time
|
||||||
blip_time_t const period = osc->period;
|
blip_time_t const period = osc->period;
|
||||||
blip_time_t time = start_time + osc->delay;
|
blip_time_t time = start_time + osc->delay;
|
||||||
if ( osc_mode & tone_off ) // maintain tone's phase when off
|
if ( osc_mode & tone_off ) // maintain tone's phase when off
|
||||||
{
|
{
|
||||||
int count = (final_end_time - time + period - 1) / period;
|
int count = (final_end_time - time + period - 1) / period;
|
||||||
time += count * period;
|
time += count * period;
|
||||||
osc->phase ^= count & 1;
|
osc->phase ^= count & 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// noise time
|
// noise time
|
||||||
blip_time_t ntime = final_end_time;
|
blip_time_t ntime = final_end_time;
|
||||||
unsigned noise_lfsr = 1;
|
unsigned noise_lfsr = 1;
|
||||||
if ( !(osc_mode & noise_off) )
|
if ( !(osc_mode & noise_off) )
|
||||||
{
|
{
|
||||||
ntime = start_time + old_noise_delay;
|
ntime = start_time + old_noise_delay;
|
||||||
noise_lfsr = old_noise_lfsr;
|
noise_lfsr = old_noise_lfsr;
|
||||||
//if ( (regs [6] & 0x1F) == 0 )
|
//if ( (regs [6] & 0x1F) == 0 )
|
||||||
// dprintf( "Used noise period 0\n" );
|
// dprintf( "Used noise period 0\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following efficiently handles several cases (least demanding first):
|
// The following efficiently handles several cases (least demanding first):
|
||||||
// * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC
|
// * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC
|
||||||
// * Just tone or just noise, envelope disabled
|
// * Just tone or just noise, envelope disabled
|
||||||
// * Envelope controlling tone and/or noise
|
// * Envelope controlling tone and/or noise
|
||||||
// * Tone and noise disabled, envelope enabled with high frequency
|
// * Tone and noise disabled, envelope enabled with high frequency
|
||||||
// * Tone and noise together
|
// * Tone and noise together
|
||||||
// * Tone and noise together with envelope
|
// * Tone and noise together with envelope
|
||||||
|
|
||||||
// This loop only runs one iteration if envelope is disabled. If envelope
|
// This loop only runs one iteration if envelope is disabled. If envelope
|
||||||
// is being used as a waveform (tone and noise disabled), this loop will
|
// is being used as a waveform (tone and noise disabled), this loop will
|
||||||
// still be reasonably efficient since the bulk of it will be skipped.
|
// still be reasonably efficient since the bulk of it will be skipped.
|
||||||
while ( 1 )
|
while ( 1 )
|
||||||
{
|
{
|
||||||
// current amplitude
|
// current amplitude
|
||||||
int amp = 0;
|
int amp = 0;
|
||||||
if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) )
|
if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) )
|
||||||
amp = volume;
|
amp = volume;
|
||||||
{
|
{
|
||||||
int delta = amp - osc->last_amp;
|
int delta = amp - osc->last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
osc->last_amp = amp;
|
osc->last_amp = amp;
|
||||||
synth_.offset( start_time, delta, osc_output );
|
synth_.offset( start_time, delta, osc_output );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run wave and noise interleved with each catching up to the other.
|
// Run wave and noise interleved with each catching up to the other.
|
||||||
// If one or both are disabled, their "current time" will be past end time,
|
// If one or both are disabled, their "current time" will be past end time,
|
||||||
// so there will be no significant performance hit.
|
// so there will be no significant performance hit.
|
||||||
if ( ntime < end_time || time < end_time )
|
if ( ntime < end_time || time < end_time )
|
||||||
{
|
{
|
||||||
// Since amplitude was updated above, delta will always be +/- volume,
|
// Since amplitude was updated above, delta will always be +/- volume,
|
||||||
// so we can avoid using last_amp every time to calculate the delta.
|
// so we can avoid using last_amp every time to calculate the delta.
|
||||||
int delta = amp * 2 - volume;
|
int delta = amp * 2 - volume;
|
||||||
int delta_non_zero = delta != 0;
|
int delta_non_zero = delta != 0;
|
||||||
int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 );
|
int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 );
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// run noise
|
// run noise
|
||||||
blip_time_t end = end_time;
|
blip_time_t end = end_time;
|
||||||
if ( end_time > time ) end = time;
|
if ( end_time > time ) end = time;
|
||||||
if ( phase & delta_non_zero )
|
if ( phase & delta_non_zero )
|
||||||
{
|
{
|
||||||
while ( ntime <= end ) // must advance *past* time to avoid hang
|
while ( ntime <= end ) // must advance *past* time to avoid hang
|
||||||
{
|
{
|
||||||
int changed = noise_lfsr + 1;
|
int changed = noise_lfsr + 1;
|
||||||
noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1);
|
noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1);
|
||||||
if ( changed & 2 )
|
if ( changed & 2 )
|
||||||
{
|
{
|
||||||
delta = -delta;
|
delta = -delta;
|
||||||
synth_.offset( ntime, delta, osc_output );
|
synth_.offset( ntime, delta, osc_output );
|
||||||
}
|
}
|
||||||
ntime += noise_period;
|
ntime += noise_period;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 20 or more noise periods on average for some music
|
// 20 or more noise periods on average for some music
|
||||||
int remain = end - ntime;
|
int remain = end - ntime;
|
||||||
int count = remain / noise_period;
|
int count = remain / noise_period;
|
||||||
if ( remain >= 0 )
|
if ( remain >= 0 )
|
||||||
ntime += noise_period + count * noise_period;
|
ntime += noise_period + count * noise_period;
|
||||||
}
|
}
|
||||||
|
|
||||||
// run tone
|
// run tone
|
||||||
end = end_time;
|
end = end_time;
|
||||||
if ( end_time > ntime ) end = ntime;
|
if ( end_time > ntime ) end = ntime;
|
||||||
if ( noise_lfsr & delta_non_zero )
|
if ( noise_lfsr & delta_non_zero )
|
||||||
{
|
{
|
||||||
while ( time < end )
|
while ( time < end )
|
||||||
{
|
{
|
||||||
delta = -delta;
|
delta = -delta;
|
||||||
synth_.offset( time, delta, osc_output );
|
synth_.offset( time, delta, osc_output );
|
||||||
time += period;
|
time += period;
|
||||||
|
|
||||||
// alternate (less-efficient) implementation
|
// alternate (less-efficient) implementation
|
||||||
//phase ^= 1;
|
//phase ^= 1;
|
||||||
}
|
}
|
||||||
phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
|
phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
|
||||||
check( phase == (delta > 0) );
|
check( phase == (delta > 0) );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// loop usually runs less than once
|
// loop usually runs less than once
|
||||||
//SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period );
|
//SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period );
|
||||||
|
|
||||||
while ( time < end )
|
while ( time < end )
|
||||||
{
|
{
|
||||||
time += period;
|
time += period;
|
||||||
phase ^= 1;
|
phase ^= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ( time < end_time || ntime < end_time );
|
while ( time < end_time || ntime < end_time );
|
||||||
|
|
||||||
osc->last_amp = (delta + volume) >> 1;
|
osc->last_amp = (delta + volume) >> 1;
|
||||||
if ( !(osc_mode & tone_off) )
|
if ( !(osc_mode & tone_off) )
|
||||||
osc->phase = phase;
|
osc->phase = phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( end_time >= final_end_time )
|
if ( end_time >= final_end_time )
|
||||||
break; // breaks first time when envelope is disabled
|
break; // breaks first time when envelope is disabled
|
||||||
|
|
||||||
// next envelope step
|
// next envelope step
|
||||||
if ( ++osc_env_pos >= 0 )
|
if ( ++osc_env_pos >= 0 )
|
||||||
osc_env_pos -= 32;
|
osc_env_pos -= 32;
|
||||||
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale);
|
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale);
|
||||||
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
|
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
|
||||||
|
|
||||||
start_time = end_time;
|
start_time = end_time;
|
||||||
end_time += env_period;
|
end_time += env_period;
|
||||||
if ( end_time > final_end_time )
|
if ( end_time > final_end_time )
|
||||||
end_time = final_end_time;
|
end_time = final_end_time;
|
||||||
}
|
}
|
||||||
osc->delay = time - final_end_time;
|
osc->delay = time - final_end_time;
|
||||||
|
|
||||||
if ( !(osc_mode & noise_off) )
|
if ( !(osc_mode & noise_off) )
|
||||||
{
|
{
|
||||||
noise_delay = ntime - final_end_time;
|
noise_delay = ntime - final_end_time;
|
||||||
this->noise_lfsr = noise_lfsr;
|
this->noise_lfsr = noise_lfsr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: optimized saw wave envelope?
|
// TODO: optimized saw wave envelope?
|
||||||
|
|
||||||
// maintain envelope phase
|
// maintain envelope phase
|
||||||
blip_time_t remain = final_end_time - last_time - env_delay;
|
blip_time_t remain = final_end_time - last_time - env_delay;
|
||||||
if ( remain >= 0 )
|
if ( remain >= 0 )
|
||||||
{
|
{
|
||||||
int count = (remain + env_period) / env_period;
|
int count = (remain + env_period) / env_period;
|
||||||
env_pos += count;
|
env_pos += count;
|
||||||
if ( env_pos >= 0 )
|
if ( env_pos >= 0 )
|
||||||
env_pos = (env_pos & 31) - 32;
|
env_pos = (env_pos & 31) - 32;
|
||||||
remain -= count * env_period;
|
remain -= count * env_period;
|
||||||
assert( -remain <= env_period );
|
assert( -remain <= env_period );
|
||||||
}
|
}
|
||||||
env_delay = -remain;
|
env_delay = -remain;
|
||||||
assert( env_delay > 0 );
|
assert( env_delay > 0 );
|
||||||
assert( env_pos < 0 );
|
assert( env_pos < 0 );
|
||||||
|
|
||||||
last_time = final_end_time;
|
last_time = final_end_time;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,123 @@
|
||||||
// AY-3-8910 sound chip emulator
|
// AY-3-8910 sound chip emulator
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef AY_APU_H
|
#ifndef AY_APU_H
|
||||||
#define AY_APU_H
|
#define AY_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Ay_Apu {
|
class Ay_Apu {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
enum Ay_Apu_Type
|
enum Ay_Apu_Type
|
||||||
{
|
{
|
||||||
Ay8910 = 0,
|
Ay8910 = 0,
|
||||||
Ay8912,
|
Ay8912,
|
||||||
Ay8913,
|
Ay8913,
|
||||||
Ay8914,
|
Ay8914,
|
||||||
Ym2149 = 0x10,
|
Ym2149 = 0x10,
|
||||||
Ym3439,
|
Ym3439,
|
||||||
Ymz284,
|
Ymz284,
|
||||||
Ymz294,
|
Ymz294,
|
||||||
Ym2203 = 0x20,
|
Ym2203 = 0x20,
|
||||||
Ym2608,
|
Ym2608,
|
||||||
Ym2610,
|
Ym2610,
|
||||||
Ym2610b
|
Ym2610b
|
||||||
};
|
};
|
||||||
|
|
||||||
void set_type( Ay_Apu_Type type ) { type_ = type; }
|
void set_type( Ay_Apu_Type type ) { type_ = type; }
|
||||||
|
|
||||||
// Sets buffer to generate sound into, or 0 to mute.
|
// Sets buffer to generate sound into, or 0 to mute.
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
|
|
||||||
// Writes to address register
|
// Writes to address register
|
||||||
void write_addr( int data ) { addr_ = data & 0x0F; }
|
void write_addr( int data ) { addr_ = data & 0x0F; }
|
||||||
|
|
||||||
// Emulates to time t, then writes to current data register
|
// Emulates to time t, then writes to current data register
|
||||||
void write_data( blip_time_t t, int data ) { run_until( t ); write_data_( addr_, data ); }
|
void write_data( blip_time_t t, int data ) { run_until( t ); write_data_( addr_, data ); }
|
||||||
|
|
||||||
// Emulates to time t, then subtracts t from the current time.
|
// Emulates to time t, then subtracts t from the current time.
|
||||||
// OK if previous write call had time slightly after t.
|
// OK if previous write call had time slightly after t.
|
||||||
void end_frame( blip_time_t t );
|
void end_frame( blip_time_t t );
|
||||||
|
|
||||||
// More features
|
// More features
|
||||||
|
|
||||||
// Reads from current data register
|
// Reads from current data register
|
||||||
int read();
|
int read();
|
||||||
|
|
||||||
// Resets sound chip
|
// Resets sound chip
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
// Number of registers
|
// Number of registers
|
||||||
enum { reg_count = 16 };
|
enum { reg_count = 16 };
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
enum { osc_count = 3 };
|
enum { osc_count = 3 };
|
||||||
void set_output( int chan, Blip_Buffer* );
|
void set_output( int chan, Blip_Buffer* );
|
||||||
|
|
||||||
// Sets overall volume, where 1.0 is normal
|
// Sets overall volume, where 1.0 is normal
|
||||||
void volume( double v ) { synth_.volume( 0.7/osc_count/amp_range * v ); }
|
void volume( double v ) { synth_.volume( 0.7/osc_count/amp_range * v ); }
|
||||||
|
|
||||||
// Sets treble equalization
|
// Sets treble equalization
|
||||||
void treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); }
|
void treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Ay_Apu( const Ay_Apu& );
|
Ay_Apu( const Ay_Apu& );
|
||||||
Ay_Apu& operator = ( const Ay_Apu& );
|
Ay_Apu& operator = ( const Ay_Apu& );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Ay_Apu();
|
Ay_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct osc_t
|
struct osc_t
|
||||||
{
|
{
|
||||||
blip_time_t period;
|
blip_time_t period;
|
||||||
blip_time_t delay;
|
blip_time_t delay;
|
||||||
short last_amp;
|
short last_amp;
|
||||||
short phase;
|
short phase;
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
} oscs [osc_count];
|
} oscs [osc_count];
|
||||||
|
|
||||||
Ay_Apu_Type type_;
|
Ay_Apu_Type type_;
|
||||||
|
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
byte addr_;
|
byte addr_;
|
||||||
byte regs [reg_count];
|
byte regs [reg_count];
|
||||||
|
|
||||||
blip_time_t noise_delay;
|
blip_time_t noise_delay;
|
||||||
unsigned noise_lfsr;
|
unsigned noise_lfsr;
|
||||||
|
|
||||||
blip_time_t env_delay;
|
blip_time_t env_delay;
|
||||||
byte const* env_wave;
|
byte const* env_wave;
|
||||||
int env_pos;
|
int env_pos;
|
||||||
byte env_modes [8] [48]; // values already passed through volume table
|
byte env_modes [8] [48]; // values already passed through volume table
|
||||||
|
|
||||||
void write_data_( int addr, int data );
|
void write_data_( int addr, int data );
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum { amp_range = 255 };
|
enum { amp_range = 255 };
|
||||||
Blip_Synth_Norm synth_; // used by Ay_Core for beeper sound
|
Blip_Synth_Norm synth_; // used by Ay_Core for beeper sound
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Ay_Apu::set_output( int i, Blip_Buffer* out )
|
inline void Ay_Apu::set_output( int i, Blip_Buffer* out )
|
||||||
{
|
{
|
||||||
assert( (unsigned) i < osc_count );
|
assert( (unsigned) i < osc_count );
|
||||||
oscs [i].output = out;
|
oscs [i].output = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Ay_Apu::end_frame( blip_time_t time )
|
inline void Ay_Apu::end_frame( blip_time_t time )
|
||||||
{
|
{
|
||||||
if ( time > last_time )
|
if ( time > last_time )
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
last_time -= time;
|
last_time -= time;
|
||||||
assert( last_time >= 0 );
|
assert( last_time >= 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,190 +1,190 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Ay_Core.h"
|
#include "Ay_Core.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
inline void Ay_Core::disable_beeper()
|
inline void Ay_Core::disable_beeper()
|
||||||
{
|
{
|
||||||
beeper_mask = 0;
|
beeper_mask = 0;
|
||||||
last_beeper = 0;
|
last_beeper = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ay_Core::Ay_Core()
|
Ay_Core::Ay_Core()
|
||||||
{
|
{
|
||||||
beeper_output = NULL;
|
beeper_output = NULL;
|
||||||
disable_beeper();
|
disable_beeper();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ay_Core::~Ay_Core() { }
|
Ay_Core::~Ay_Core() { }
|
||||||
|
|
||||||
void Ay_Core::set_beeper_output( Blip_Buffer* b )
|
void Ay_Core::set_beeper_output( Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
beeper_output = b;
|
beeper_output = b;
|
||||||
if ( b && !cpc_mode )
|
if ( b && !cpc_mode )
|
||||||
beeper_mask = 0x10;
|
beeper_mask = 0x10;
|
||||||
else
|
else
|
||||||
disable_beeper();
|
disable_beeper();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ay_Core::start_track( registers_t const& r, addr_t play )
|
void Ay_Core::start_track( registers_t const& r, addr_t play )
|
||||||
{
|
{
|
||||||
play_addr = play;
|
play_addr = play;
|
||||||
|
|
||||||
memset( mem_.padding1, 0xFF, sizeof mem_.padding1 );
|
memset( mem_.padding1, 0xFF, sizeof mem_.padding1 );
|
||||||
|
|
||||||
int const mirrored = 0x80; // this much is mirrored after end of memory
|
int const mirrored = 0x80; // this much is mirrored after end of memory
|
||||||
memset( mem_.ram + mem_size + mirrored, 0xFF, sizeof mem_.ram - mem_size - mirrored );
|
memset( mem_.ram + mem_size + mirrored, 0xFF, sizeof mem_.ram - mem_size - mirrored );
|
||||||
memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh)
|
memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh)
|
||||||
|
|
||||||
cpu.reset( mem_.padding1, mem_.padding1 );
|
cpu.reset( mem_.padding1, mem_.padding1 );
|
||||||
cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram );
|
cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram );
|
||||||
cpu.r = r;
|
cpu.r = r;
|
||||||
|
|
||||||
beeper_delta = (int) (apu_.amp_range * 0.8);
|
beeper_delta = (int) (apu_.amp_range * 0.8);
|
||||||
last_beeper = 0;
|
last_beeper = 0;
|
||||||
next_play = play_period;
|
next_play = play_period;
|
||||||
spectrum_mode = false;
|
spectrum_mode = false;
|
||||||
cpc_mode = false;
|
cpc_mode = false;
|
||||||
cpc_latch = 0;
|
cpc_latch = 0;
|
||||||
set_beeper_output( beeper_output );
|
set_beeper_output( beeper_output );
|
||||||
apu_.reset();
|
apu_.reset();
|
||||||
|
|
||||||
// a few tunes rely on channels having tone enabled at the beginning
|
// a few tunes rely on channels having tone enabled at the beginning
|
||||||
apu_.write_addr( 7 );
|
apu_.write_addr( 7 );
|
||||||
apu_.write_data( 0, 0x38 );
|
apu_.write_data( 0, 0x38 );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emulation
|
// Emulation
|
||||||
|
|
||||||
void Ay_Core::cpu_out_( time_t time, addr_t addr, int data )
|
void Ay_Core::cpu_out_( time_t time, addr_t addr, int data )
|
||||||
{
|
{
|
||||||
// Spectrum
|
// Spectrum
|
||||||
if ( !cpc_mode )
|
if ( !cpc_mode )
|
||||||
{
|
{
|
||||||
switch ( addr & 0xFEFF )
|
switch ( addr & 0xFEFF )
|
||||||
{
|
{
|
||||||
case 0xFEFD:
|
case 0xFEFD:
|
||||||
spectrum_mode = true;
|
spectrum_mode = true;
|
||||||
apu_.write_addr( data );
|
apu_.write_addr( data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0xBEFD:
|
case 0xBEFD:
|
||||||
spectrum_mode = true;
|
spectrum_mode = true;
|
||||||
apu_.write_data( time, data );
|
apu_.write_data( time, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPC
|
// CPC
|
||||||
if ( !spectrum_mode )
|
if ( !spectrum_mode )
|
||||||
{
|
{
|
||||||
switch ( addr >> 8 )
|
switch ( addr >> 8 )
|
||||||
{
|
{
|
||||||
case 0xF6:
|
case 0xF6:
|
||||||
switch ( data & 0xC0 )
|
switch ( data & 0xC0 )
|
||||||
{
|
{
|
||||||
case 0xC0:
|
case 0xC0:
|
||||||
apu_.write_addr( cpc_latch );
|
apu_.write_addr( cpc_latch );
|
||||||
goto enable_cpc;
|
goto enable_cpc;
|
||||||
|
|
||||||
case 0x80:
|
case 0x80:
|
||||||
apu_.write_data( time, cpc_latch );
|
apu_.write_data( time, cpc_latch );
|
||||||
goto enable_cpc;
|
goto enable_cpc;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xF4:
|
case 0xF4:
|
||||||
cpc_latch = data;
|
cpc_latch = data;
|
||||||
goto enable_cpc;
|
goto enable_cpc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
|
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
enable_cpc:
|
enable_cpc:
|
||||||
if ( !cpc_mode )
|
if ( !cpc_mode )
|
||||||
{
|
{
|
||||||
cpc_mode = true;
|
cpc_mode = true;
|
||||||
disable_beeper();
|
disable_beeper();
|
||||||
set_cpc_callback.f( set_cpc_callback.data );
|
set_cpc_callback.f( set_cpc_callback.data );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Ay_Core::cpu_in( addr_t addr )
|
int Ay_Core::cpu_in( addr_t addr )
|
||||||
{
|
{
|
||||||
// keyboard read and other things
|
// keyboard read and other things
|
||||||
if ( (addr & 0xFF) == 0xFE )
|
if ( (addr & 0xFF) == 0xFE )
|
||||||
return 0xFF; // other values break some beeper tunes
|
return 0xFF; // other values break some beeper tunes
|
||||||
|
|
||||||
dprintf( "Unmapped IN : $%04X\n", addr );
|
dprintf( "Unmapped IN : $%04X\n", addr );
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ay_Core::end_frame( time_t* end )
|
void Ay_Core::end_frame( time_t* end )
|
||||||
{
|
{
|
||||||
cpu.set_time( 0 );
|
cpu.set_time( 0 );
|
||||||
|
|
||||||
// Since detection of CPC mode will halve clock rate during the frame
|
// Since detection of CPC mode will halve clock rate during the frame
|
||||||
// and thus generate up to twice as much sound, we must generate half
|
// and thus generate up to twice as much sound, we must generate half
|
||||||
// as much until mode is known.
|
// as much until mode is known.
|
||||||
if ( !(spectrum_mode | cpc_mode) )
|
if ( !(spectrum_mode | cpc_mode) )
|
||||||
*end /= 2;
|
*end /= 2;
|
||||||
|
|
||||||
while ( cpu.time() < *end )
|
while ( cpu.time() < *end )
|
||||||
{
|
{
|
||||||
run_cpu( min( *end, next_play ) );
|
run_cpu( min( *end, next_play ) );
|
||||||
|
|
||||||
if ( cpu.time() >= next_play )
|
if ( cpu.time() >= next_play )
|
||||||
{
|
{
|
||||||
// next frame
|
// next frame
|
||||||
next_play += play_period;
|
next_play += play_period;
|
||||||
|
|
||||||
if ( cpu.r.iff1 )
|
if ( cpu.r.iff1 )
|
||||||
{
|
{
|
||||||
// interrupt enabled
|
// interrupt enabled
|
||||||
|
|
||||||
if ( mem_.ram [cpu.r.pc] == 0x76 )
|
if ( mem_.ram [cpu.r.pc] == 0x76 )
|
||||||
cpu.r.pc++; // advance past HALT instruction
|
cpu.r.pc++; // advance past HALT instruction
|
||||||
|
|
||||||
cpu.r.iff1 = 0;
|
cpu.r.iff1 = 0;
|
||||||
cpu.r.iff2 = 0;
|
cpu.r.iff2 = 0;
|
||||||
|
|
||||||
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8);
|
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8);
|
||||||
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc);
|
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc);
|
||||||
|
|
||||||
// fixed interrupt
|
// fixed interrupt
|
||||||
cpu.r.pc = 0x38;
|
cpu.r.pc = 0x38;
|
||||||
cpu.adjust_time( 12 );
|
cpu.adjust_time( 12 );
|
||||||
|
|
||||||
if ( cpu.r.im == 2 )
|
if ( cpu.r.im == 2 )
|
||||||
{
|
{
|
||||||
// vectored interrupt
|
// vectored interrupt
|
||||||
addr_t addr = cpu.r.i * 0x100 + 0xFF;
|
addr_t addr = cpu.r.i * 0x100 + 0xFF;
|
||||||
cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr];
|
cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr];
|
||||||
cpu.adjust_time( 6 );
|
cpu.adjust_time( 6 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// End time frame
|
// End time frame
|
||||||
*end = cpu.time();
|
*end = cpu.time();
|
||||||
next_play -= *end;
|
next_play -= *end;
|
||||||
check( next_play >= 0 );
|
check( next_play >= 0 );
|
||||||
cpu.adjust_time( -*end );
|
cpu.adjust_time( -*end );
|
||||||
apu_.end_frame( *end );
|
apu_.end_frame( *end );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +1,81 @@
|
||||||
// Sinclair Spectrum AY music emulator core
|
// Sinclair Spectrum AY music emulator core
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef AY_CORE_H
|
#ifndef AY_CORE_H
|
||||||
#define AY_CORE_H
|
#define AY_CORE_H
|
||||||
|
|
||||||
#include "Z80_Cpu.h"
|
#include "Z80_Cpu.h"
|
||||||
#include "Ay_Apu.h"
|
#include "Ay_Apu.h"
|
||||||
|
|
||||||
class Ay_Core {
|
class Ay_Core {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Clock count
|
// Clock count
|
||||||
typedef int time_t;
|
typedef int time_t;
|
||||||
|
|
||||||
// Sound chip access, to assign it to Blip_Buffer etc.
|
// Sound chip access, to assign it to Blip_Buffer etc.
|
||||||
Ay_Apu& apu() { return apu_; }
|
Ay_Apu& apu() { return apu_; }
|
||||||
|
|
||||||
// Sets beeper sound buffer, or NULL to mute it. Volume and treble EQ of
|
// Sets beeper sound buffer, or NULL to mute it. Volume and treble EQ of
|
||||||
// beeper are set by APU.
|
// beeper are set by APU.
|
||||||
void set_beeper_output( Blip_Buffer* );
|
void set_beeper_output( Blip_Buffer* );
|
||||||
|
|
||||||
// Sets time between calls to play routine. Can be changed while playing.
|
// Sets time between calls to play routine. Can be changed while playing.
|
||||||
void set_play_period( time_t p ) { play_period = p; }
|
void set_play_period( time_t p ) { play_period = p; }
|
||||||
|
|
||||||
// 64K memory to load code and data into before starting track. Caller
|
// 64K memory to load code and data into before starting track. Caller
|
||||||
// must parse the AY file.
|
// must parse the AY file.
|
||||||
BOOST::uint8_t* mem() { return mem_.ram; }
|
BOOST::uint8_t* mem() { return mem_.ram; }
|
||||||
enum { mem_size = 0x10000 };
|
enum { mem_size = 0x10000 };
|
||||||
enum { ram_addr = 0x4000 }; // where official RAM starts
|
enum { ram_addr = 0x4000 }; // where official RAM starts
|
||||||
|
|
||||||
// Starts track using specified register values, and sets play routine that
|
// Starts track using specified register values, and sets play routine that
|
||||||
// is called periodically
|
// is called periodically
|
||||||
typedef Z80_Cpu::registers_t registers_t;
|
typedef Z80_Cpu::registers_t registers_t;
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
void start_track( registers_t const&, addr_t play );
|
void start_track( registers_t const&, addr_t play );
|
||||||
|
|
||||||
// Ends time frame of at most *end clocks and sets *end to number of clocks
|
// Ends time frame of at most *end clocks and sets *end to number of clocks
|
||||||
// emulated. Until Spectrum/CPC mode is determined, *end is HALVED.
|
// emulated. Until Spectrum/CPC mode is determined, *end is HALVED.
|
||||||
void end_frame( time_t* end );
|
void end_frame( time_t* end );
|
||||||
|
|
||||||
// Called when CPC hardware is first accessed. AY file format doesn't specify
|
// Called when CPC hardware is first accessed. AY file format doesn't specify
|
||||||
// which sound hardware is used, so it must be determined during playback
|
// which sound hardware is used, so it must be determined during playback
|
||||||
// based on which sound port is first used.
|
// based on which sound port is first used.
|
||||||
blargg_callback<void (*)( void* )> set_cpc_callback;
|
blargg_callback<void (*)( void* )> set_cpc_callback;
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Ay_Core();
|
Ay_Core();
|
||||||
~Ay_Core();
|
~Ay_Core();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Blip_Buffer* beeper_output;
|
Blip_Buffer* beeper_output;
|
||||||
int beeper_delta;
|
int beeper_delta;
|
||||||
int last_beeper;
|
int last_beeper;
|
||||||
int beeper_mask;
|
int beeper_mask;
|
||||||
|
|
||||||
addr_t play_addr;
|
addr_t play_addr;
|
||||||
time_t play_period;
|
time_t play_period;
|
||||||
time_t next_play;
|
time_t next_play;
|
||||||
|
|
||||||
int cpc_latch;
|
int cpc_latch;
|
||||||
bool spectrum_mode;
|
bool spectrum_mode;
|
||||||
bool cpc_mode;
|
bool cpc_mode;
|
||||||
|
|
||||||
// large items
|
// large items
|
||||||
Z80_Cpu cpu;
|
Z80_Cpu cpu;
|
||||||
struct {
|
struct {
|
||||||
BOOST::uint8_t padding1 [0x100];
|
BOOST::uint8_t padding1 [0x100];
|
||||||
BOOST::uint8_t ram [mem_size + 0x100];
|
BOOST::uint8_t ram [mem_size + 0x100];
|
||||||
} mem_;
|
} mem_;
|
||||||
Ay_Apu apu_;
|
Ay_Apu apu_;
|
||||||
|
|
||||||
int cpu_in( addr_t );
|
int cpu_in( addr_t );
|
||||||
void cpu_out( time_t, addr_t, int data );
|
void cpu_out( time_t, addr_t, int data );
|
||||||
void cpu_out_( time_t, addr_t, int data );
|
void cpu_out_( time_t, addr_t, int data );
|
||||||
bool run_cpu( time_t end );
|
bool run_cpu( time_t end );
|
||||||
void disable_beeper();
|
void disable_beeper();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,59 +1,59 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Ay_Core.h"
|
#include "Ay_Core.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
//#include "z80_cpu_log.h"
|
//#include "z80_cpu_log.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
void Ay_Core::cpu_out( time_t time, addr_t addr, int data )
|
void Ay_Core::cpu_out( time_t time, addr_t addr, int data )
|
||||||
{
|
{
|
||||||
if ( (addr & 0xFF) == 0xFE )
|
if ( (addr & 0xFF) == 0xFE )
|
||||||
{
|
{
|
||||||
check( !cpc_mode );
|
check( !cpc_mode );
|
||||||
spectrum_mode = !cpc_mode;
|
spectrum_mode = !cpc_mode;
|
||||||
|
|
||||||
// beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output)
|
// beeper_mask and last_beeper are 0 if (cpc_mode || !beeper_output)
|
||||||
if ( (data &= beeper_mask) != last_beeper )
|
if ( (data &= beeper_mask) != last_beeper )
|
||||||
{
|
{
|
||||||
last_beeper = data;
|
last_beeper = data;
|
||||||
int delta = -beeper_delta;
|
int delta = -beeper_delta;
|
||||||
beeper_delta = delta;
|
beeper_delta = delta;
|
||||||
Blip_Buffer* bb = beeper_output;
|
Blip_Buffer* bb = beeper_output;
|
||||||
bb->set_modified();
|
bb->set_modified();
|
||||||
apu_.synth_.offset( time, delta, bb );
|
apu_.synth_.offset( time, delta, bb );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cpu_out_( time, addr, data );
|
cpu_out_( time, addr, data );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
|
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
|
||||||
#define IN_PORT( addr ) cpu_in( addr )
|
#define IN_PORT( addr ) cpu_in( addr )
|
||||||
#define FLAT_MEM mem
|
#define FLAT_MEM mem
|
||||||
#define CPU cpu
|
#define CPU cpu
|
||||||
|
|
||||||
#define CPU_BEGIN \
|
#define CPU_BEGIN \
|
||||||
bool Ay_Core::run_cpu( time_t end_time ) \
|
bool Ay_Core::run_cpu( time_t end_time ) \
|
||||||
{\
|
{\
|
||||||
cpu.set_end_time( end_time );\
|
cpu.set_end_time( end_time );\
|
||||||
byte* const mem = mem_.ram; // cache
|
byte* const mem = mem_.ram; // cache
|
||||||
|
|
||||||
#include "Z80_Cpu_run.h"
|
#include "Z80_Cpu_run.h"
|
||||||
|
|
||||||
return warning;
|
return warning;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
// Sinclair Spectrum AY music file emulator
|
// Sinclair Spectrum AY music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef AY_EMU_H
|
#ifndef AY_EMU_H
|
||||||
#define AY_EMU_H
|
#define AY_EMU_H
|
||||||
|
|
||||||
#include "Classic_Emu.h"
|
#include "Classic_Emu.h"
|
||||||
#include "Ay_Core.h"
|
#include "Ay_Core.h"
|
||||||
|
|
||||||
class Ay_Emu : public Classic_Emu {
|
class Ay_Emu : public Classic_Emu {
|
||||||
public:
|
public:
|
||||||
// AY file header
|
// AY file header
|
||||||
struct header_t
|
struct header_t
|
||||||
{
|
{
|
||||||
enum { size = 0x14 };
|
enum { size = 0x14 };
|
||||||
|
|
||||||
byte tag [8];
|
byte tag [8];
|
||||||
byte vers;
|
byte vers;
|
||||||
byte player;
|
byte player;
|
||||||
byte unused [2];
|
byte unused [2];
|
||||||
byte author [2];
|
byte author [2];
|
||||||
byte comment [2];
|
byte comment [2];
|
||||||
byte max_track;
|
byte max_track;
|
||||||
byte first_track;
|
byte first_track;
|
||||||
byte track_info [2];
|
byte track_info [2];
|
||||||
};
|
};
|
||||||
|
|
||||||
static gme_type_t static_type() { return gme_ay_type; }
|
static gme_type_t static_type() { return gme_ay_type; }
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Ay_Emu();
|
Ay_Emu();
|
||||||
~Ay_Emu();
|
~Ay_Emu();
|
||||||
|
|
||||||
struct file_t {
|
struct file_t {
|
||||||
header_t const* header;
|
header_t const* header;
|
||||||
byte const* tracks;
|
byte const* tracks;
|
||||||
byte const* end; // end of file data
|
byte const* end; // end of file data
|
||||||
};
|
};
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& out ) const;
|
blargg_err_t hash_( Hash_Function& out ) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||||
virtual blargg_err_t load_mem_( byte const [], int );
|
virtual blargg_err_t load_mem_( byte const [], int );
|
||||||
virtual blargg_err_t start_track_( int );
|
virtual blargg_err_t start_track_( int );
|
||||||
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
||||||
virtual void set_tempo_( double );
|
virtual void set_tempo_( double );
|
||||||
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
||||||
virtual void update_eq( blip_eq_t const& );
|
virtual void update_eq( blip_eq_t const& );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
file_t file;
|
file_t file;
|
||||||
Ay_Core core;
|
Ay_Core core;
|
||||||
|
|
||||||
void enable_cpc();
|
void enable_cpc();
|
||||||
static void enable_cpc_( void* data );
|
static void enable_cpc_( void* data );
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,124 +1,124 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Classic_Emu.h"
|
#include "Classic_Emu.h"
|
||||||
|
|
||||||
#include "Multi_Buffer.h"
|
#include "Multi_Buffer.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
Classic_Emu::Classic_Emu()
|
Classic_Emu::Classic_Emu()
|
||||||
{
|
{
|
||||||
buf = NULL;
|
buf = NULL;
|
||||||
stereo_buffer = NULL;
|
stereo_buffer = NULL;
|
||||||
voice_types = NULL;
|
voice_types = NULL;
|
||||||
|
|
||||||
// avoid inconsistency in our duplicated constants
|
// avoid inconsistency in our duplicated constants
|
||||||
assert( (int) wave_type == (int) Multi_Buffer::wave_type );
|
assert( (int) wave_type == (int) Multi_Buffer::wave_type );
|
||||||
assert( (int) noise_type == (int) Multi_Buffer::noise_type );
|
assert( (int) noise_type == (int) Multi_Buffer::noise_type );
|
||||||
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
|
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
|
||||||
}
|
}
|
||||||
|
|
||||||
Classic_Emu::~Classic_Emu()
|
Classic_Emu::~Classic_Emu()
|
||||||
{
|
{
|
||||||
delete stereo_buffer;
|
delete stereo_buffer;
|
||||||
delete effects_buffer_;
|
delete effects_buffer_;
|
||||||
effects_buffer_ = NULL;
|
effects_buffer_ = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Classic_Emu::set_equalizer_( equalizer_t const& eq )
|
void Classic_Emu::set_equalizer_( equalizer_t const& eq )
|
||||||
{
|
{
|
||||||
Music_Emu::set_equalizer_( eq );
|
Music_Emu::set_equalizer_( eq );
|
||||||
update_eq( eq.treble );
|
update_eq( eq.treble );
|
||||||
if ( buf )
|
if ( buf )
|
||||||
buf->bass_freq( (int) equalizer().bass );
|
buf->bass_freq( (int) equalizer().bass );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Classic_Emu::set_sample_rate_( int rate )
|
blargg_err_t Classic_Emu::set_sample_rate_( int rate )
|
||||||
{
|
{
|
||||||
if ( !buf )
|
if ( !buf )
|
||||||
{
|
{
|
||||||
if ( !stereo_buffer )
|
if ( !stereo_buffer )
|
||||||
CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
|
CHECK_ALLOC( stereo_buffer = BLARGG_NEW Stereo_Buffer );
|
||||||
buf = stereo_buffer;
|
buf = stereo_buffer;
|
||||||
}
|
}
|
||||||
return buf->set_sample_rate( rate, 1000 / 20 );
|
return buf->set_sample_rate( rate, 1000 / 20 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Classic_Emu::mute_voices_( int mask )
|
void Classic_Emu::mute_voices_( int mask )
|
||||||
{
|
{
|
||||||
Music_Emu::mute_voices_( mask );
|
Music_Emu::mute_voices_( mask );
|
||||||
for ( int i = voice_count(); i--; )
|
for ( int i = voice_count(); i--; )
|
||||||
{
|
{
|
||||||
if ( mask & (1 << i) )
|
if ( mask & (1 << i) )
|
||||||
{
|
{
|
||||||
set_voice( i, NULL, NULL, NULL );
|
set_voice( i, NULL, NULL, NULL );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Multi_Buffer::channel_t ch = buf->channel( i );
|
Multi_Buffer::channel_t ch = buf->channel( i );
|
||||||
assert( (ch.center && ch.left && ch.right) ||
|
assert( (ch.center && ch.left && ch.right) ||
|
||||||
(!ch.center && !ch.left && !ch.right) ); // all or nothing
|
(!ch.center && !ch.left && !ch.right) ); // all or nothing
|
||||||
set_voice( i, ch.center, ch.left, ch.right );
|
set_voice( i, ch.center, ch.left, ch.right );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Classic_Emu::change_clock_rate( int rate )
|
void Classic_Emu::change_clock_rate( int rate )
|
||||||
{
|
{
|
||||||
clock_rate_ = rate;
|
clock_rate_ = rate;
|
||||||
buf->clock_rate( rate );
|
buf->clock_rate( rate );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Classic_Emu::setup_buffer( int rate )
|
blargg_err_t Classic_Emu::setup_buffer( int rate )
|
||||||
{
|
{
|
||||||
change_clock_rate( rate );
|
change_clock_rate( rate );
|
||||||
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) );
|
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) );
|
||||||
set_equalizer( equalizer() );
|
set_equalizer( equalizer() );
|
||||||
buf_changed_count = buf->channels_changed_count();
|
buf_changed_count = buf->channels_changed_count();
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Classic_Emu::start_track_( int track )
|
blargg_err_t Classic_Emu::start_track_( int track )
|
||||||
{
|
{
|
||||||
RETURN_ERR( Music_Emu::start_track_( track ) );
|
RETURN_ERR( Music_Emu::start_track_( track ) );
|
||||||
buf->clear();
|
buf->clear();
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Classic_Emu::play_( int count, sample_t out [] )
|
blargg_err_t Classic_Emu::play_( int count, sample_t out [] )
|
||||||
{
|
{
|
||||||
// read from buffer, then refill buffer and repeat if necessary
|
// read from buffer, then refill buffer and repeat if necessary
|
||||||
int remain = count;
|
int remain = count;
|
||||||
while ( remain )
|
while ( remain )
|
||||||
{
|
{
|
||||||
buf->disable_immediate_removal();
|
buf->disable_immediate_removal();
|
||||||
remain -= buf->read_samples( &out [count - remain], remain );
|
remain -= buf->read_samples( &out [count - remain], remain );
|
||||||
if ( remain )
|
if ( remain )
|
||||||
{
|
{
|
||||||
if ( buf_changed_count != buf->channels_changed_count() )
|
if ( buf_changed_count != buf->channels_changed_count() )
|
||||||
{
|
{
|
||||||
buf_changed_count = buf->channels_changed_count();
|
buf_changed_count = buf->channels_changed_count();
|
||||||
remute_voices();
|
remute_voices();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use more accurate length calculation
|
// TODO: use more accurate length calculation
|
||||||
int msec = buf->length();
|
int msec = buf->length();
|
||||||
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100;
|
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100;
|
||||||
RETURN_ERR( run_clocks( clocks_emulated, msec ) );
|
RETURN_ERR( run_clocks( clocks_emulated, msec ) );
|
||||||
assert( clocks_emulated );
|
assert( clocks_emulated );
|
||||||
buf->end_frame( clocks_emulated );
|
buf->end_frame( clocks_emulated );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,79 @@
|
||||||
// Common aspects of emulators which use Blip_Buffer for sound output
|
// Common aspects of emulators which use Blip_Buffer for sound output
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef CLASSIC_EMU_H
|
#ifndef CLASSIC_EMU_H
|
||||||
#define CLASSIC_EMU_H
|
#define CLASSIC_EMU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
#include "Music_Emu.h"
|
#include "Music_Emu.h"
|
||||||
|
|
||||||
class Classic_Emu : public Music_Emu {
|
class Classic_Emu : public Music_Emu {
|
||||||
protected:
|
protected:
|
||||||
// Derived interface
|
// Derived interface
|
||||||
|
|
||||||
// Advertises type of sound on each voice, so Effects_Buffer can better choose
|
// Advertises type of sound on each voice, so Effects_Buffer can better choose
|
||||||
// what effect to apply (pan, echo, surround). Constant can have value added so
|
// what effect to apply (pan, echo, surround). Constant can have value added so
|
||||||
// that voices of the same type can be spread around the stereo sound space.
|
// that voices of the same type can be spread around the stereo sound space.
|
||||||
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
|
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
|
||||||
void set_voice_types( int const types [] ) { voice_types = types; }
|
void set_voice_types( int const types [] ) { voice_types = types; }
|
||||||
|
|
||||||
// Sets up Blip_Buffers after loading file
|
// Sets up Blip_Buffers after loading file
|
||||||
blargg_err_t setup_buffer( int clock_rate );
|
blargg_err_t setup_buffer( int clock_rate );
|
||||||
|
|
||||||
// Clock rate of Blip_buffers
|
// Clock rate of Blip_buffers
|
||||||
int clock_rate() const { return clock_rate_; }
|
int clock_rate() const { return clock_rate_; }
|
||||||
|
|
||||||
// Changes clock rate of Blip_Buffers (experimental)
|
// Changes clock rate of Blip_Buffers (experimental)
|
||||||
void change_clock_rate( int );
|
void change_clock_rate( int );
|
||||||
|
|
||||||
// Overrides should do the indicated task
|
// Overrides should do the indicated task
|
||||||
|
|
||||||
// Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL
|
// Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL
|
||||||
virtual void set_voice( int index, Blip_Buffer* center,
|
virtual void set_voice( int index, Blip_Buffer* center,
|
||||||
Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; )
|
Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; )
|
||||||
|
|
||||||
// Update equalization
|
// Update equalization
|
||||||
virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; )
|
virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; )
|
||||||
|
|
||||||
// Start track
|
// Start track
|
||||||
virtual blargg_err_t start_track_( int track ) BLARGG_PURE( ; )
|
virtual blargg_err_t start_track_( int track ) BLARGG_PURE( ; )
|
||||||
|
|
||||||
// Run for at most msec or time_io clocks, then set time_io to number of clocks
|
// Run for at most msec or time_io clocks, then set time_io to number of clocks
|
||||||
// actually run for. After returning, Blip_Buffers have time frame of time_io clocks
|
// actually run for. After returning, Blip_Buffers have time frame of time_io clocks
|
||||||
// ended.
|
// ended.
|
||||||
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; )
|
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; )
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
public:
|
public:
|
||||||
Classic_Emu();
|
Classic_Emu();
|
||||||
~Classic_Emu();
|
~Classic_Emu();
|
||||||
virtual void set_buffer( Multi_Buffer* );
|
virtual void set_buffer( Multi_Buffer* );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t set_sample_rate_( int sample_rate );
|
virtual blargg_err_t set_sample_rate_( int sample_rate );
|
||||||
virtual void mute_voices_( int );
|
virtual void mute_voices_( int );
|
||||||
virtual void set_equalizer_( equalizer_t const& );
|
virtual void set_equalizer_( equalizer_t const& );
|
||||||
virtual blargg_err_t play_( int, sample_t [] );
|
virtual blargg_err_t play_( int, sample_t [] );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Multi_Buffer* buf;
|
Multi_Buffer* buf;
|
||||||
Multi_Buffer* stereo_buffer; // NULL if using custom buffer
|
Multi_Buffer* stereo_buffer; // NULL if using custom buffer
|
||||||
int clock_rate_;
|
int clock_rate_;
|
||||||
unsigned buf_changed_count;
|
unsigned buf_changed_count;
|
||||||
int const* voice_types;
|
int const* voice_types;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
|
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
|
||||||
{
|
{
|
||||||
assert( !buf && new_buf );
|
assert( !buf && new_buf );
|
||||||
buf = new_buf;
|
buf = new_buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { }
|
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { }
|
||||||
|
|
||||||
inline void Classic_Emu::update_eq( blip_eq_t const& ) { }
|
inline void Classic_Emu::update_eq( blip_eq_t const& ) { }
|
||||||
|
|
||||||
inline blargg_err_t Classic_Emu::run_clocks( blip_time_t&, int ) { return blargg_ok; }
|
inline blargg_err_t Classic_Emu::run_clocks( blip_time_t&, int ) { return blargg_ok; }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,315 +1,315 @@
|
||||||
// File_Extractor 0.4.0. http://www.slack.net/~ant/
|
// File_Extractor 0.4.0. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Data_Reader.h"
|
#include "Data_Reader.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
|
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
const char Data_Reader::eof_error [] = "Unexpected end of file";
|
const char Data_Reader::eof_error [] = "Unexpected end of file";
|
||||||
|
|
||||||
blargg_err_t Data_Reader::read( void* p, long s )
|
blargg_err_t Data_Reader::read( void* p, long s )
|
||||||
{
|
{
|
||||||
long result = read_avail( p, s );
|
long result = read_avail( p, s );
|
||||||
if ( result != s )
|
if ( result != s )
|
||||||
{
|
{
|
||||||
if ( result >= 0 && result < s )
|
if ( result >= 0 && result < s )
|
||||||
return eof_error;
|
return eof_error;
|
||||||
|
|
||||||
return "Read error";
|
return "Read error";
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Data_Reader::skip( long count )
|
blargg_err_t Data_Reader::skip( long count )
|
||||||
{
|
{
|
||||||
char buf [512];
|
char buf [512];
|
||||||
while ( count )
|
while ( count )
|
||||||
{
|
{
|
||||||
long n = sizeof buf;
|
long n = sizeof buf;
|
||||||
if ( n > count )
|
if ( n > count )
|
||||||
n = count;
|
n = count;
|
||||||
count -= n;
|
count -= n;
|
||||||
RETURN_ERR( read( buf, n ) );
|
RETURN_ERR( read( buf, n ) );
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long File_Reader::remain() const { return size() - tell(); }
|
long File_Reader::remain() const { return size() - tell(); }
|
||||||
|
|
||||||
blargg_err_t File_Reader::skip( long n )
|
blargg_err_t File_Reader::skip( long n )
|
||||||
{
|
{
|
||||||
assert( n >= 0 );
|
assert( n >= 0 );
|
||||||
if ( !n )
|
if ( !n )
|
||||||
return 0;
|
return 0;
|
||||||
return seek( tell() + n );
|
return seek( tell() + n );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subset_Reader
|
// Subset_Reader
|
||||||
|
|
||||||
Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
|
Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
|
||||||
{
|
{
|
||||||
in = dr;
|
in = dr;
|
||||||
remain_ = dr->remain();
|
remain_ = dr->remain();
|
||||||
if ( remain_ > size )
|
if ( remain_ > size )
|
||||||
remain_ = size;
|
remain_ = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Subset_Reader::remain() const { return remain_; }
|
long Subset_Reader::remain() const { return remain_; }
|
||||||
|
|
||||||
long Subset_Reader::read_avail( void* p, long s )
|
long Subset_Reader::read_avail( void* p, long s )
|
||||||
{
|
{
|
||||||
if ( s > remain_ )
|
if ( s > remain_ )
|
||||||
s = remain_;
|
s = remain_;
|
||||||
remain_ -= s;
|
remain_ -= s;
|
||||||
return in->read_avail( p, s );
|
return in->read_avail( p, s );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining_Reader
|
// Remaining_Reader
|
||||||
|
|
||||||
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
|
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
|
||||||
{
|
{
|
||||||
header = (char const*) h;
|
header = (char const*) h;
|
||||||
header_end = header + size;
|
header_end = header + size;
|
||||||
in = r;
|
in = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
|
long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
|
||||||
|
|
||||||
long Remaining_Reader::read_first( void* out, long count )
|
long Remaining_Reader::read_first( void* out, long count )
|
||||||
{
|
{
|
||||||
long first = header_end - header;
|
long first = header_end - header;
|
||||||
if ( first )
|
if ( first )
|
||||||
{
|
{
|
||||||
if ( first > count )
|
if ( first > count )
|
||||||
first = count;
|
first = count;
|
||||||
void const* old = header;
|
void const* old = header;
|
||||||
header += first;
|
header += first;
|
||||||
memcpy( out, old, first );
|
memcpy( out, old, first );
|
||||||
}
|
}
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Remaining_Reader::read_avail( void* out, long count )
|
long Remaining_Reader::read_avail( void* out, long count )
|
||||||
{
|
{
|
||||||
long first = read_first( out, count );
|
long first = read_first( out, count );
|
||||||
long second = count - first;
|
long second = count - first;
|
||||||
if ( second )
|
if ( second )
|
||||||
{
|
{
|
||||||
second = in->read_avail( (char*) out + first, second );
|
second = in->read_avail( (char*) out + first, second );
|
||||||
if ( second <= 0 )
|
if ( second <= 0 )
|
||||||
return second;
|
return second;
|
||||||
}
|
}
|
||||||
return first + second;
|
return first + second;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Remaining_Reader::read( void* out, long count )
|
blargg_err_t Remaining_Reader::read( void* out, long count )
|
||||||
{
|
{
|
||||||
long first = read_first( out, count );
|
long first = read_first( out, count );
|
||||||
long second = count - first;
|
long second = count - first;
|
||||||
if ( !second )
|
if ( !second )
|
||||||
return 0;
|
return 0;
|
||||||
return in->read( (char*) out + first, second );
|
return in->read( (char*) out + first, second );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mem_File_Reader
|
// Mem_File_Reader
|
||||||
|
|
||||||
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
|
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
|
||||||
begin( (const char*) p ),
|
begin( (const char*) p ),
|
||||||
size_( s )
|
size_( s )
|
||||||
{
|
{
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Mem_File_Reader::size() const { return size_; }
|
long Mem_File_Reader::size() const { return size_; }
|
||||||
|
|
||||||
long Mem_File_Reader::read_avail( void* p, long s )
|
long Mem_File_Reader::read_avail( void* p, long s )
|
||||||
{
|
{
|
||||||
long r = remain();
|
long r = remain();
|
||||||
if ( s > r )
|
if ( s > r )
|
||||||
s = r;
|
s = r;
|
||||||
memcpy( p, begin + pos, s );
|
memcpy( p, begin + pos, s );
|
||||||
pos += s;
|
pos += s;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Mem_File_Reader::tell() const { return pos; }
|
long Mem_File_Reader::tell() const { return pos; }
|
||||||
|
|
||||||
blargg_err_t Mem_File_Reader::seek( long n )
|
blargg_err_t Mem_File_Reader::seek( long n )
|
||||||
{
|
{
|
||||||
if ( n > size_ )
|
if ( n > size_ )
|
||||||
return eof_error;
|
return eof_error;
|
||||||
pos = n;
|
pos = n;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback_Reader
|
// Callback_Reader
|
||||||
|
|
||||||
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
|
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
|
||||||
callback( c ),
|
callback( c ),
|
||||||
data( d )
|
data( d )
|
||||||
{
|
{
|
||||||
remain_ = size;
|
remain_ = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Callback_Reader::remain() const { return remain_; }
|
long Callback_Reader::remain() const { return remain_; }
|
||||||
|
|
||||||
long Callback_Reader::read_avail( void* out, long count )
|
long Callback_Reader::read_avail( void* out, long count )
|
||||||
{
|
{
|
||||||
if ( count > remain_ )
|
if ( count > remain_ )
|
||||||
count = remain_;
|
count = remain_;
|
||||||
if ( Callback_Reader::read( out, count ) )
|
if ( Callback_Reader::read( out, count ) )
|
||||||
count = -1;
|
count = -1;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Callback_Reader::read( void* out, long count )
|
blargg_err_t Callback_Reader::read( void* out, long count )
|
||||||
{
|
{
|
||||||
if ( count > remain_ )
|
if ( count > remain_ )
|
||||||
return eof_error;
|
return eof_error;
|
||||||
return callback( data, out, count );
|
return callback( data, out, count );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Std_File_Reader
|
// Std_File_Reader
|
||||||
|
|
||||||
Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
|
Std_File_Reader::Std_File_Reader() : file_( 0 ) { }
|
||||||
|
|
||||||
Std_File_Reader::~Std_File_Reader() { close(); }
|
Std_File_Reader::~Std_File_Reader() { close(); }
|
||||||
|
|
||||||
blargg_err_t Std_File_Reader::open( const char* path )
|
blargg_err_t Std_File_Reader::open( const char* path )
|
||||||
{
|
{
|
||||||
file_ = fopen( path, "rb" );
|
file_ = fopen( path, "rb" );
|
||||||
if ( !file_ )
|
if ( !file_ )
|
||||||
return "Couldn't open file";
|
return "Couldn't open file";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Std_File_Reader::size() const
|
long Std_File_Reader::size() const
|
||||||
{
|
{
|
||||||
long pos = tell();
|
long pos = tell();
|
||||||
fseek( (FILE*) file_, 0, SEEK_END );
|
fseek( (FILE*) file_, 0, SEEK_END );
|
||||||
long result = tell();
|
long result = tell();
|
||||||
fseek( (FILE*) file_, pos, SEEK_SET );
|
fseek( (FILE*) file_, pos, SEEK_SET );
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Std_File_Reader::read_avail( void* p, long s )
|
long Std_File_Reader::read_avail( void* p, long s )
|
||||||
{
|
{
|
||||||
return fread( p, 1, s, (FILE*) file_ );
|
return fread( p, 1, s, (FILE*) file_ );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Std_File_Reader::read( void* p, long s )
|
blargg_err_t Std_File_Reader::read( void* p, long s )
|
||||||
{
|
{
|
||||||
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
|
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) )
|
||||||
return 0;
|
return 0;
|
||||||
if ( feof( (FILE*) file_ ) )
|
if ( feof( (FILE*) file_ ) )
|
||||||
return eof_error;
|
return eof_error;
|
||||||
return "Couldn't read from file";
|
return "Couldn't read from file";
|
||||||
}
|
}
|
||||||
|
|
||||||
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }
|
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); }
|
||||||
|
|
||||||
blargg_err_t Std_File_Reader::seek( long n )
|
blargg_err_t Std_File_Reader::seek( long n )
|
||||||
{
|
{
|
||||||
if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
|
if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
|
||||||
return 0;
|
return 0;
|
||||||
if ( n > size() )
|
if ( n > size() )
|
||||||
return eof_error;
|
return eof_error;
|
||||||
return "Error seeking in file";
|
return "Error seeking in file";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Std_File_Reader::close()
|
void Std_File_Reader::close()
|
||||||
{
|
{
|
||||||
if ( file_ )
|
if ( file_ )
|
||||||
{
|
{
|
||||||
fclose( (FILE*) file_ );
|
fclose( (FILE*) file_ );
|
||||||
file_ = 0;
|
file_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gzip_File_Reader
|
// Gzip_File_Reader
|
||||||
|
|
||||||
#ifdef HAVE_ZLIB_H
|
#ifdef HAVE_ZLIB_H
|
||||||
|
|
||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
|
|
||||||
static const char* get_gzip_eof( const char* path, long* eof )
|
static const char* get_gzip_eof( const char* path, long* eof )
|
||||||
{
|
{
|
||||||
FILE* file = fopen( path, "rb" );
|
FILE* file = fopen( path, "rb" );
|
||||||
if ( !file )
|
if ( !file )
|
||||||
return "Couldn't open file";
|
return "Couldn't open file";
|
||||||
|
|
||||||
unsigned char buf [4];
|
unsigned char buf [4];
|
||||||
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
|
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
|
||||||
{
|
{
|
||||||
fseek( file, -4, SEEK_END );
|
fseek( file, -4, SEEK_END );
|
||||||
fread( buf, 4, 1, file );
|
fread( buf, 4, 1, file );
|
||||||
*eof = get_le32( buf );
|
*eof = get_le32( buf );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fseek( file, 0, SEEK_END );
|
fseek( file, 0, SEEK_END );
|
||||||
*eof = ftell( file );
|
*eof = ftell( file );
|
||||||
}
|
}
|
||||||
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
|
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
|
||||||
fclose( file );
|
fclose( file );
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
|
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
|
||||||
|
|
||||||
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
|
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
|
||||||
|
|
||||||
blargg_err_t Gzip_File_Reader::open( const char* path )
|
blargg_err_t Gzip_File_Reader::open( const char* path )
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
|
|
||||||
RETURN_ERR( get_gzip_eof( path, &size_ ) );
|
RETURN_ERR( get_gzip_eof( path, &size_ ) );
|
||||||
|
|
||||||
file_ = gzopen( path, "rb" );
|
file_ = gzopen( path, "rb" );
|
||||||
if ( !file_ )
|
if ( !file_ )
|
||||||
return "Couldn't open file";
|
return "Couldn't open file";
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
long Gzip_File_Reader::size() const { return size_; }
|
long Gzip_File_Reader::size() const { return size_; }
|
||||||
|
|
||||||
long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); }
|
long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); }
|
||||||
|
|
||||||
long Gzip_File_Reader::tell() const { return gztell( file_ ); }
|
long Gzip_File_Reader::tell() const { return gztell( file_ ); }
|
||||||
|
|
||||||
blargg_err_t Gzip_File_Reader::seek( long n )
|
blargg_err_t Gzip_File_Reader::seek( long n )
|
||||||
{
|
{
|
||||||
if ( gzseek( file_, n, SEEK_SET ) >= 0 )
|
if ( gzseek( file_, n, SEEK_SET ) >= 0 )
|
||||||
return 0;
|
return 0;
|
||||||
if ( n > size_ )
|
if ( n > size_ )
|
||||||
return eof_error;
|
return eof_error;
|
||||||
return "Error seeking in file";
|
return "Error seeking in file";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gzip_File_Reader::close()
|
void Gzip_File_Reader::close()
|
||||||
{
|
{
|
||||||
if ( file_ )
|
if ( file_ )
|
||||||
{
|
{
|
||||||
gzclose( file_ );
|
gzclose( file_ );
|
||||||
file_ = 0;
|
file_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,151 +1,151 @@
|
||||||
// Data reader interface for uniform access
|
// Data reader interface for uniform access
|
||||||
|
|
||||||
// File_Extractor 0.4.0
|
// File_Extractor 0.4.0
|
||||||
#ifndef DATA_READER_H
|
#ifndef DATA_READER_H
|
||||||
#define DATA_READER_H
|
#define DATA_READER_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
|
|
||||||
// Supports reading and finding out how many bytes are remaining
|
// Supports reading and finding out how many bytes are remaining
|
||||||
class Data_Reader {
|
class Data_Reader {
|
||||||
public:
|
public:
|
||||||
virtual ~Data_Reader() { }
|
virtual ~Data_Reader() { }
|
||||||
|
|
||||||
static const char eof_error []; // returned by read() when request goes beyond end
|
static const char eof_error []; // returned by read() when request goes beyond end
|
||||||
|
|
||||||
// Read at most count bytes and return number actually read, or <= 0 if error
|
// Read at most count bytes and return number actually read, or <= 0 if error
|
||||||
virtual long read_avail( void*, long n ) = 0;
|
virtual long read_avail( void*, long n ) = 0;
|
||||||
|
|
||||||
// Read exactly count bytes and return error if they couldn't be read
|
// Read exactly count bytes and return error if they couldn't be read
|
||||||
virtual blargg_err_t read( void*, long count );
|
virtual blargg_err_t read( void*, long count );
|
||||||
|
|
||||||
// Number of bytes remaining until end of file
|
// Number of bytes remaining until end of file
|
||||||
virtual long remain() const = 0;
|
virtual long remain() const = 0;
|
||||||
|
|
||||||
// Read and discard count bytes
|
// Read and discard count bytes
|
||||||
virtual blargg_err_t skip( long count );
|
virtual blargg_err_t skip( long count );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Data_Reader() { }
|
Data_Reader() { }
|
||||||
typedef blargg_err_t error_t; // deprecated
|
typedef blargg_err_t error_t; // deprecated
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Data_Reader( const Data_Reader& );
|
Data_Reader( const Data_Reader& );
|
||||||
Data_Reader& operator = ( const Data_Reader& );
|
Data_Reader& operator = ( const Data_Reader& );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Supports seeking in addition to Data_Reader operations
|
// Supports seeking in addition to Data_Reader operations
|
||||||
class File_Reader : public Data_Reader {
|
class File_Reader : public Data_Reader {
|
||||||
public:
|
public:
|
||||||
// Size of file
|
// Size of file
|
||||||
virtual long size() const = 0;
|
virtual long size() const = 0;
|
||||||
|
|
||||||
// Current position in file
|
// Current position in file
|
||||||
virtual long tell() const = 0;
|
virtual long tell() const = 0;
|
||||||
|
|
||||||
// Go to new position
|
// Go to new position
|
||||||
virtual blargg_err_t seek( long ) = 0;
|
virtual blargg_err_t seek( long ) = 0;
|
||||||
|
|
||||||
long remain() const;
|
long remain() const;
|
||||||
blargg_err_t skip( long n );
|
blargg_err_t skip( long n );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disk file reader
|
// Disk file reader
|
||||||
class Std_File_Reader : public File_Reader {
|
class Std_File_Reader : public File_Reader {
|
||||||
public:
|
public:
|
||||||
blargg_err_t open( const char* path );
|
blargg_err_t open( const char* path );
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Std_File_Reader();
|
Std_File_Reader();
|
||||||
~Std_File_Reader();
|
~Std_File_Reader();
|
||||||
long size() const;
|
long size() const;
|
||||||
blargg_err_t read( void*, long );
|
blargg_err_t read( void*, long );
|
||||||
long read_avail( void*, long );
|
long read_avail( void*, long );
|
||||||
long tell() const;
|
long tell() const;
|
||||||
blargg_err_t seek( long );
|
blargg_err_t seek( long );
|
||||||
private:
|
private:
|
||||||
void* file_;
|
void* file_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Treats range of memory as a file
|
// Treats range of memory as a file
|
||||||
class Mem_File_Reader : public File_Reader {
|
class Mem_File_Reader : public File_Reader {
|
||||||
public:
|
public:
|
||||||
Mem_File_Reader( const void*, long size );
|
Mem_File_Reader( const void*, long size );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
long size() const;
|
long size() const;
|
||||||
long read_avail( void*, long );
|
long read_avail( void*, long );
|
||||||
long tell() const;
|
long tell() const;
|
||||||
blargg_err_t seek( long );
|
blargg_err_t seek( long );
|
||||||
private:
|
private:
|
||||||
const char* const begin;
|
const char* const begin;
|
||||||
const long size_;
|
const long size_;
|
||||||
long pos;
|
long pos;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Makes it look like there are only count bytes remaining
|
// Makes it look like there are only count bytes remaining
|
||||||
class Subset_Reader : public Data_Reader {
|
class Subset_Reader : public Data_Reader {
|
||||||
public:
|
public:
|
||||||
Subset_Reader( Data_Reader*, long count );
|
Subset_Reader( Data_Reader*, long count );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
long remain() const;
|
long remain() const;
|
||||||
long read_avail( void*, long );
|
long read_avail( void*, long );
|
||||||
private:
|
private:
|
||||||
Data_Reader* in;
|
Data_Reader* in;
|
||||||
long remain_;
|
long remain_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Joins already-read header and remaining data into original file (to avoid seeking)
|
// Joins already-read header and remaining data into original file (to avoid seeking)
|
||||||
class Remaining_Reader : public Data_Reader {
|
class Remaining_Reader : public Data_Reader {
|
||||||
public:
|
public:
|
||||||
Remaining_Reader( void const* header, long size, Data_Reader* );
|
Remaining_Reader( void const* header, long size, Data_Reader* );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
long remain() const;
|
long remain() const;
|
||||||
long read_avail( void*, long );
|
long read_avail( void*, long );
|
||||||
blargg_err_t read( void*, long );
|
blargg_err_t read( void*, long );
|
||||||
private:
|
private:
|
||||||
char const* header;
|
char const* header;
|
||||||
char const* header_end;
|
char const* header_end;
|
||||||
Data_Reader* in;
|
Data_Reader* in;
|
||||||
long read_first( void* out, long count );
|
long read_first( void* out, long count );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Invokes callback function to read data. Size of data must be specified in advance.
|
// Invokes callback function to read data. Size of data must be specified in advance.
|
||||||
class Callback_Reader : public Data_Reader {
|
class Callback_Reader : public Data_Reader {
|
||||||
public:
|
public:
|
||||||
typedef const char* (*callback_t)( void* data, void* out, long count );
|
typedef const char* (*callback_t)( void* data, void* out, long count );
|
||||||
Callback_Reader( callback_t, long size, void* data = 0 );
|
Callback_Reader( callback_t, long size, void* data = 0 );
|
||||||
public:
|
public:
|
||||||
long read_avail( void*, long );
|
long read_avail( void*, long );
|
||||||
blargg_err_t read( void*, long );
|
blargg_err_t read( void*, long );
|
||||||
long remain() const;
|
long remain() const;
|
||||||
private:
|
private:
|
||||||
callback_t const callback;
|
callback_t const callback;
|
||||||
void* const data;
|
void* const data;
|
||||||
long remain_;
|
long remain_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef HAVE_ZLIB_H
|
#ifdef HAVE_ZLIB_H
|
||||||
// Gzip compressed file reader
|
// Gzip compressed file reader
|
||||||
class Gzip_File_Reader : public File_Reader {
|
class Gzip_File_Reader : public File_Reader {
|
||||||
public:
|
public:
|
||||||
blargg_err_t open( const char* path );
|
blargg_err_t open( const char* path );
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Gzip_File_Reader();
|
Gzip_File_Reader();
|
||||||
~Gzip_File_Reader();
|
~Gzip_File_Reader();
|
||||||
long size() const;
|
long size() const;
|
||||||
long read_avail( void*, long );
|
long read_avail( void*, long );
|
||||||
long tell() const;
|
long tell() const;
|
||||||
blargg_err_t seek( long );
|
blargg_err_t seek( long );
|
||||||
private:
|
private:
|
||||||
void* file_;
|
void* file_;
|
||||||
long size_;
|
long size_;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,74 +1,74 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Downsampler.h"
|
#include "Downsampler.h"
|
||||||
|
|
||||||
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const shift = 14;
|
int const shift = 14;
|
||||||
int const unit = 1 << shift;
|
int const unit = 1 << shift;
|
||||||
|
|
||||||
void Downsampler::clear_()
|
void Downsampler::clear_()
|
||||||
{
|
{
|
||||||
pos = 0;
|
pos = 0;
|
||||||
Resampler::clear_();
|
Resampler::clear_();
|
||||||
}
|
}
|
||||||
|
|
||||||
Downsampler::Downsampler()
|
Downsampler::Downsampler()
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Downsampler::set_rate_( double new_factor )
|
blargg_err_t Downsampler::set_rate_( double new_factor )
|
||||||
{
|
{
|
||||||
step = (int) (new_factor * unit + 0.5);
|
step = (int) (new_factor * unit + 0.5);
|
||||||
return Resampler::set_rate_( 1.0 / unit * step );
|
return Resampler::set_rate_( 1.0 / unit * step );
|
||||||
}
|
}
|
||||||
|
|
||||||
Resampler::sample_t const* Downsampler::resample_( sample_t** out_,
|
Resampler::sample_t const* Downsampler::resample_( sample_t** out_,
|
||||||
sample_t const* out_end, sample_t const in [], int in_size )
|
sample_t const* out_end, sample_t const in [], int in_size )
|
||||||
{
|
{
|
||||||
in_size -= write_offset;
|
in_size -= write_offset;
|
||||||
if ( in_size > 0 )
|
if ( in_size > 0 )
|
||||||
{
|
{
|
||||||
sample_t* BLARGG_RESTRICT out = *out_;
|
sample_t* BLARGG_RESTRICT out = *out_;
|
||||||
sample_t const* const in_end = in + in_size;
|
sample_t const* const in_end = in + in_size;
|
||||||
|
|
||||||
int const step = this->step;
|
int const step = this->step;
|
||||||
int pos = this->pos;
|
int pos = this->pos;
|
||||||
|
|
||||||
// TODO: IIR filter, then linear resample
|
// TODO: IIR filter, then linear resample
|
||||||
// TODO: detect skipped sample, allowing merging of IIR and resample?
|
// TODO: detect skipped sample, allowing merging of IIR and resample?
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
#define INTERP( i, out )\
|
#define INTERP( i, out )\
|
||||||
out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\
|
out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\
|
||||||
in [8 + i] * pos) >> (shift + 2);
|
in [8 + i] * pos) >> (shift + 2);
|
||||||
|
|
||||||
int out_0;
|
int out_0;
|
||||||
INTERP( 0, out_0 )
|
INTERP( 0, out_0 )
|
||||||
INTERP( 1, out [0] = out_0; out [1] )
|
INTERP( 1, out [0] = out_0; out [1] )
|
||||||
out += stereo;
|
out += stereo;
|
||||||
|
|
||||||
pos += step;
|
pos += step;
|
||||||
in += ((unsigned) pos >> shift) * stereo;
|
in += ((unsigned) pos >> shift) * stereo;
|
||||||
pos &= unit - 1;
|
pos &= unit - 1;
|
||||||
}
|
}
|
||||||
while ( in < in_end && out < out_end );
|
while ( in < in_end && out < out_end );
|
||||||
|
|
||||||
this->pos = pos;
|
this->pos = pos;
|
||||||
*out_ = out;
|
*out_ = out;
|
||||||
}
|
}
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
// Linear downsampler with pre-low-pass
|
// Linear downsampler with pre-low-pass
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef DOWNSAMPLER_H
|
#ifndef DOWNSAMPLER_H
|
||||||
#define DOWNSAMPLER_H
|
#define DOWNSAMPLER_H
|
||||||
|
|
||||||
#include "Resampler.h"
|
#include "Resampler.h"
|
||||||
|
|
||||||
class Downsampler : public Resampler {
|
class Downsampler : public Resampler {
|
||||||
public:
|
public:
|
||||||
Downsampler();
|
Downsampler();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t set_rate_( double );
|
virtual blargg_err_t set_rate_( double );
|
||||||
virtual void clear_();
|
virtual void clear_();
|
||||||
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
|
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { stereo = 2 };
|
enum { stereo = 2 };
|
||||||
enum { write_offset = 8 * stereo };
|
enum { write_offset = 8 * stereo };
|
||||||
int pos;
|
int pos;
|
||||||
int step;
|
int step;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
// Combination of Fir_Resampler and Stereo_Buffer mixing. Used by Sega FM emulators.
|
// Combination of Fir_Resampler and Stereo_Buffer mixing. Used by Sega FM emulators.
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef DUAL_RESAMPLER_H
|
#ifndef DUAL_RESAMPLER_H
|
||||||
#define DUAL_RESAMPLER_H
|
#define DUAL_RESAMPLER_H
|
||||||
|
|
||||||
#include "Multi_Buffer.h"
|
#include "Multi_Buffer.h"
|
||||||
|
|
||||||
#if GME_VGM_FAST_RESAMPLER
|
#if GME_VGM_FAST_RESAMPLER
|
||||||
#include "Downsampler.h"
|
#include "Downsampler.h"
|
||||||
typedef Downsampler Dual_Resampler_Downsampler;
|
typedef Downsampler Dual_Resampler_Downsampler;
|
||||||
#else
|
#else
|
||||||
#include "Fir_Resampler.h"
|
#include "Fir_Resampler.h"
|
||||||
typedef Fir_Resampler_Norm Dual_Resampler_Downsampler;
|
typedef Fir_Resampler_Norm Dual_Resampler_Downsampler;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Dual_Resampler {
|
class Dual_Resampler {
|
||||||
public:
|
public:
|
||||||
typedef short dsample_t;
|
typedef short dsample_t;
|
||||||
|
|
||||||
blargg_err_t setup( double oversample, double rolloff, double gain );
|
blargg_err_t setup( double oversample, double rolloff, double gain );
|
||||||
double rate() const { return resampler.rate(); }
|
double rate() const { return resampler.rate(); }
|
||||||
blargg_err_t reset( int max_pairs );
|
blargg_err_t reset( int max_pairs );
|
||||||
void resize( int pairs_per_frame );
|
void resize( int pairs_per_frame );
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
void dual_play( int count, dsample_t out [], Stereo_Buffer&, Stereo_Buffer** secondary_buf_set = NULL, int secondary_buf_set_count = 0 );
|
void dual_play( int count, dsample_t out [], Stereo_Buffer&, Stereo_Buffer** secondary_buf_set = NULL, int secondary_buf_set_count = 0 );
|
||||||
|
|
||||||
blargg_callback<int (*)( void*, blip_time_t, int, dsample_t* )> set_callback;
|
blargg_callback<int (*)( void*, blip_time_t, int, dsample_t* )> set_callback;
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Dual_Resampler();
|
Dual_Resampler();
|
||||||
~Dual_Resampler();
|
~Dual_Resampler();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { gain_bits = 14 };
|
enum { gain_bits = 14 };
|
||||||
blargg_vector<dsample_t> sample_buf;
|
blargg_vector<dsample_t> sample_buf;
|
||||||
int sample_buf_size;
|
int sample_buf_size;
|
||||||
int oversamples_per_frame;
|
int oversamples_per_frame;
|
||||||
int buf_pos;
|
int buf_pos;
|
||||||
int buffered;
|
int buffered;
|
||||||
int resampler_size;
|
int resampler_size;
|
||||||
int gain_;
|
int gain_;
|
||||||
|
|
||||||
Dual_Resampler_Downsampler resampler;
|
Dual_Resampler_Downsampler resampler;
|
||||||
void mix_samples( Stereo_Buffer&, dsample_t [], int, Stereo_Buffer**, int );
|
void mix_samples( Stereo_Buffer&, dsample_t [], int, Stereo_Buffer**, int );
|
||||||
void mix_mono( Stereo_Buffer&, dsample_t [], int );
|
void mix_mono( Stereo_Buffer&, dsample_t [], int );
|
||||||
void mix_stereo( Stereo_Buffer&, dsample_t [], int );
|
void mix_stereo( Stereo_Buffer&, dsample_t [], int );
|
||||||
void mix_extra_mono( Stereo_Buffer&, dsample_t [], int );
|
void mix_extra_mono( Stereo_Buffer&, dsample_t [], int );
|
||||||
void mix_extra_stereo( Stereo_Buffer&, dsample_t [], int );
|
void mix_extra_stereo( Stereo_Buffer&, dsample_t [], int );
|
||||||
int play_frame_( Stereo_Buffer&, dsample_t [], Stereo_Buffer**, int );
|
int play_frame_( Stereo_Buffer&, dsample_t [], Stereo_Buffer**, int );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline blargg_err_t Dual_Resampler::setup( double oversample, double rolloff, double gain )
|
inline blargg_err_t Dual_Resampler::setup( double oversample, double rolloff, double gain )
|
||||||
{
|
{
|
||||||
gain_ = (int) ((1 << gain_bits) * gain);
|
gain_ = (int) ((1 << gain_bits) * gain);
|
||||||
return resampler.set_rate( oversample );
|
return resampler.set_rate( oversample );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -18,13 +18,13 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
#ifdef BLARGG_ENABLE_OPTIMIZER
|
#ifdef BLARGG_ENABLE_OPTIMIZER
|
||||||
#include BLARGG_ENABLE_OPTIMIZER
|
#include BLARGG_ENABLE_OPTIMIZER
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int const fixed_shift = 12;
|
int const fixed_shift = 12;
|
||||||
#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift))
|
#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift))
|
||||||
#define FROM_FIXED( f ) ((f) >> fixed_shift)
|
#define FROM_FIXED( f ) ((f) >> fixed_shift)
|
||||||
|
|
||||||
int const max_read = 2560; // determines minimum delay
|
int const max_read = 2560; // determines minimum delay
|
||||||
|
|
||||||
Effects_Buffer::Effects_Buffer( int max_bufs, int echo_size_ ) : Multi_Buffer( stereo )
|
Effects_Buffer::Effects_Buffer( int max_bufs, int echo_size_ ) : Multi_Buffer( stereo )
|
||||||
{
|
{
|
||||||
echo_size = max( max_read * (int) stereo, echo_size_ & ~1 );
|
echo_size = max( max_read * (int) stereo, echo_size_ & ~1 );
|
||||||
|
|
|
@ -1,149 +1,149 @@
|
||||||
// Multi-channel effects buffer with echo and individual panning for each channel
|
// Multi-channel effects buffer with echo and individual panning for each channel
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef EFFECTS_BUFFER_H
|
#ifndef EFFECTS_BUFFER_H
|
||||||
#define EFFECTS_BUFFER_H
|
#define EFFECTS_BUFFER_H
|
||||||
|
|
||||||
#include "Multi_Buffer.h"
|
#include "Multi_Buffer.h"
|
||||||
|
|
||||||
// See Simple_Effects_Buffer (below) for a simpler interface
|
// See Simple_Effects_Buffer (below) for a simpler interface
|
||||||
|
|
||||||
class Effects_Buffer : public Multi_Buffer {
|
class Effects_Buffer : public Multi_Buffer {
|
||||||
public:
|
public:
|
||||||
// To reduce memory usage, fewer buffers can be used (with a best-fit
|
// To reduce memory usage, fewer buffers can be used (with a best-fit
|
||||||
// approach if there are too few), and maximum echo delay can be reduced
|
// approach if there are too few), and maximum echo delay can be reduced
|
||||||
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 );
|
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 );
|
||||||
|
|
||||||
struct pan_vol_t
|
struct pan_vol_t
|
||||||
{
|
{
|
||||||
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal
|
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal
|
||||||
float pan; // -1.0 = left, 0.0 = center, +1.0 = right
|
float pan; // -1.0 = left, 0.0 = center, +1.0 = right
|
||||||
};
|
};
|
||||||
|
|
||||||
// Global configuration
|
// Global configuration
|
||||||
struct config_t
|
struct config_t
|
||||||
{
|
{
|
||||||
bool enabled; // false = disable all effects
|
bool enabled; // false = disable all effects
|
||||||
|
|
||||||
// Current sound is echoed at adjustable left/right delay,
|
// Current sound is echoed at adjustable left/right delay,
|
||||||
// with reduced treble and volume (feedback).
|
// with reduced treble and volume (feedback).
|
||||||
float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent
|
float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent
|
||||||
int delay [2]; // left, right delays (msec)
|
int delay [2]; // left, right delays (msec)
|
||||||
float feedback; // 0.0 = no echo, 0.5 = each echo half previous, 1.0 = cacophony
|
float feedback; // 0.0 = no echo, 0.5 = each echo half previous, 1.0 = cacophony
|
||||||
pan_vol_t side_chans [2]; // left and right side channel volume and pan
|
pan_vol_t side_chans [2]; // left and right side channel volume and pan
|
||||||
};
|
};
|
||||||
config_t& config() { return config_; }
|
config_t& config() { return config_; }
|
||||||
|
|
||||||
// Limits of delay (msec)
|
// Limits of delay (msec)
|
||||||
int min_delay() const;
|
int min_delay() const;
|
||||||
int max_delay() const;
|
int max_delay() const;
|
||||||
|
|
||||||
// Per-channel configuration. Two or more channels with matching parameters are
|
// Per-channel configuration. Two or more channels with matching parameters are
|
||||||
// optimized to internally use the same buffer.
|
// optimized to internally use the same buffer.
|
||||||
struct chan_config_t : pan_vol_t
|
struct chan_config_t : pan_vol_t
|
||||||
{
|
{
|
||||||
// (inherited from pan_vol_t)
|
// (inherited from pan_vol_t)
|
||||||
//float vol; // these only affect center channel
|
//float vol; // these only affect center channel
|
||||||
//float pan;
|
//float pan;
|
||||||
bool surround; // if true, negates left volume to put sound in back
|
bool surround; // if true, negates left volume to put sound in back
|
||||||
bool echo; // false = channel doesn't have any echo
|
bool echo; // false = channel doesn't have any echo
|
||||||
};
|
};
|
||||||
chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; }
|
chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; }
|
||||||
|
|
||||||
// Applies any changes made to config() and chan_config()
|
// Applies any changes made to config() and chan_config()
|
||||||
virtual void apply_config();
|
virtual void apply_config();
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
~Effects_Buffer();
|
~Effects_Buffer();
|
||||||
blargg_err_t set_sample_rate( int samples_per_sec, int msec = blip_default_length );
|
blargg_err_t set_sample_rate( int samples_per_sec, int msec = blip_default_length );
|
||||||
blargg_err_t set_channel_count( int, int const* = NULL );
|
blargg_err_t set_channel_count( int, int const* = NULL );
|
||||||
void clock_rate( int );
|
void clock_rate( int );
|
||||||
void bass_freq( int );
|
void bass_freq( int );
|
||||||
void clear();
|
void clear();
|
||||||
channel_t channel( int );
|
channel_t channel( int );
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
int read_samples( blip_sample_t [], int );
|
int read_samples( blip_sample_t [], int );
|
||||||
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
|
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
|
||||||
enum { stereo = 2 };
|
enum { stereo = 2 };
|
||||||
typedef int fixed_t;
|
typedef int fixed_t;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum { extra_chans = stereo * stereo };
|
enum { extra_chans = stereo * stereo };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
config_t config_;
|
config_t config_;
|
||||||
int clock_rate_;
|
int clock_rate_;
|
||||||
int bass_freq_;
|
int bass_freq_;
|
||||||
|
|
||||||
int echo_size;
|
int echo_size;
|
||||||
|
|
||||||
struct chan_t
|
struct chan_t
|
||||||
{
|
{
|
||||||
fixed_t vol [stereo];
|
fixed_t vol [stereo];
|
||||||
chan_config_t cfg;
|
chan_config_t cfg;
|
||||||
channel_t channel;
|
channel_t channel;
|
||||||
};
|
};
|
||||||
blargg_vector<chan_t> chans;
|
blargg_vector<chan_t> chans;
|
||||||
|
|
||||||
struct buf_t : Tracked_Blip_Buffer
|
struct buf_t : Tracked_Blip_Buffer
|
||||||
{
|
{
|
||||||
// nasty: Blip_Buffer has something called fixed_t
|
// nasty: Blip_Buffer has something called fixed_t
|
||||||
Effects_Buffer::fixed_t vol [stereo];
|
Effects_Buffer::fixed_t vol [stereo];
|
||||||
bool echo;
|
bool echo;
|
||||||
|
|
||||||
void* operator new ( size_t, void* p ) { return p; }
|
void* operator new ( size_t, void* p ) { return p; }
|
||||||
void operator delete ( void* ) { }
|
void operator delete ( void* ) { }
|
||||||
|
|
||||||
~buf_t() { }
|
~buf_t() { }
|
||||||
};
|
};
|
||||||
buf_t* bufs;
|
buf_t* bufs;
|
||||||
int bufs_size;
|
int bufs_size;
|
||||||
int bufs_max; // bufs_size <= bufs_max, to limit memory usage
|
int bufs_max; // bufs_size <= bufs_max, to limit memory usage
|
||||||
Stereo_Mixer mixer;
|
Stereo_Mixer mixer;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int delay [stereo];
|
int delay [stereo];
|
||||||
fixed_t treble;
|
fixed_t treble;
|
||||||
fixed_t feedback;
|
fixed_t feedback;
|
||||||
fixed_t low_pass [stereo];
|
fixed_t low_pass [stereo];
|
||||||
} s;
|
} s;
|
||||||
|
|
||||||
blargg_vector<fixed_t> echo;
|
blargg_vector<fixed_t> echo;
|
||||||
int echo_pos;
|
int echo_pos;
|
||||||
|
|
||||||
bool no_effects;
|
bool no_effects;
|
||||||
bool no_echo;
|
bool no_echo;
|
||||||
|
|
||||||
void assign_buffers();
|
void assign_buffers();
|
||||||
void clear_echo();
|
void clear_echo();
|
||||||
void mix_effects( blip_sample_t out [], int pair_count );
|
void mix_effects( blip_sample_t out [], int pair_count );
|
||||||
blargg_err_t new_bufs( int size );
|
blargg_err_t new_bufs( int size );
|
||||||
void delete_bufs();
|
void delete_bufs();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simpler interface and lower memory usage
|
// Simpler interface and lower memory usage
|
||||||
class Simple_Effects_Buffer : public Effects_Buffer {
|
class Simple_Effects_Buffer : public Effects_Buffer {
|
||||||
public:
|
public:
|
||||||
struct config_t
|
struct config_t
|
||||||
{
|
{
|
||||||
bool enabled; // false = disable all effects
|
bool enabled; // false = disable all effects
|
||||||
|
|
||||||
float echo; // 0.0 = none, 1.0 = lots
|
float echo; // 0.0 = none, 1.0 = lots
|
||||||
float stereo; // 0.0 = channels in center, 1.0 = channels on left/right
|
float stereo; // 0.0 = channels in center, 1.0 = channels on left/right
|
||||||
bool surround; // true = put some channels in back
|
bool surround; // true = put some channels in back
|
||||||
};
|
};
|
||||||
config_t& config() { return config_; }
|
config_t& config() { return config_; }
|
||||||
|
|
||||||
// Applies any changes made to config()
|
// Applies any changes made to config()
|
||||||
void apply_config();
|
void apply_config();
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Simple_Effects_Buffer();
|
Simple_Effects_Buffer();
|
||||||
private:
|
private:
|
||||||
config_t config_;
|
config_t config_;
|
||||||
void chan_config(); // hide
|
void chan_config(); // hide
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,123 +1,123 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Fir_Resampler.h"
|
#include "Fir_Resampler.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
#undef PI
|
#undef PI
|
||||||
#define PI 3.1415926535897932384626433832795029
|
#define PI 3.1415926535897932384626433832795029
|
||||||
|
|
||||||
static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
|
static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
|
||||||
int count, short* out )
|
int count, short* out )
|
||||||
{
|
{
|
||||||
double const maxh = 256;
|
double const maxh = 256;
|
||||||
double const step = PI / maxh * spacing;
|
double const step = PI / maxh * spacing;
|
||||||
double const to_w = maxh * 2 / width;
|
double const to_w = maxh * 2 / width;
|
||||||
double const pow_a_n = pow( rolloff, maxh );
|
double const pow_a_n = pow( rolloff, maxh );
|
||||||
scale /= maxh * 2;
|
scale /= maxh * 2;
|
||||||
|
|
||||||
double angle = (count / 2 - 1 + offset) * -step;
|
double angle = (count / 2 - 1 + offset) * -step;
|
||||||
while ( count-- )
|
while ( count-- )
|
||||||
{
|
{
|
||||||
*out++ = 0;
|
*out++ = 0;
|
||||||
double w = angle * to_w;
|
double w = angle * to_w;
|
||||||
if ( fabs( w ) < PI )
|
if ( fabs( w ) < PI )
|
||||||
{
|
{
|
||||||
double rolloff_cos_a = rolloff * cos( angle );
|
double rolloff_cos_a = rolloff * cos( angle );
|
||||||
double num = 1 - rolloff_cos_a -
|
double num = 1 - rolloff_cos_a -
|
||||||
pow_a_n * cos( maxh * angle ) +
|
pow_a_n * cos( maxh * angle ) +
|
||||||
pow_a_n * rolloff * cos( (maxh - 1) * angle );
|
pow_a_n * rolloff * cos( (maxh - 1) * angle );
|
||||||
double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
|
double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
|
||||||
double sinc = scale * num / den - scale;
|
double sinc = scale * num / den - scale;
|
||||||
|
|
||||||
out [-1] = (short) (cos( w ) * sinc + sinc);
|
out [-1] = (short) (cos( w ) * sinc + sinc);
|
||||||
}
|
}
|
||||||
angle += step;
|
angle += step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) :
|
Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) :
|
||||||
width_( width ),
|
width_( width ),
|
||||||
impulses( impulses_ )
|
impulses( impulses_ )
|
||||||
{
|
{
|
||||||
imp = NULL;
|
imp = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fir_Resampler_::clear_()
|
void Fir_Resampler_::clear_()
|
||||||
{
|
{
|
||||||
imp = impulses;
|
imp = impulses;
|
||||||
Resampler::clear_();
|
Resampler::clear_();
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Fir_Resampler_::set_rate_( double new_factor )
|
blargg_err_t Fir_Resampler_::set_rate_( double new_factor )
|
||||||
{
|
{
|
||||||
double const rolloff = 0.999;
|
double const rolloff = 0.999;
|
||||||
double const gain = 1.0;
|
double const gain = 1.0;
|
||||||
|
|
||||||
// determine number of sub-phases that yield lowest error
|
// determine number of sub-phases that yield lowest error
|
||||||
double ratio_ = 0.0;
|
double ratio_ = 0.0;
|
||||||
int res = -1;
|
int res = -1;
|
||||||
{
|
{
|
||||||
double least_error = 2;
|
double least_error = 2;
|
||||||
double pos = 0;
|
double pos = 0;
|
||||||
for ( int r = 1; r <= max_res; r++ )
|
for ( int r = 1; r <= max_res; r++ )
|
||||||
{
|
{
|
||||||
pos += new_factor;
|
pos += new_factor;
|
||||||
double nearest = floor( pos + 0.5 );
|
double nearest = floor( pos + 0.5 );
|
||||||
double error = fabs( pos - nearest );
|
double error = fabs( pos - nearest );
|
||||||
if ( error < least_error )
|
if ( error < least_error )
|
||||||
{
|
{
|
||||||
res = r;
|
res = r;
|
||||||
ratio_ = nearest / res;
|
ratio_ = nearest / res;
|
||||||
least_error = error;
|
least_error = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RETURN_ERR( Resampler::set_rate_( ratio_ ) );
|
RETURN_ERR( Resampler::set_rate_( ratio_ ) );
|
||||||
|
|
||||||
// how much of input is used for each output sample
|
// how much of input is used for each output sample
|
||||||
int const step = stereo * (int) floor( ratio_ );
|
int const step = stereo * (int) floor( ratio_ );
|
||||||
double fraction = fmod( ratio_, 1.0 );
|
double fraction = fmod( ratio_, 1.0 );
|
||||||
|
|
||||||
double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
|
double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
|
||||||
double pos = 0.0;
|
double pos = 0.0;
|
||||||
//int input_per_cycle = 0;
|
//int input_per_cycle = 0;
|
||||||
sample_t* out = impulses;
|
sample_t* out = impulses;
|
||||||
for ( int n = res; --n >= 0; )
|
for ( int n = res; --n >= 0; )
|
||||||
{
|
{
|
||||||
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
|
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
|
||||||
double (0x7FFF * gain * filter), (int) width_, out );
|
double (0x7FFF * gain * filter), (int) width_, out );
|
||||||
out += width_;
|
out += width_;
|
||||||
|
|
||||||
int cur_step = step;
|
int cur_step = step;
|
||||||
pos += fraction;
|
pos += fraction;
|
||||||
if ( pos >= 0.9999999 )
|
if ( pos >= 0.9999999 )
|
||||||
{
|
{
|
||||||
pos -= 1.0;
|
pos -= 1.0;
|
||||||
cur_step += stereo;
|
cur_step += stereo;
|
||||||
}
|
}
|
||||||
|
|
||||||
*out++ = (cur_step - width_ * 2 + 4) * sizeof (sample_t);
|
*out++ = (cur_step - width_ * 2 + 4) * sizeof (sample_t);
|
||||||
*out++ = 4 * sizeof (sample_t);
|
*out++ = 4 * sizeof (sample_t);
|
||||||
//input_per_cycle += cur_step;
|
//input_per_cycle += cur_step;
|
||||||
}
|
}
|
||||||
// last offset moves back to beginning of impulses
|
// last offset moves back to beginning of impulses
|
||||||
out [-1] -= (char*) out - (char*) impulses;
|
out [-1] -= (char*) out - (char*) impulses;
|
||||||
|
|
||||||
imp = impulses;
|
imp = impulses;
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +1,101 @@
|
||||||
// Finite impulse response (FIR) resampler with adjustable FIR size
|
// Finite impulse response (FIR) resampler with adjustable FIR size
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef FIR_RESAMPLER_H
|
#ifndef FIR_RESAMPLER_H
|
||||||
#define FIR_RESAMPLER_H
|
#define FIR_RESAMPLER_H
|
||||||
|
|
||||||
#include "Resampler.h"
|
#include "Resampler.h"
|
||||||
|
|
||||||
template<int width>
|
template<int width>
|
||||||
class Fir_Resampler;
|
class Fir_Resampler;
|
||||||
|
|
||||||
// Use one of these typedefs
|
// Use one of these typedefs
|
||||||
typedef Fir_Resampler< 8> Fir_Resampler_Fast;
|
typedef Fir_Resampler< 8> Fir_Resampler_Fast;
|
||||||
typedef Fir_Resampler<16> Fir_Resampler_Norm;
|
typedef Fir_Resampler<16> Fir_Resampler_Norm;
|
||||||
typedef Fir_Resampler<24> Fir_Resampler_Good;
|
typedef Fir_Resampler<24> Fir_Resampler_Good;
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
class Fir_Resampler_ : public Resampler {
|
class Fir_Resampler_ : public Resampler {
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t set_rate_( double );
|
virtual blargg_err_t set_rate_( double );
|
||||||
virtual void clear_();
|
virtual void clear_();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum { stereo = 2 };
|
enum { stereo = 2 };
|
||||||
enum { max_res = 32 }; // TODO: eliminate and keep impulses on freestore?
|
enum { max_res = 32 }; // TODO: eliminate and keep impulses on freestore?
|
||||||
sample_t const* imp;
|
sample_t const* imp;
|
||||||
int const width_;
|
int const width_;
|
||||||
sample_t* impulses;
|
sample_t* impulses;
|
||||||
|
|
||||||
Fir_Resampler_( int width, sample_t [] );
|
Fir_Resampler_( int width, sample_t [] );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Width is number of points in FIR. More points give better quality and
|
// Width is number of points in FIR. More points give better quality and
|
||||||
// rolloff effectiveness, and take longer to calculate.
|
// rolloff effectiveness, and take longer to calculate.
|
||||||
template<int width>
|
template<int width>
|
||||||
class Fir_Resampler : public Fir_Resampler_ {
|
class Fir_Resampler : public Fir_Resampler_ {
|
||||||
enum { min_width = (width < 4 ? 4 : width) };
|
enum { min_width = (width < 4 ? 4 : width) };
|
||||||
enum { adj_width = min_width / 4 * 4 + 2 };
|
enum { adj_width = min_width / 4 * 4 + 2 };
|
||||||
enum { write_offset = adj_width * stereo };
|
enum { write_offset = adj_width * stereo };
|
||||||
short impulses [max_res * (adj_width + 2)];
|
short impulses [max_res * (adj_width + 2)];
|
||||||
public:
|
public:
|
||||||
Fir_Resampler() : Fir_Resampler_( adj_width, impulses ) { }
|
Fir_Resampler() : Fir_Resampler_( adj_width, impulses ) { }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
|
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
|
||||||
};
|
};
|
||||||
|
|
||||||
template<int width>
|
template<int width>
|
||||||
Resampler::sample_t const* Fir_Resampler<width>::resample_( sample_t** out_,
|
Resampler::sample_t const* Fir_Resampler<width>::resample_( sample_t** out_,
|
||||||
sample_t const* out_end, sample_t const in [], int in_size )
|
sample_t const* out_end, sample_t const in [], int in_size )
|
||||||
{
|
{
|
||||||
in_size -= write_offset;
|
in_size -= write_offset;
|
||||||
if ( in_size > 0 )
|
if ( in_size > 0 )
|
||||||
{
|
{
|
||||||
sample_t* BLARGG_RESTRICT out = *out_;
|
sample_t* BLARGG_RESTRICT out = *out_;
|
||||||
sample_t const* const in_end = in + in_size;
|
sample_t const* const in_end = in + in_size;
|
||||||
sample_t const* imp = this->imp;
|
sample_t const* imp = this->imp;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// accumulate in extended precision
|
// accumulate in extended precision
|
||||||
int pt = imp [0];
|
int pt = imp [0];
|
||||||
int l = pt * in [0];
|
int l = pt * in [0];
|
||||||
int r = pt * in [1];
|
int r = pt * in [1];
|
||||||
if ( out >= out_end )
|
if ( out >= out_end )
|
||||||
break;
|
break;
|
||||||
for ( int n = (adj_width - 2) / 2; n; --n )
|
for ( int n = (adj_width - 2) / 2; n; --n )
|
||||||
{
|
{
|
||||||
pt = imp [1];
|
pt = imp [1];
|
||||||
l += pt * in [2];
|
l += pt * in [2];
|
||||||
r += pt * in [3];
|
r += pt * in [3];
|
||||||
|
|
||||||
// pre-increment more efficient on some RISC processors
|
// pre-increment more efficient on some RISC processors
|
||||||
imp += 2;
|
imp += 2;
|
||||||
pt = imp [0];
|
pt = imp [0];
|
||||||
r += pt * in [5];
|
r += pt * in [5];
|
||||||
in += 4;
|
in += 4;
|
||||||
l += pt * in [0];
|
l += pt * in [0];
|
||||||
}
|
}
|
||||||
pt = imp [1];
|
pt = imp [1];
|
||||||
l += pt * in [2];
|
l += pt * in [2];
|
||||||
r += pt * in [3];
|
r += pt * in [3];
|
||||||
|
|
||||||
// these two "samples" after the end of the impulse give the
|
// these two "samples" after the end of the impulse give the
|
||||||
// proper offsets to the next input sample and next impulse
|
// proper offsets to the next input sample and next impulse
|
||||||
in = (sample_t const*) ((char const*) in + imp [2]); // some negative value
|
in = (sample_t const*) ((char const*) in + imp [2]); // some negative value
|
||||||
imp = (sample_t const*) ((char const*) imp + imp [3]); // small positive or large negative
|
imp = (sample_t const*) ((char const*) imp + imp [3]); // small positive or large negative
|
||||||
|
|
||||||
out [0] = sample_t (l >> 15);
|
out [0] = sample_t (l >> 15);
|
||||||
out [1] = sample_t (r >> 15);
|
out [1] = sample_t (r >> 15);
|
||||||
out += 2;
|
out += 2;
|
||||||
}
|
}
|
||||||
while ( in < in_end );
|
while ( in < in_end );
|
||||||
|
|
||||||
this->imp = imp;
|
this->imp = imp;
|
||||||
*out_ = out;
|
*out_ = out;
|
||||||
}
|
}
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,407 +1,407 @@
|
||||||
// Gb_Snd_Emu $vers. http://www.slack.net/~ant/
|
// Gb_Snd_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Gb_Apu.h"
|
#include "Gb_Apu.h"
|
||||||
|
|
||||||
//#include "gb_apu_logger.h"
|
//#include "gb_apu_logger.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const vol_reg = 0xFF24;
|
int const vol_reg = 0xFF24;
|
||||||
int const stereo_reg = 0xFF25;
|
int const stereo_reg = 0xFF25;
|
||||||
int const status_reg = 0xFF26;
|
int const status_reg = 0xFF26;
|
||||||
int const wave_ram = 0xFF30;
|
int const wave_ram = 0xFF30;
|
||||||
|
|
||||||
int const power_mask = 0x80;
|
int const power_mask = 0x80;
|
||||||
|
|
||||||
void Gb_Apu::treble_eq( blip_eq_t const& eq )
|
void Gb_Apu::treble_eq( blip_eq_t const& eq )
|
||||||
{
|
{
|
||||||
norm_synth.treble_eq( eq );
|
norm_synth.treble_eq( eq );
|
||||||
fast_synth.treble_eq( eq );
|
fast_synth.treble_eq( eq );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Gb_Apu::calc_output( int osc ) const
|
inline int Gb_Apu::calc_output( int osc ) const
|
||||||
{
|
{
|
||||||
int bits = regs [stereo_reg - io_addr] >> osc;
|
int bits = regs [stereo_reg - io_addr] >> osc;
|
||||||
return (bits >> 3 & 2) | (bits & 1);
|
return (bits >> 3 & 2) | (bits & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
void Gb_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
||||||
{
|
{
|
||||||
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
|
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
|
||||||
require( !center || (center && !left && !right) || (center && left && right) );
|
require( !center || (center && !left && !right) || (center && left && right) );
|
||||||
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
|
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
|
||||||
|
|
||||||
if ( !center || !left || !right )
|
if ( !center || !left || !right )
|
||||||
{
|
{
|
||||||
left = center;
|
left = center;
|
||||||
right = center;
|
right = center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gb_Osc& o = *oscs [i];
|
Gb_Osc& o = *oscs [i];
|
||||||
o.outputs [1] = right;
|
o.outputs [1] = right;
|
||||||
o.outputs [2] = left;
|
o.outputs [2] = left;
|
||||||
o.outputs [3] = center;
|
o.outputs [3] = center;
|
||||||
o.output = o.outputs [calc_output( i )];
|
o.output = o.outputs [calc_output( i )];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::synth_volume( int iv )
|
void Gb_Apu::synth_volume( int iv )
|
||||||
{
|
{
|
||||||
double v = volume_ * 0.60 / osc_count / 15 /*steps*/ / 8 /*master vol range*/ * iv;
|
double v = volume_ * 0.60 / osc_count / 15 /*steps*/ / 8 /*master vol range*/ * iv;
|
||||||
norm_synth.volume( v );
|
norm_synth.volume( v );
|
||||||
fast_synth.volume( v );
|
fast_synth.volume( v );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::apply_volume()
|
void Gb_Apu::apply_volume()
|
||||||
{
|
{
|
||||||
// TODO: Doesn't handle differing left and right volumes (panning).
|
// TODO: Doesn't handle differing left and right volumes (panning).
|
||||||
// Not worth the complexity.
|
// Not worth the complexity.
|
||||||
int data = regs [vol_reg - io_addr];
|
int data = regs [vol_reg - io_addr];
|
||||||
int left = data >> 4 & 7;
|
int left = data >> 4 & 7;
|
||||||
int right = data & 7;
|
int right = data & 7;
|
||||||
//if ( data & 0x88 ) dprintf( "Vin: %02X\n", data & 0x88 );
|
//if ( data & 0x88 ) dprintf( "Vin: %02X\n", data & 0x88 );
|
||||||
//if ( left != right ) dprintf( "l: %d r: %d\n", left, right );
|
//if ( left != right ) dprintf( "l: %d r: %d\n", left, right );
|
||||||
synth_volume( max( left, right ) + 1 );
|
synth_volume( max( left, right ) + 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::volume( double v )
|
void Gb_Apu::volume( double v )
|
||||||
{
|
{
|
||||||
if ( volume_ != v )
|
if ( volume_ != v )
|
||||||
{
|
{
|
||||||
volume_ = v;
|
volume_ = v;
|
||||||
apply_volume();
|
apply_volume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::reset_regs()
|
void Gb_Apu::reset_regs()
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < 0x20; i++ )
|
for ( int i = 0; i < 0x20; i++ )
|
||||||
regs [i] = 0;
|
regs [i] = 0;
|
||||||
|
|
||||||
square1.reset();
|
square1.reset();
|
||||||
square2.reset();
|
square2.reset();
|
||||||
wave .reset();
|
wave .reset();
|
||||||
noise .reset();
|
noise .reset();
|
||||||
|
|
||||||
apply_volume();
|
apply_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::reset_lengths()
|
void Gb_Apu::reset_lengths()
|
||||||
{
|
{
|
||||||
square1.length_ctr = 64;
|
square1.length_ctr = 64;
|
||||||
square2.length_ctr = 64;
|
square2.length_ctr = 64;
|
||||||
wave .length_ctr = 256;
|
wave .length_ctr = 256;
|
||||||
noise .length_ctr = 64;
|
noise .length_ctr = 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::reduce_clicks( bool reduce )
|
void Gb_Apu::reduce_clicks( bool reduce )
|
||||||
{
|
{
|
||||||
reduce_clicks_ = reduce;
|
reduce_clicks_ = reduce;
|
||||||
|
|
||||||
// Click reduction makes DAC off generate same output as volume 0
|
// Click reduction makes DAC off generate same output as volume 0
|
||||||
int dac_off_amp = 0;
|
int dac_off_amp = 0;
|
||||||
if ( reduce && wave.mode != mode_agb ) // AGB already eliminates clicks
|
if ( reduce && wave.mode != mode_agb ) // AGB already eliminates clicks
|
||||||
dac_off_amp = -Gb_Osc::dac_bias;
|
dac_off_amp = -Gb_Osc::dac_bias;
|
||||||
|
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
oscs [i]->dac_off_amp = dac_off_amp;
|
oscs [i]->dac_off_amp = dac_off_amp;
|
||||||
|
|
||||||
// AGB always eliminates clicks on wave channel using same method
|
// AGB always eliminates clicks on wave channel using same method
|
||||||
if ( wave.mode == mode_agb )
|
if ( wave.mode == mode_agb )
|
||||||
wave.dac_off_amp = -Gb_Osc::dac_bias;
|
wave.dac_off_amp = -Gb_Osc::dac_bias;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::reset( mode_t mode, bool agb_wave )
|
void Gb_Apu::reset( mode_t mode, bool agb_wave )
|
||||||
{
|
{
|
||||||
// Hardware mode
|
// Hardware mode
|
||||||
if ( agb_wave )
|
if ( agb_wave )
|
||||||
mode = mode_agb; // using AGB wave features implies AGB hardware
|
mode = mode_agb; // using AGB wave features implies AGB hardware
|
||||||
wave.agb_mask = agb_wave ? 0xFF : 0;
|
wave.agb_mask = agb_wave ? 0xFF : 0;
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
oscs [i]->mode = mode;
|
oscs [i]->mode = mode;
|
||||||
reduce_clicks( reduce_clicks_ );
|
reduce_clicks( reduce_clicks_ );
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
frame_time = 0;
|
frame_time = 0;
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
frame_phase = 0;
|
frame_phase = 0;
|
||||||
|
|
||||||
reset_regs();
|
reset_regs();
|
||||||
reset_lengths();
|
reset_lengths();
|
||||||
|
|
||||||
// Load initial wave RAM
|
// Load initial wave RAM
|
||||||
static byte const initial_wave [2] [16] = {
|
static byte const initial_wave [2] [16] = {
|
||||||
{0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C,0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA},
|
{0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C,0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA},
|
||||||
{0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF},
|
{0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF},
|
||||||
};
|
};
|
||||||
for ( int b = 2; --b >= 0; )
|
for ( int b = 2; --b >= 0; )
|
||||||
{
|
{
|
||||||
// Init both banks (does nothing if not in AGB mode)
|
// Init both banks (does nothing if not in AGB mode)
|
||||||
// TODO: verify that this works
|
// TODO: verify that this works
|
||||||
write_register( 0, 0xFF1A, b * 0x40 );
|
write_register( 0, 0xFF1A, b * 0x40 );
|
||||||
for ( unsigned i = 0; i < sizeof initial_wave [0]; i++ )
|
for ( unsigned i = 0; i < sizeof initial_wave [0]; i++ )
|
||||||
write_register( 0, i + wave_ram, initial_wave [(mode != mode_dmg)] [i] );
|
write_register( 0, i + wave_ram, initial_wave [(mode != mode_dmg)] [i] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::set_tempo( double t )
|
void Gb_Apu::set_tempo( double t )
|
||||||
{
|
{
|
||||||
frame_period = 4194304 / 512; // 512 Hz
|
frame_period = 4194304 / 512; // 512 Hz
|
||||||
if ( t != 1.0 )
|
if ( t != 1.0 )
|
||||||
frame_period = t ? blip_time_t (frame_period / t) : blip_time_t(0);
|
frame_period = t ? blip_time_t (frame_period / t) : blip_time_t(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gb_Apu::Gb_Apu()
|
Gb_Apu::Gb_Apu()
|
||||||
{
|
{
|
||||||
wave.wave_ram = ®s [wave_ram - io_addr];
|
wave.wave_ram = ®s [wave_ram - io_addr];
|
||||||
|
|
||||||
oscs [0] = &square1;
|
oscs [0] = &square1;
|
||||||
oscs [1] = &square2;
|
oscs [1] = &square2;
|
||||||
oscs [2] = &wave;
|
oscs [2] = &wave;
|
||||||
oscs [3] = &noise;
|
oscs [3] = &noise;
|
||||||
|
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
{
|
{
|
||||||
Gb_Osc& o = *oscs [i];
|
Gb_Osc& o = *oscs [i];
|
||||||
o.regs = ®s [i * 5];
|
o.regs = ®s [i * 5];
|
||||||
o.output = NULL;
|
o.output = NULL;
|
||||||
o.outputs [0] = NULL;
|
o.outputs [0] = NULL;
|
||||||
o.outputs [1] = NULL;
|
o.outputs [1] = NULL;
|
||||||
o.outputs [2] = NULL;
|
o.outputs [2] = NULL;
|
||||||
o.outputs [3] = NULL;
|
o.outputs [3] = NULL;
|
||||||
o.norm_synth = &norm_synth;
|
o.norm_synth = &norm_synth;
|
||||||
o.fast_synth = &fast_synth;
|
o.fast_synth = &fast_synth;
|
||||||
}
|
}
|
||||||
|
|
||||||
reduce_clicks_ = false;
|
reduce_clicks_ = false;
|
||||||
set_tempo( 1.0 );
|
set_tempo( 1.0 );
|
||||||
volume_ = 1.0;
|
volume_ = 1.0;
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::run_until_( blip_time_t end_time )
|
void Gb_Apu::run_until_( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
if ( !frame_period )
|
if ( !frame_period )
|
||||||
frame_time += end_time - last_time;
|
frame_time += end_time - last_time;
|
||||||
|
|
||||||
while ( true )
|
while ( true )
|
||||||
{
|
{
|
||||||
// run oscillators
|
// run oscillators
|
||||||
blip_time_t time = end_time;
|
blip_time_t time = end_time;
|
||||||
if ( time > frame_time )
|
if ( time > frame_time )
|
||||||
time = frame_time;
|
time = frame_time;
|
||||||
|
|
||||||
square1.run( last_time, time );
|
square1.run( last_time, time );
|
||||||
square2.run( last_time, time );
|
square2.run( last_time, time );
|
||||||
wave .run( last_time, time );
|
wave .run( last_time, time );
|
||||||
noise .run( last_time, time );
|
noise .run( last_time, time );
|
||||||
last_time = time;
|
last_time = time;
|
||||||
|
|
||||||
if ( time == end_time )
|
if ( time == end_time )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// run frame sequencer
|
// run frame sequencer
|
||||||
assert( frame_period );
|
assert( frame_period );
|
||||||
frame_time += frame_period * Gb_Osc::clk_mul;
|
frame_time += frame_period * Gb_Osc::clk_mul;
|
||||||
switch ( frame_phase++ )
|
switch ( frame_phase++ )
|
||||||
{
|
{
|
||||||
case 2:
|
case 2:
|
||||||
case 6:
|
case 6:
|
||||||
// 128 Hz
|
// 128 Hz
|
||||||
square1.clock_sweep();
|
square1.clock_sweep();
|
||||||
case 0:
|
case 0:
|
||||||
case 4:
|
case 4:
|
||||||
// 256 Hz
|
// 256 Hz
|
||||||
square1.clock_length();
|
square1.clock_length();
|
||||||
square2.clock_length();
|
square2.clock_length();
|
||||||
wave .clock_length();
|
wave .clock_length();
|
||||||
noise .clock_length();
|
noise .clock_length();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 7:
|
case 7:
|
||||||
// 64 Hz
|
// 64 Hz
|
||||||
frame_phase = 0;
|
frame_phase = 0;
|
||||||
square1.clock_envelope();
|
square1.clock_envelope();
|
||||||
square2.clock_envelope();
|
square2.clock_envelope();
|
||||||
noise .clock_envelope();
|
noise .clock_envelope();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Gb_Apu::run_until( blip_time_t time )
|
inline void Gb_Apu::run_until( blip_time_t time )
|
||||||
{
|
{
|
||||||
require( time >= last_time ); // end_time must not be before previous time
|
require( time >= last_time ); // end_time must not be before previous time
|
||||||
if ( time > last_time )
|
if ( time > last_time )
|
||||||
run_until_( time );
|
run_until_( time );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::end_frame( blip_time_t end_time )
|
void Gb_Apu::end_frame( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
#ifdef LOG_FRAME
|
#ifdef LOG_FRAME
|
||||||
LOG_FRAME( end_time );
|
LOG_FRAME( end_time );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ( end_time > last_time )
|
if ( end_time > last_time )
|
||||||
run_until( end_time );
|
run_until( end_time );
|
||||||
|
|
||||||
frame_time -= end_time;
|
frame_time -= end_time;
|
||||||
assert( frame_time >= 0 );
|
assert( frame_time >= 0 );
|
||||||
|
|
||||||
last_time -= end_time;
|
last_time -= end_time;
|
||||||
assert( last_time >= 0 );
|
assert( last_time >= 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::silence_osc( Gb_Osc& o )
|
void Gb_Apu::silence_osc( Gb_Osc& o )
|
||||||
{
|
{
|
||||||
int delta = -o.last_amp;
|
int delta = -o.last_amp;
|
||||||
if ( reduce_clicks_ )
|
if ( reduce_clicks_ )
|
||||||
delta += o.dac_off_amp;
|
delta += o.dac_off_amp;
|
||||||
|
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
o.last_amp = o.dac_off_amp;
|
o.last_amp = o.dac_off_amp;
|
||||||
if ( o.output )
|
if ( o.output )
|
||||||
{
|
{
|
||||||
o.output->set_modified();
|
o.output->set_modified();
|
||||||
fast_synth.offset( last_time, delta, o.output );
|
fast_synth.offset( last_time, delta, o.output );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::apply_stereo()
|
void Gb_Apu::apply_stereo()
|
||||||
{
|
{
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
{
|
{
|
||||||
Gb_Osc& o = *oscs [i];
|
Gb_Osc& o = *oscs [i];
|
||||||
Blip_Buffer* out = o.outputs [calc_output( i )];
|
Blip_Buffer* out = o.outputs [calc_output( i )];
|
||||||
if ( o.output != out )
|
if ( o.output != out )
|
||||||
{
|
{
|
||||||
silence_osc( o );
|
silence_osc( o );
|
||||||
o.output = out;
|
o.output = out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Apu::write_register( blip_time_t time, int addr, int data )
|
void Gb_Apu::write_register( blip_time_t time, int addr, int data )
|
||||||
{
|
{
|
||||||
require( (unsigned) data < 0x100 );
|
require( (unsigned) data < 0x100 );
|
||||||
|
|
||||||
int reg = addr - io_addr;
|
int reg = addr - io_addr;
|
||||||
if ( (unsigned) reg >= io_size )
|
if ( (unsigned) reg >= io_size )
|
||||||
{
|
{
|
||||||
require( false );
|
require( false );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LOG_WRITE
|
#ifdef LOG_WRITE
|
||||||
LOG_WRITE( time, addr, data );
|
LOG_WRITE( time, addr, data );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ( addr < status_reg && !(regs [status_reg - io_addr] & power_mask) )
|
if ( addr < status_reg && !(regs [status_reg - io_addr] & power_mask) )
|
||||||
{
|
{
|
||||||
// Power is off
|
// Power is off
|
||||||
|
|
||||||
// length counters can only be written in DMG mode
|
// length counters can only be written in DMG mode
|
||||||
if ( wave.mode != mode_dmg || (reg != 1 && reg != 5+1 && reg != 10+1 && reg != 15+1) )
|
if ( wave.mode != mode_dmg || (reg != 1 && reg != 5+1 && reg != 10+1 && reg != 15+1) )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( reg < 10 )
|
if ( reg < 10 )
|
||||||
data &= 0x3F; // clear square duty
|
data &= 0x3F; // clear square duty
|
||||||
}
|
}
|
||||||
|
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
if ( addr >= wave_ram )
|
if ( addr >= wave_ram )
|
||||||
{
|
{
|
||||||
wave.write( addr, data );
|
wave.write( addr, data );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int old_data = regs [reg];
|
int old_data = regs [reg];
|
||||||
regs [reg] = data;
|
regs [reg] = data;
|
||||||
|
|
||||||
if ( addr < vol_reg )
|
if ( addr < vol_reg )
|
||||||
{
|
{
|
||||||
// Oscillator
|
// Oscillator
|
||||||
write_osc( reg, old_data, data );
|
write_osc( reg, old_data, data );
|
||||||
}
|
}
|
||||||
else if ( addr == vol_reg && data != old_data )
|
else if ( addr == vol_reg && data != old_data )
|
||||||
{
|
{
|
||||||
// Master volume
|
// Master volume
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
silence_osc( *oscs [i] );
|
silence_osc( *oscs [i] );
|
||||||
|
|
||||||
apply_volume();
|
apply_volume();
|
||||||
}
|
}
|
||||||
else if ( addr == stereo_reg )
|
else if ( addr == stereo_reg )
|
||||||
{
|
{
|
||||||
// Stereo panning
|
// Stereo panning
|
||||||
apply_stereo();
|
apply_stereo();
|
||||||
}
|
}
|
||||||
else if ( addr == status_reg && (data ^ old_data) & power_mask )
|
else if ( addr == status_reg && (data ^ old_data) & power_mask )
|
||||||
{
|
{
|
||||||
// Power control
|
// Power control
|
||||||
frame_phase = 0;
|
frame_phase = 0;
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
silence_osc( *oscs [i] );
|
silence_osc( *oscs [i] );
|
||||||
|
|
||||||
reset_regs();
|
reset_regs();
|
||||||
if ( wave.mode != mode_dmg )
|
if ( wave.mode != mode_dmg )
|
||||||
reset_lengths();
|
reset_lengths();
|
||||||
|
|
||||||
regs [status_reg - io_addr] = data;
|
regs [status_reg - io_addr] = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Gb_Apu::read_register( blip_time_t time, int addr )
|
int Gb_Apu::read_register( blip_time_t time, int addr )
|
||||||
{
|
{
|
||||||
if ( addr >= status_reg )
|
if ( addr >= status_reg )
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
int reg = addr - io_addr;
|
int reg = addr - io_addr;
|
||||||
if ( (unsigned) reg >= io_size )
|
if ( (unsigned) reg >= io_size )
|
||||||
{
|
{
|
||||||
require( false );
|
require( false );
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( addr >= wave_ram )
|
if ( addr >= wave_ram )
|
||||||
return wave.read( addr );
|
return wave.read( addr );
|
||||||
|
|
||||||
// Value read back has some bits always set
|
// Value read back has some bits always set
|
||||||
static byte const masks [] = {
|
static byte const masks [] = {
|
||||||
0x80,0x3F,0x00,0xFF,0xBF,
|
0x80,0x3F,0x00,0xFF,0xBF,
|
||||||
0xFF,0x3F,0x00,0xFF,0xBF,
|
0xFF,0x3F,0x00,0xFF,0xBF,
|
||||||
0x7F,0xFF,0x9F,0xFF,0xBF,
|
0x7F,0xFF,0x9F,0xFF,0xBF,
|
||||||
0xFF,0xFF,0x00,0x00,0xBF,
|
0xFF,0xFF,0x00,0x00,0xBF,
|
||||||
0x00,0x00,0x70,
|
0x00,0x00,0x70,
|
||||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
|
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
|
||||||
};
|
};
|
||||||
int mask = masks [reg];
|
int mask = masks [reg];
|
||||||
if ( wave.agb_mask && (reg == 10 || reg == 12) )
|
if ( wave.agb_mask && (reg == 10 || reg == 12) )
|
||||||
mask = 0x1F; // extra implemented bits in wave regs on AGB
|
mask = 0x1F; // extra implemented bits in wave regs on AGB
|
||||||
int data = regs [reg] | mask;
|
int data = regs [reg] | mask;
|
||||||
|
|
||||||
// Status register
|
// Status register
|
||||||
if ( addr == status_reg )
|
if ( addr == status_reg )
|
||||||
{
|
{
|
||||||
data &= 0xF0;
|
data &= 0xF0;
|
||||||
data |= (int) square1.enabled << 0;
|
data |= (int) square1.enabled << 0;
|
||||||
data |= (int) square2.enabled << 1;
|
data |= (int) square2.enabled << 1;
|
||||||
data |= (int) wave .enabled << 2;
|
data |= (int) wave .enabled << 2;
|
||||||
data |= (int) noise .enabled << 3;
|
data |= (int) noise .enabled << 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,193 +1,193 @@
|
||||||
// Nintendo Game Boy sound hardware emulator with save state support
|
// Nintendo Game Boy sound hardware emulator with save state support
|
||||||
|
|
||||||
// Gb_Snd_Emu $vers
|
// Gb_Snd_Emu $vers
|
||||||
#ifndef GB_APU_H
|
#ifndef GB_APU_H
|
||||||
#define GB_APU_H
|
#define GB_APU_H
|
||||||
|
|
||||||
#include "Gb_Oscs.h"
|
#include "Gb_Oscs.h"
|
||||||
|
|
||||||
struct gb_apu_state_t;
|
struct gb_apu_state_t;
|
||||||
|
|
||||||
class Gb_Apu {
|
class Gb_Apu {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
|
|
||||||
// Sets buffer(s) to generate sound into, or NULL to mute. If only center is not NULL,
|
// Sets buffer(s) to generate sound into, or NULL to mute. If only center is not NULL,
|
||||||
// output is mono.
|
// output is mono.
|
||||||
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||||
|
|
||||||
// Emulates to time t, then writes data to addr
|
// Emulates to time t, then writes data to addr
|
||||||
void write_register( blip_time_t t, int addr, int data );
|
void write_register( blip_time_t t, int addr, int data );
|
||||||
|
|
||||||
// Emulates to time t, then subtracts t from the current time.
|
// Emulates to time t, then subtracts t from the current time.
|
||||||
// OK if previous write call had time slightly after t.
|
// OK if previous write call had time slightly after t.
|
||||||
void end_frame( blip_time_t t );
|
void end_frame( blip_time_t t );
|
||||||
|
|
||||||
// More features
|
// More features
|
||||||
|
|
||||||
// Clock rate sound hardware runs at
|
// Clock rate sound hardware runs at
|
||||||
enum { clock_rate = 4194304 * GB_APU_OVERCLOCK };
|
enum { clock_rate = 4194304 * GB_APU_OVERCLOCK };
|
||||||
|
|
||||||
// Registers are at io_addr to io_addr+io_size-1
|
// Registers are at io_addr to io_addr+io_size-1
|
||||||
enum { io_addr = 0xFF10 };
|
enum { io_addr = 0xFF10 };
|
||||||
enum { io_size = 0x30 };
|
enum { io_size = 0x30 };
|
||||||
|
|
||||||
// Emulates to time t, then reads from addr
|
// Emulates to time t, then reads from addr
|
||||||
int read_register( blip_time_t t, int addr );
|
int read_register( blip_time_t t, int addr );
|
||||||
|
|
||||||
// Resets hardware to state after power, BEFORE boot ROM runs. Mode selects
|
// Resets hardware to state after power, BEFORE boot ROM runs. Mode selects
|
||||||
// sound hardware. If agb_wave is true, enables AGB's extra wave features.
|
// sound hardware. If agb_wave is true, enables AGB's extra wave features.
|
||||||
enum mode_t {
|
enum mode_t {
|
||||||
mode_dmg, // Game Boy monochrome
|
mode_dmg, // Game Boy monochrome
|
||||||
mode_cgb, // Game Boy Color
|
mode_cgb, // Game Boy Color
|
||||||
mode_agb // Game Boy Advance
|
mode_agb // Game Boy Advance
|
||||||
};
|
};
|
||||||
void reset( mode_t mode = mode_cgb, bool agb_wave = false );
|
void reset( mode_t mode = mode_cgb, bool agb_wave = false );
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
// 0: Square 1, 1: Square 2, 2: Wave, 3: Noise
|
// 0: Square 1, 1: Square 2, 2: Wave, 3: Noise
|
||||||
enum { osc_count = 4 }; // 0 <= chan < osc_count
|
enum { osc_count = 4 }; // 0 <= chan < osc_count
|
||||||
void set_output( int chan, Blip_Buffer* center,
|
void set_output( int chan, Blip_Buffer* center,
|
||||||
Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||||
|
|
||||||
// Sets overall volume, where 1.0 is normal
|
// Sets overall volume, where 1.0 is normal
|
||||||
void volume( double );
|
void volume( double );
|
||||||
|
|
||||||
// Sets treble equalization
|
// Sets treble equalization
|
||||||
void treble_eq( blip_eq_t const& );
|
void treble_eq( blip_eq_t const& );
|
||||||
|
|
||||||
// Treble and bass values for various hardware.
|
// Treble and bass values for various hardware.
|
||||||
enum {
|
enum {
|
||||||
speaker_treble = -47, // speaker on system
|
speaker_treble = -47, // speaker on system
|
||||||
speaker_bass = 2000,
|
speaker_bass = 2000,
|
||||||
dmg_treble = 0, // headphones on each system
|
dmg_treble = 0, // headphones on each system
|
||||||
dmg_bass = 30,
|
dmg_bass = 30,
|
||||||
cgb_treble = 0,
|
cgb_treble = 0,
|
||||||
cgb_bass = 300, // CGB has much less bass
|
cgb_bass = 300, // CGB has much less bass
|
||||||
agb_treble = 0,
|
agb_treble = 0,
|
||||||
agb_bass = 30
|
agb_bass = 30
|
||||||
};
|
};
|
||||||
|
|
||||||
// If true, reduces clicking by disabling DAC biasing. Note that this reduces
|
// If true, reduces clicking by disabling DAC biasing. Note that this reduces
|
||||||
// emulation accuracy, since the clicks are authentic.
|
// emulation accuracy, since the clicks are authentic.
|
||||||
void reduce_clicks( bool reduce = true );
|
void reduce_clicks( bool reduce = true );
|
||||||
|
|
||||||
// Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the
|
// Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the
|
||||||
// tempo in a music player.
|
// tempo in a music player.
|
||||||
void set_tempo( double );
|
void set_tempo( double );
|
||||||
|
|
||||||
// Saves full emulation state to state_out. Data format is portable and
|
// Saves full emulation state to state_out. Data format is portable and
|
||||||
// includes some extra space to avoid expansion in case more state needs
|
// includes some extra space to avoid expansion in case more state needs
|
||||||
// to be stored in the future.
|
// to be stored in the future.
|
||||||
void save_state( gb_apu_state_t* state_out );
|
void save_state( gb_apu_state_t* state_out );
|
||||||
|
|
||||||
// Loads state. You should call reset() BEFORE this.
|
// Loads state. You should call reset() BEFORE this.
|
||||||
blargg_err_t load_state( gb_apu_state_t const& in );
|
blargg_err_t load_state( gb_apu_state_t const& in );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Gb_Apu( const Gb_Apu& );
|
Gb_Apu( const Gb_Apu& );
|
||||||
Gb_Apu& operator = ( const Gb_Apu& );
|
Gb_Apu& operator = ( const Gb_Apu& );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Gb_Apu();
|
Gb_Apu();
|
||||||
|
|
||||||
// Use set_output() in place of these
|
// Use set_output() in place of these
|
||||||
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); )
|
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); )
|
||||||
BLARGG_DEPRECATED( void output ( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ); )
|
BLARGG_DEPRECATED( void output ( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ); )
|
||||||
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ) { set_output( i, c, c, c ); } )
|
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ) { set_output( i, c, c, c ); } )
|
||||||
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( i, c, l, r ); } )
|
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( i, c, l, r ); } )
|
||||||
|
|
||||||
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0xFF10 }; )
|
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0xFF10 }; )
|
||||||
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0xFF3F }; )
|
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0xFF3F }; )
|
||||||
BLARGG_DEPRECATED_TEXT( enum { register_count = end_addr - start_addr + 1 }; )
|
BLARGG_DEPRECATED_TEXT( enum { register_count = end_addr - start_addr + 1 }; )
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Gb_Osc* oscs [osc_count];
|
Gb_Osc* oscs [osc_count];
|
||||||
blip_time_t last_time; // time sound emulator has been run to
|
blip_time_t last_time; // time sound emulator has been run to
|
||||||
blip_time_t frame_period; // clocks between each frame sequencer step
|
blip_time_t frame_period; // clocks between each frame sequencer step
|
||||||
double volume_;
|
double volume_;
|
||||||
bool reduce_clicks_;
|
bool reduce_clicks_;
|
||||||
|
|
||||||
Gb_Sweep_Square square1;
|
Gb_Sweep_Square square1;
|
||||||
Gb_Square square2;
|
Gb_Square square2;
|
||||||
Gb_Wave wave;
|
Gb_Wave wave;
|
||||||
Gb_Noise noise;
|
Gb_Noise noise;
|
||||||
blip_time_t frame_time; // time of next frame sequencer action
|
blip_time_t frame_time; // time of next frame sequencer action
|
||||||
int frame_phase; // phase of next frame sequencer step
|
int frame_phase; // phase of next frame sequencer step
|
||||||
enum { regs_size = io_size + 0x10 };
|
enum { regs_size = io_size + 0x10 };
|
||||||
BOOST::uint8_t regs [regs_size];// last values written to registers
|
BOOST::uint8_t regs [regs_size];// last values written to registers
|
||||||
|
|
||||||
// large objects after everything else
|
// large objects after everything else
|
||||||
Blip_Synth_Norm norm_synth;
|
Blip_Synth_Norm norm_synth;
|
||||||
Blip_Synth_Fast fast_synth;
|
Blip_Synth_Fast fast_synth;
|
||||||
|
|
||||||
void reset_lengths();
|
void reset_lengths();
|
||||||
void reset_regs();
|
void reset_regs();
|
||||||
int calc_output( int osc ) const;
|
int calc_output( int osc ) const;
|
||||||
void apply_stereo();
|
void apply_stereo();
|
||||||
void apply_volume();
|
void apply_volume();
|
||||||
void synth_volume( int );
|
void synth_volume( int );
|
||||||
void run_until_( blip_time_t );
|
void run_until_( blip_time_t );
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
void silence_osc( Gb_Osc& );
|
void silence_osc( Gb_Osc& );
|
||||||
void write_osc( int reg, int old_data, int data );
|
void write_osc( int reg, int old_data, int data );
|
||||||
const char* save_load( gb_apu_state_t*, bool save );
|
const char* save_load( gb_apu_state_t*, bool save );
|
||||||
void save_load2( gb_apu_state_t*, bool save );
|
void save_load2( gb_apu_state_t*, bool save );
|
||||||
friend class Gb_Apu2;
|
friend class Gb_Apu2;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format of save state. Should be stable across versions of the library,
|
// Format of save state. Should be stable across versions of the library,
|
||||||
// with earlier versions properly opening later save states. Includes some
|
// with earlier versions properly opening later save states. Includes some
|
||||||
// room for expansion so the state size shouldn't increase.
|
// room for expansion so the state size shouldn't increase.
|
||||||
struct gb_apu_state_t
|
struct gb_apu_state_t
|
||||||
{
|
{
|
||||||
#if GB_APU_CUSTOM_STATE
|
#if GB_APU_CUSTOM_STATE
|
||||||
// Values stored as plain int so your code can read/write them easily.
|
// Values stored as plain int so your code can read/write them easily.
|
||||||
// Structure can NOT be written to disk, since format is not portable.
|
// Structure can NOT be written to disk, since format is not portable.
|
||||||
typedef int val_t;
|
typedef int val_t;
|
||||||
#else
|
#else
|
||||||
// Values written in portable little-endian format, allowing structure
|
// Values written in portable little-endian format, allowing structure
|
||||||
// to be written directly to disk.
|
// to be written directly to disk.
|
||||||
typedef unsigned char val_t [4];
|
typedef unsigned char val_t [4];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
enum { format0 = 0x50414247 }; // 'GBAP'
|
enum { format0 = 0x50414247 }; // 'GBAP'
|
||||||
|
|
||||||
val_t format; // format of all following data
|
val_t format; // format of all following data
|
||||||
val_t version; // later versions just add fields to end
|
val_t version; // later versions just add fields to end
|
||||||
|
|
||||||
unsigned char regs [0x40];
|
unsigned char regs [0x40];
|
||||||
val_t frame_time;
|
val_t frame_time;
|
||||||
val_t frame_phase;
|
val_t frame_phase;
|
||||||
|
|
||||||
val_t sweep_freq;
|
val_t sweep_freq;
|
||||||
val_t sweep_delay;
|
val_t sweep_delay;
|
||||||
val_t sweep_enabled;
|
val_t sweep_enabled;
|
||||||
val_t sweep_neg;
|
val_t sweep_neg;
|
||||||
val_t noise_divider;
|
val_t noise_divider;
|
||||||
val_t wave_buf;
|
val_t wave_buf;
|
||||||
|
|
||||||
val_t delay [4];
|
val_t delay [4];
|
||||||
val_t length_ctr [4];
|
val_t length_ctr [4];
|
||||||
val_t phase [4];
|
val_t phase [4];
|
||||||
val_t enabled [4];
|
val_t enabled [4];
|
||||||
|
|
||||||
val_t env_delay [3];
|
val_t env_delay [3];
|
||||||
val_t env_volume [3];
|
val_t env_volume [3];
|
||||||
val_t env_enabled [3];
|
val_t env_enabled [3];
|
||||||
|
|
||||||
val_t unused [13]; // for future expansion
|
val_t unused [13]; // for future expansion
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Gb_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
inline void Gb_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
||||||
{
|
{
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
set_output( i, c, l, r );
|
set_output( i, c, l, r );
|
||||||
}
|
}
|
||||||
|
|
||||||
BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c ) { set_output( c, c, c ); } )
|
BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c ) { set_output( c, c, c ); } )
|
||||||
BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( c, l, r ); } )
|
BLARGG_DEPRECATED_TEXT( inline void Gb_Apu::output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) { set_output( c, l, r ); } )
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,51 +1,51 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Gb_Cpu.h"
|
#include "Gb_Cpu.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
inline void Gb_Cpu::set_code_page( int i, void* p )
|
inline void Gb_Cpu::set_code_page( int i, void* p )
|
||||||
{
|
{
|
||||||
byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size );
|
byte* p2 = STATIC_CAST(byte*,p) - GB_CPU_OFFSET( i * page_size );
|
||||||
cpu_state_.code_map [i] = p2;
|
cpu_state_.code_map [i] = p2;
|
||||||
cpu_state->code_map [i] = p2;
|
cpu_state->code_map [i] = p2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Cpu::reset( void* unmapped )
|
void Gb_Cpu::reset( void* unmapped )
|
||||||
{
|
{
|
||||||
check( cpu_state == &cpu_state_ );
|
check( cpu_state == &cpu_state_ );
|
||||||
cpu_state = &cpu_state_;
|
cpu_state = &cpu_state_;
|
||||||
|
|
||||||
cpu_state_.time = 0;
|
cpu_state_.time = 0;
|
||||||
|
|
||||||
for ( int i = 0; i < page_count + 1; ++i )
|
for ( int i = 0; i < page_count + 1; ++i )
|
||||||
set_code_page( i, unmapped );
|
set_code_page( i, unmapped );
|
||||||
|
|
||||||
memset( &r, 0, sizeof r );
|
memset( &r, 0, sizeof r );
|
||||||
|
|
||||||
blargg_verify_byte_order();
|
blargg_verify_byte_order();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gb_Cpu::map_code( addr_t start, int size, void* data )
|
void Gb_Cpu::map_code( addr_t start, int size, void* data )
|
||||||
{
|
{
|
||||||
// address range must begin and end on page boundaries
|
// address range must begin and end on page boundaries
|
||||||
require( start % page_size == 0 );
|
require( start % page_size == 0 );
|
||||||
require( size % page_size == 0 );
|
require( size % page_size == 0 );
|
||||||
require( start + size <= mem_size );
|
require( start + size <= mem_size );
|
||||||
|
|
||||||
for ( int offset = 0; offset < size; offset += page_size )
|
for ( int offset = 0; offset < size; offset += page_size )
|
||||||
set_code_page( (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset );
|
set_code_page( (start + offset) >> page_bits, STATIC_CAST(char*,data) + offset );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +1,82 @@
|
||||||
// Nintendo Game Boy CPU emulator
|
// Nintendo Game Boy CPU emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef GB_CPU_H
|
#ifndef GB_CPU_H
|
||||||
#define GB_CPU_H
|
#define GB_CPU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
|
|
||||||
class Gb_Cpu {
|
class Gb_Cpu {
|
||||||
public:
|
public:
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
|
|
||||||
enum { mem_size = 0x10000 };
|
enum { mem_size = 0x10000 };
|
||||||
|
|
||||||
// Clears registers and map all pages to unmapped
|
// Clears registers and map all pages to unmapped
|
||||||
void reset( void* unmapped = NULL );
|
void reset( void* unmapped = NULL );
|
||||||
|
|
||||||
// Maps code memory (memory accessed via the program counter). Start and size
|
// Maps code memory (memory accessed via the program counter). Start and size
|
||||||
// must be multiple of page_size.
|
// must be multiple of page_size.
|
||||||
enum { page_bits = 13 };
|
enum { page_bits = 13 };
|
||||||
enum { page_size = 1 << page_bits };
|
enum { page_size = 1 << page_bits };
|
||||||
void map_code( addr_t start, int size, void* code );
|
void map_code( addr_t start, int size, void* code );
|
||||||
|
|
||||||
// Accesses emulated memory as CPU does
|
// Accesses emulated memory as CPU does
|
||||||
byte* get_code( addr_t );
|
byte* get_code( addr_t );
|
||||||
|
|
||||||
// Game Boy Z-80 registers. NOT kept updated during emulation.
|
// Game Boy Z-80 registers. NOT kept updated during emulation.
|
||||||
struct core_regs_t {
|
struct core_regs_t {
|
||||||
BOOST::uint16_t bc, de, hl, fa;
|
BOOST::uint16_t bc, de, hl, fa;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct registers_t : core_regs_t {
|
struct registers_t : core_regs_t {
|
||||||
int pc; // more than 16 bits to allow overflow detection
|
int pc; // more than 16 bits to allow overflow detection
|
||||||
BOOST::uint16_t sp;
|
BOOST::uint16_t sp;
|
||||||
};
|
};
|
||||||
registers_t r;
|
registers_t r;
|
||||||
|
|
||||||
// Base address for RST vectors, to simplify GBS player (normally 0)
|
// Base address for RST vectors, to simplify GBS player (normally 0)
|
||||||
addr_t rst_base;
|
addr_t rst_base;
|
||||||
|
|
||||||
// Current time.
|
// Current time.
|
||||||
int time() const { return cpu_state->time; }
|
int time() const { return cpu_state->time; }
|
||||||
|
|
||||||
// Changes time. Must not be called during emulation.
|
// Changes time. Must not be called during emulation.
|
||||||
// Should be negative, because emulation stops once it becomes >= 0.
|
// Should be negative, because emulation stops once it becomes >= 0.
|
||||||
void set_time( int t ) { cpu_state->time = t; }
|
void set_time( int t ) { cpu_state->time = t; }
|
||||||
|
|
||||||
// Emulator reads this many bytes past end of a page
|
// Emulator reads this many bytes past end of a page
|
||||||
enum { cpu_padding = 8 };
|
enum { cpu_padding = 8 };
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; }
|
Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; }
|
||||||
enum { page_count = mem_size >> page_bits };
|
enum { page_count = mem_size >> page_bits };
|
||||||
|
|
||||||
struct cpu_state_t {
|
struct cpu_state_t {
|
||||||
byte* code_map [page_count + 1];
|
byte* code_map [page_count + 1];
|
||||||
int time;
|
int time;
|
||||||
};
|
};
|
||||||
cpu_state_t* cpu_state; // points to state_ or a local copy within run()
|
cpu_state_t* cpu_state; // points to state_ or a local copy within run()
|
||||||
cpu_state_t cpu_state_;
|
cpu_state_t cpu_state_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_code_page( int, void* );
|
void set_code_page( int, void* );
|
||||||
};
|
};
|
||||||
|
|
||||||
#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits)
|
#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits)
|
||||||
|
|
||||||
#if BLARGG_NONPORTABLE
|
#if BLARGG_NONPORTABLE
|
||||||
#define GB_CPU_OFFSET( addr ) (addr)
|
#define GB_CPU_OFFSET( addr ) (addr)
|
||||||
#else
|
#else
|
||||||
#define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1))
|
#define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr )
|
inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr )
|
||||||
{
|
{
|
||||||
return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr );
|
return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,188 +1,188 @@
|
||||||
// Private oscillators used by Gb_Apu
|
// Private oscillators used by Gb_Apu
|
||||||
|
|
||||||
// Gb_Snd_Emu $vers
|
// Gb_Snd_Emu $vers
|
||||||
#ifndef GB_OSCS_H
|
#ifndef GB_OSCS_H
|
||||||
#define GB_OSCS_H
|
#define GB_OSCS_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
#ifndef GB_APU_OVERCLOCK
|
#ifndef GB_APU_OVERCLOCK
|
||||||
#define GB_APU_OVERCLOCK 1
|
#define GB_APU_OVERCLOCK 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1)
|
#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1)
|
||||||
#error "GB_APU_OVERCLOCK must be a power of 2"
|
#error "GB_APU_OVERCLOCK must be a power of 2"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Gb_Osc {
|
class Gb_Osc {
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
// 11-bit frequency in NRx3 and NRx4
|
// 11-bit frequency in NRx3 and NRx4
|
||||||
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
|
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
|
||||||
|
|
||||||
void update_amp( blip_time_t, int new_amp );
|
void update_amp( blip_time_t, int new_amp );
|
||||||
int write_trig( int frame_phase, int max_len, int old_data );
|
int write_trig( int frame_phase, int max_len, int old_data );
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum { clk_mul = GB_APU_OVERCLOCK };
|
enum { clk_mul = GB_APU_OVERCLOCK };
|
||||||
enum { dac_bias = 7 };
|
enum { dac_bias = 7 };
|
||||||
|
|
||||||
Blip_Buffer* outputs [4];// NULL, right, left, center
|
Blip_Buffer* outputs [4];// NULL, right, left, center
|
||||||
Blip_Buffer* output; // where to output sound
|
Blip_Buffer* output; // where to output sound
|
||||||
BOOST::uint8_t* regs; // osc's 5 registers
|
BOOST::uint8_t* regs; // osc's 5 registers
|
||||||
int mode; // mode_dmg, mode_cgb, mode_agb
|
int mode; // mode_dmg, mode_cgb, mode_agb
|
||||||
int dac_off_amp;// amplitude when DAC is off
|
int dac_off_amp;// amplitude when DAC is off
|
||||||
int last_amp; // current amplitude in Blip_Buffer
|
int last_amp; // current amplitude in Blip_Buffer
|
||||||
Blip_Synth_Norm const* norm_synth;
|
Blip_Synth_Norm const* norm_synth;
|
||||||
Blip_Synth_Fast const* fast_synth;
|
Blip_Synth_Fast const* fast_synth;
|
||||||
|
|
||||||
int delay; // clocks until frequency timer expires
|
int delay; // clocks until frequency timer expires
|
||||||
int length_ctr; // length counter
|
int length_ctr; // length counter
|
||||||
unsigned phase; // waveform phase (or equivalent)
|
unsigned phase; // waveform phase (or equivalent)
|
||||||
bool enabled; // internal enabled flag
|
bool enabled; // internal enabled flag
|
||||||
|
|
||||||
void clock_length();
|
void clock_length();
|
||||||
void reset();
|
void reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
class Gb_Env : public Gb_Osc {
|
class Gb_Env : public Gb_Osc {
|
||||||
public:
|
public:
|
||||||
int env_delay;
|
int env_delay;
|
||||||
int volume;
|
int volume;
|
||||||
bool env_enabled;
|
bool env_enabled;
|
||||||
|
|
||||||
void clock_envelope();
|
void clock_envelope();
|
||||||
bool write_register( int frame_phase, int reg, int old_data, int data );
|
bool write_register( int frame_phase, int reg, int old_data, int data );
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
env_delay = 0;
|
env_delay = 0;
|
||||||
volume = 0;
|
volume = 0;
|
||||||
Gb_Osc::reset();
|
Gb_Osc::reset();
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
// Non-zero if DAC is enabled
|
// Non-zero if DAC is enabled
|
||||||
int dac_enabled() const { return regs [2] & 0xF8; }
|
int dac_enabled() const { return regs [2] & 0xF8; }
|
||||||
private:
|
private:
|
||||||
void zombie_volume( int old, int data );
|
void zombie_volume( int old, int data );
|
||||||
int reload_env_timer();
|
int reload_env_timer();
|
||||||
};
|
};
|
||||||
|
|
||||||
class Gb_Square : public Gb_Env {
|
class Gb_Square : public Gb_Env {
|
||||||
public:
|
public:
|
||||||
bool write_register( int frame_phase, int reg, int old_data, int data );
|
bool write_register( int frame_phase, int reg, int old_data, int data );
|
||||||
void run( blip_time_t, blip_time_t );
|
void run( blip_time_t, blip_time_t );
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
Gb_Env::reset();
|
Gb_Env::reset();
|
||||||
delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger)
|
delay = 0x40000000; // TODO: something less hacky (never clocked until first trigger)
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
// Frequency timer period
|
// Frequency timer period
|
||||||
int period() const { return (2048 - frequency()) * (4 * clk_mul); }
|
int period() const { return (2048 - frequency()) * (4 * clk_mul); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Gb_Sweep_Square : public Gb_Square {
|
class Gb_Sweep_Square : public Gb_Square {
|
||||||
public:
|
public:
|
||||||
int sweep_freq;
|
int sweep_freq;
|
||||||
int sweep_delay;
|
int sweep_delay;
|
||||||
bool sweep_enabled;
|
bool sweep_enabled;
|
||||||
bool sweep_neg;
|
bool sweep_neg;
|
||||||
|
|
||||||
void clock_sweep();
|
void clock_sweep();
|
||||||
void write_register( int frame_phase, int reg, int old_data, int data );
|
void write_register( int frame_phase, int reg, int old_data, int data );
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
sweep_freq = 0;
|
sweep_freq = 0;
|
||||||
sweep_delay = 0;
|
sweep_delay = 0;
|
||||||
sweep_enabled = false;
|
sweep_enabled = false;
|
||||||
sweep_neg = false;
|
sweep_neg = false;
|
||||||
Gb_Square::reset();
|
Gb_Square::reset();
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
enum { period_mask = 0x70 };
|
enum { period_mask = 0x70 };
|
||||||
enum { shift_mask = 0x07 };
|
enum { shift_mask = 0x07 };
|
||||||
|
|
||||||
void calc_sweep( bool update );
|
void calc_sweep( bool update );
|
||||||
void reload_sweep_timer();
|
void reload_sweep_timer();
|
||||||
};
|
};
|
||||||
|
|
||||||
class Gb_Noise : public Gb_Env {
|
class Gb_Noise : public Gb_Env {
|
||||||
public:
|
public:
|
||||||
int divider; // noise has more complex frequency divider setup
|
int divider; // noise has more complex frequency divider setup
|
||||||
|
|
||||||
void run( blip_time_t, blip_time_t );
|
void run( blip_time_t, blip_time_t );
|
||||||
void write_register( int frame_phase, int reg, int old_data, int data );
|
void write_register( int frame_phase, int reg, int old_data, int data );
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
divider = 0;
|
divider = 0;
|
||||||
Gb_Env::reset();
|
Gb_Env::reset();
|
||||||
delay = 4 * clk_mul; // TODO: remove?
|
delay = 4 * clk_mul; // TODO: remove?
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { period2_mask = 0x1FFFF };
|
enum { period2_mask = 0x1FFFF };
|
||||||
|
|
||||||
int period2_index() const { return regs [3] >> 4; }
|
int period2_index() const { return regs [3] >> 4; }
|
||||||
int period2( int base = 8 ) const { return base << period2_index(); }
|
int period2( int base = 8 ) const { return base << period2_index(); }
|
||||||
unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; }
|
unsigned lfsr_mask() const { return (regs [3] & 0x08) ? ~0x4040 : ~0x4000; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Gb_Wave : public Gb_Osc {
|
class Gb_Wave : public Gb_Osc {
|
||||||
public:
|
public:
|
||||||
int sample_buf; // last wave RAM byte read (hardware has this as well)
|
int sample_buf; // last wave RAM byte read (hardware has this as well)
|
||||||
|
|
||||||
void write_register( int frame_phase, int reg, int old_data, int data );
|
void write_register( int frame_phase, int reg, int old_data, int data );
|
||||||
void run( blip_time_t, blip_time_t );
|
void run( blip_time_t, blip_time_t );
|
||||||
|
|
||||||
// Reads/writes wave RAM
|
// Reads/writes wave RAM
|
||||||
int read( int addr ) const;
|
int read( int addr ) const;
|
||||||
void write( int addr, int data );
|
void write( int addr, int data );
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
sample_buf = 0;
|
sample_buf = 0;
|
||||||
Gb_Osc::reset();
|
Gb_Osc::reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { bank40_mask = 0x40 };
|
enum { bank40_mask = 0x40 };
|
||||||
enum { bank_size = 32 };
|
enum { bank_size = 32 };
|
||||||
|
|
||||||
int agb_mask; // 0xFF if AGB features enabled, 0 otherwise
|
int agb_mask; // 0xFF if AGB features enabled, 0 otherwise
|
||||||
BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU
|
BOOST::uint8_t* wave_ram; // 32 bytes (64 nybbles), stored in APU
|
||||||
|
|
||||||
friend class Gb_Apu;
|
friend class Gb_Apu;
|
||||||
|
|
||||||
// Frequency timer period
|
// Frequency timer period
|
||||||
int period() const { return (2048 - frequency()) * (2 * clk_mul); }
|
int period() const { return (2048 - frequency()) * (2 * clk_mul); }
|
||||||
|
|
||||||
// Non-zero if DAC is enabled
|
// Non-zero if DAC is enabled
|
||||||
int dac_enabled() const { return regs [0] & 0x80; }
|
int dac_enabled() const { return regs [0] & 0x80; }
|
||||||
|
|
||||||
void corrupt_wave();
|
void corrupt_wave();
|
||||||
|
|
||||||
BOOST::uint8_t* wave_bank() const { return &wave_ram [(~regs [0] & bank40_mask) >> 2 & agb_mask]; }
|
BOOST::uint8_t* wave_bank() const { return &wave_ram [(~regs [0] & bank40_mask) >> 2 & agb_mask]; }
|
||||||
|
|
||||||
// Wave index that would be accessed, or -1 if no access would occur
|
// Wave index that would be accessed, or -1 if no access would occur
|
||||||
int access( int addr ) const;
|
int access( int addr ) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline int Gb_Wave::read( int addr ) const
|
inline int Gb_Wave::read( int addr ) const
|
||||||
{
|
{
|
||||||
int index = access( addr );
|
int index = access( addr );
|
||||||
return (index < 0 ? 0xFF : wave_bank() [index]);
|
return (index < 0 ? 0xFF : wave_bank() [index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Gb_Wave::write( int addr, int data )
|
inline void Gb_Wave::write( int addr, int data )
|
||||||
{
|
{
|
||||||
int index = access( addr );
|
int index = access( addr );
|
||||||
if ( index >= 0 )
|
if ( index >= 0 )
|
||||||
wave_bank() [index] = data;;
|
wave_bank() [index] = data;;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,208 +1,208 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Gbs_Core.h"
|
#include "Gbs_Core.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const tempo_unit = 16;
|
int const tempo_unit = 16;
|
||||||
int const idle_addr = 0xF00D;
|
int const idle_addr = 0xF00D;
|
||||||
int const bank_size = 0x4000;
|
int const bank_size = 0x4000;
|
||||||
|
|
||||||
Gbs_Core::Gbs_Core() : rom( bank_size )
|
Gbs_Core::Gbs_Core() : rom( bank_size )
|
||||||
{
|
{
|
||||||
tempo = tempo_unit;
|
tempo = tempo_unit;
|
||||||
assert( offsetof (header_t,copyright [32]) == header_t::size );
|
assert( offsetof (header_t,copyright [32]) == header_t::size );
|
||||||
}
|
}
|
||||||
|
|
||||||
Gbs_Core::~Gbs_Core() { }
|
Gbs_Core::~Gbs_Core() { }
|
||||||
|
|
||||||
void Gbs_Core::unload()
|
void Gbs_Core::unload()
|
||||||
{
|
{
|
||||||
header_.timer_mode = 0; // set_tempo() reads this
|
header_.timer_mode = 0; // set_tempo() reads this
|
||||||
rom.clear();
|
rom.clear();
|
||||||
Gme_Loader::unload();
|
Gme_Loader::unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Gbs_Core::header_t::valid_tag() const
|
bool Gbs_Core::header_t::valid_tag() const
|
||||||
{
|
{
|
||||||
return 0 == memcmp( tag, "GBS", 3 );
|
return 0 == memcmp( tag, "GBS", 3 );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Core::load_( Data_Reader& in )
|
blargg_err_t Gbs_Core::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
|
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
|
||||||
|
|
||||||
if ( !header_.valid_tag() )
|
if ( !header_.valid_tag() )
|
||||||
return blargg_err_file_type;
|
return blargg_err_file_type;
|
||||||
|
|
||||||
if ( header_.vers != 1 )
|
if ( header_.vers != 1 )
|
||||||
set_warning( "Unknown file version" );
|
set_warning( "Unknown file version" );
|
||||||
|
|
||||||
if ( header_.timer_mode & 0x78 )
|
if ( header_.timer_mode & 0x78 )
|
||||||
set_warning( "Invalid timer mode" );
|
set_warning( "Invalid timer mode" );
|
||||||
|
|
||||||
addr_t load_addr = get_le16( header_.load_addr );
|
addr_t load_addr = get_le16( header_.load_addr );
|
||||||
if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
|
if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
|
||||||
load_addr < 0x400 )
|
load_addr < 0x400 )
|
||||||
set_warning( "Invalid load/init/play address" );
|
set_warning( "Invalid load/init/play address" );
|
||||||
|
|
||||||
cpu.rst_base = load_addr;
|
cpu.rst_base = load_addr;
|
||||||
rom.set_addr( load_addr );
|
rom.set_addr( load_addr );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Core::set_bank( int n )
|
void Gbs_Core::set_bank( int n )
|
||||||
{
|
{
|
||||||
addr_t addr = rom.mask_addr( n * bank_size );
|
addr_t addr = rom.mask_addr( n * bank_size );
|
||||||
if ( addr == 0 && rom.size() > bank_size )
|
if ( addr == 0 && rom.size() > bank_size )
|
||||||
addr = bank_size; // MBC1&2 behavior, bank 0 acts like bank 1
|
addr = bank_size; // MBC1&2 behavior, bank 0 acts like bank 1
|
||||||
cpu.map_code( bank_size, bank_size, rom.at_addr( addr ) );
|
cpu.map_code( bank_size, bank_size, rom.at_addr( addr ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Core::update_timer()
|
void Gbs_Core::update_timer()
|
||||||
{
|
{
|
||||||
play_period_ = 70224 / tempo_unit; // 59.73 Hz
|
play_period_ = 70224 / tempo_unit; // 59.73 Hz
|
||||||
|
|
||||||
if ( header_.timer_mode & 0x04 )
|
if ( header_.timer_mode & 0x04 )
|
||||||
{
|
{
|
||||||
// Using custom rate
|
// Using custom rate
|
||||||
static byte const rates [4] = { 6, 0, 2, 4 };
|
static byte const rates [4] = { 6, 0, 2, 4 };
|
||||||
// TODO: emulate double speed CPU mode rather than halving timer rate
|
// TODO: emulate double speed CPU mode rather than halving timer rate
|
||||||
int double_speed = header_.timer_mode >> 7;
|
int double_speed = header_.timer_mode >> 7;
|
||||||
int shift = rates [ram [hi_page + 7] & 3] - double_speed;
|
int shift = rates [ram [hi_page + 7] & 3] - double_speed;
|
||||||
play_period_ = (256 - ram [hi_page + 6]) << shift;
|
play_period_ = (256 - ram [hi_page + 6]) << shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
play_period_ *= tempo;
|
play_period_ *= tempo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Core::set_tempo( double t )
|
void Gbs_Core::set_tempo( double t )
|
||||||
{
|
{
|
||||||
tempo = (int) (tempo_unit / t + 0.5);
|
tempo = (int) (tempo_unit / t + 0.5);
|
||||||
apu_.set_tempo( t );
|
apu_.set_tempo( t );
|
||||||
update_timer();
|
update_timer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jumps to routine, given pointer to address in file header. Pushes idle_addr
|
// Jumps to routine, given pointer to address in file header. Pushes idle_addr
|
||||||
// as return address, NOT old PC.
|
// as return address, NOT old PC.
|
||||||
void Gbs_Core::jsr_then_stop( byte const addr [] )
|
void Gbs_Core::jsr_then_stop( byte const addr [] )
|
||||||
{
|
{
|
||||||
check( cpu.r.sp == get_le16( header_.stack_ptr ) );
|
check( cpu.r.sp == get_le16( header_.stack_ptr ) );
|
||||||
cpu.r.pc = get_le16( addr );
|
cpu.r.pc = get_le16( addr );
|
||||||
write_mem( --cpu.r.sp, idle_addr >> 8 );
|
write_mem( --cpu.r.sp, idle_addr >> 8 );
|
||||||
write_mem( --cpu.r.sp, idle_addr );
|
write_mem( --cpu.r.sp, idle_addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Core::start_track( int track, Gb_Apu::mode_t mode )
|
blargg_err_t Gbs_Core::start_track( int track, Gb_Apu::mode_t mode )
|
||||||
{
|
{
|
||||||
// Reset APU to state expected by most rips
|
// Reset APU to state expected by most rips
|
||||||
static byte const sound_data [] = {
|
static byte const sound_data [] = {
|
||||||
0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled
|
0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled
|
||||||
0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled
|
0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled
|
||||||
0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled
|
0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled
|
||||||
0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled
|
0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled
|
||||||
0x77, 0xFF, 0x80, // max volume, all chans in center, power on
|
0x77, 0xFF, 0x80, // max volume, all chans in center, power on
|
||||||
};
|
};
|
||||||
apu_.reset( mode );
|
apu_.reset( mode );
|
||||||
apu_.write_register( 0, 0xFF26, 0x80 ); // power on
|
apu_.write_register( 0, 0xFF26, 0x80 ); // power on
|
||||||
for ( int i = 0; i < (int) sizeof sound_data; i++ )
|
for ( int i = 0; i < (int) sizeof sound_data; i++ )
|
||||||
apu_.write_register( 0, i + apu_.io_addr, sound_data [i] );
|
apu_.write_register( 0, i + apu_.io_addr, sound_data [i] );
|
||||||
apu_.end_frame( 1 ); // necessary to get click out of the way
|
apu_.end_frame( 1 ); // necessary to get click out of the way
|
||||||
|
|
||||||
// Init memory and I/O registers
|
// Init memory and I/O registers
|
||||||
memset( ram, 0, 0x4000 );
|
memset( ram, 0, 0x4000 );
|
||||||
memset( ram + 0x4000, 0xFF, 0x1F80 );
|
memset( ram + 0x4000, 0xFF, 0x1F80 );
|
||||||
memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
|
memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 );
|
||||||
ram [hi_page] = 0; // joypad reads back as 0
|
ram [hi_page] = 0; // joypad reads back as 0
|
||||||
ram [idle_addr - ram_addr] = 0xED; // illegal instruction
|
ram [idle_addr - ram_addr] = 0xED; // illegal instruction
|
||||||
ram [hi_page + 6] = header_.timer_modulo;
|
ram [hi_page + 6] = header_.timer_modulo;
|
||||||
ram [hi_page + 7] = header_.timer_mode;
|
ram [hi_page + 7] = header_.timer_mode;
|
||||||
|
|
||||||
// Map memory
|
// Map memory
|
||||||
cpu.reset( rom.unmapped() );
|
cpu.reset( rom.unmapped() );
|
||||||
cpu.map_code( ram_addr, 0x10000 - ram_addr, ram );
|
cpu.map_code( ram_addr, 0x10000 - ram_addr, ram );
|
||||||
cpu.map_code( 0, bank_size, rom.at_addr( 0 ) );
|
cpu.map_code( 0, bank_size, rom.at_addr( 0 ) );
|
||||||
set_bank( rom.size() > bank_size );
|
set_bank( rom.size() > bank_size );
|
||||||
|
|
||||||
// CPU registers, timing
|
// CPU registers, timing
|
||||||
update_timer();
|
update_timer();
|
||||||
next_play = play_period_;
|
next_play = play_period_;
|
||||||
cpu.r.fa = track;
|
cpu.r.fa = track;
|
||||||
cpu.r.sp = get_le16( header_.stack_ptr );
|
cpu.r.sp = get_le16( header_.stack_ptr );
|
||||||
jsr_then_stop( header_.init_addr );
|
jsr_then_stop( header_.init_addr );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Core::run_until( int end )
|
blargg_err_t Gbs_Core::run_until( int end )
|
||||||
{
|
{
|
||||||
end_time = end;
|
end_time = end;
|
||||||
cpu.set_time( cpu.time() - end );
|
cpu.set_time( cpu.time() - end );
|
||||||
while ( true )
|
while ( true )
|
||||||
{
|
{
|
||||||
run_cpu();
|
run_cpu();
|
||||||
if ( cpu.time() >= 0 )
|
if ( cpu.time() >= 0 )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if ( cpu.r.pc == idle_addr )
|
if ( cpu.r.pc == idle_addr )
|
||||||
{
|
{
|
||||||
if ( next_play > end_time )
|
if ( next_play > end_time )
|
||||||
{
|
{
|
||||||
cpu.set_time( 0 );
|
cpu.set_time( 0 );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cpu.time() < next_play - end_time )
|
if ( cpu.time() < next_play - end_time )
|
||||||
cpu.set_time( next_play - end_time );
|
cpu.set_time( next_play - end_time );
|
||||||
next_play += play_period_;
|
next_play += play_period_;
|
||||||
jsr_then_stop( header_.play_addr );
|
jsr_then_stop( header_.play_addr );
|
||||||
}
|
}
|
||||||
else if ( cpu.r.pc > 0xFFFF )
|
else if ( cpu.r.pc > 0xFFFF )
|
||||||
{
|
{
|
||||||
dprintf( "PC wrapped around\n" );
|
dprintf( "PC wrapped around\n" );
|
||||||
cpu.r.pc &= 0xFFFF;
|
cpu.r.pc &= 0xFFFF;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
set_warning( "Emulation error (illegal/unsupported instruction)" );
|
set_warning( "Emulation error (illegal/unsupported instruction)" );
|
||||||
dprintf( "Bad opcode $%02X at $%04X\n",
|
dprintf( "Bad opcode $%02X at $%04X\n",
|
||||||
(int) *cpu.get_code( cpu.r.pc ), (int) cpu.r.pc );
|
(int) *cpu.get_code( cpu.r.pc ), (int) cpu.r.pc );
|
||||||
cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF;
|
cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF;
|
||||||
cpu.set_time( cpu.time() + 6 );
|
cpu.set_time( cpu.time() + 6 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Core::end_frame( int end )
|
blargg_err_t Gbs_Core::end_frame( int end )
|
||||||
{
|
{
|
||||||
RETURN_ERR( run_until( end ) );
|
RETURN_ERR( run_until( end ) );
|
||||||
|
|
||||||
next_play -= end;
|
next_play -= end;
|
||||||
if ( next_play < 0 ) // happens when play routine takes too long
|
if ( next_play < 0 ) // happens when play routine takes too long
|
||||||
{
|
{
|
||||||
#if !GBS_IGNORE_STARVED_PLAY
|
#if !GBS_IGNORE_STARVED_PLAY
|
||||||
check( false );
|
check( false );
|
||||||
#endif
|
#endif
|
||||||
next_play = 0;
|
next_play = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
apu_.end_frame( end );
|
apu_.end_frame( end );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,109 @@
|
||||||
// Nintendo Game Boy GBS music file emulator core
|
// Nintendo Game Boy GBS music file emulator core
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef GBS_CORE_H
|
#ifndef GBS_CORE_H
|
||||||
#define GBS_CORE_H
|
#define GBS_CORE_H
|
||||||
|
|
||||||
#include "Gme_Loader.h"
|
#include "Gme_Loader.h"
|
||||||
#include "Rom_Data.h"
|
#include "Rom_Data.h"
|
||||||
#include "Gb_Cpu.h"
|
#include "Gb_Cpu.h"
|
||||||
#include "Gb_Apu.h"
|
#include "Gb_Apu.h"
|
||||||
|
|
||||||
class Gbs_Core : public Gme_Loader {
|
class Gbs_Core : public Gme_Loader {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// GBS file header
|
// GBS file header
|
||||||
struct header_t
|
struct header_t
|
||||||
{
|
{
|
||||||
enum { size = 112 };
|
enum { size = 112 };
|
||||||
|
|
||||||
char tag [ 3];
|
char tag [ 3];
|
||||||
byte vers;
|
byte vers;
|
||||||
byte track_count;
|
byte track_count;
|
||||||
byte first_track;
|
byte first_track;
|
||||||
byte load_addr [ 2];
|
byte load_addr [ 2];
|
||||||
byte init_addr [ 2];
|
byte init_addr [ 2];
|
||||||
byte play_addr [ 2];
|
byte play_addr [ 2];
|
||||||
byte stack_ptr [ 2];
|
byte stack_ptr [ 2];
|
||||||
byte timer_modulo;
|
byte timer_modulo;
|
||||||
byte timer_mode;
|
byte timer_mode;
|
||||||
char game [32]; // strings can be 32 chars, NOT terminated
|
char game [32]; // strings can be 32 chars, NOT terminated
|
||||||
char author [32];
|
char author [32];
|
||||||
char copyright [32];
|
char copyright [32];
|
||||||
|
|
||||||
// True if header has valid file signature
|
// True if header has valid file signature
|
||||||
bool valid_tag() const;
|
bool valid_tag() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return header_; }
|
header_t const& header() const { return header_; }
|
||||||
|
|
||||||
// Sound chip
|
// Sound chip
|
||||||
Gb_Apu& apu() { return apu_; }
|
Gb_Apu& apu() { return apu_; }
|
||||||
|
|
||||||
// ROM data
|
// ROM data
|
||||||
Rom_Data const& rom_() const { return rom; }
|
Rom_Data const& rom_() const { return rom; }
|
||||||
|
|
||||||
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
||||||
void set_tempo( double );
|
void set_tempo( double );
|
||||||
|
|
||||||
// Starts track, where 0 is the first. Uses specified APU mode.
|
// Starts track, where 0 is the first. Uses specified APU mode.
|
||||||
blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb );
|
blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb );
|
||||||
|
|
||||||
// Ends time frame at time t
|
// Ends time frame at time t
|
||||||
typedef int time_t; // clock count
|
typedef int time_t; // clock count
|
||||||
blargg_err_t end_frame( time_t t );
|
blargg_err_t end_frame( time_t t );
|
||||||
|
|
||||||
// Clocks between calls to play routine
|
// Clocks between calls to play routine
|
||||||
time_t play_period() const { return play_period_; }
|
time_t play_period() const { return play_period_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
|
|
||||||
// Current time
|
// Current time
|
||||||
time_t time() const { return cpu.time() + end_time; }
|
time_t time() const { return cpu.time() + end_time; }
|
||||||
|
|
||||||
// Runs emulator to time t
|
// Runs emulator to time t
|
||||||
blargg_err_t run_until( time_t t );
|
blargg_err_t run_until( time_t t );
|
||||||
|
|
||||||
// Runs CPU until time becomes >= 0
|
// Runs CPU until time becomes >= 0
|
||||||
void run_cpu();
|
void run_cpu();
|
||||||
|
|
||||||
// Reads/writes memory and I/O
|
// Reads/writes memory and I/O
|
||||||
int read_mem( addr_t );
|
int read_mem( addr_t );
|
||||||
void write_mem( addr_t, int );
|
void write_mem( addr_t, int );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Gbs_Core();
|
Gbs_Core();
|
||||||
~Gbs_Core();
|
~Gbs_Core();
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { ram_addr = 0xA000 };
|
enum { ram_addr = 0xA000 };
|
||||||
enum { io_base = 0xFF00 };
|
enum { io_base = 0xFF00 };
|
||||||
enum { hi_page = io_base - ram_addr };
|
enum { hi_page = io_base - ram_addr };
|
||||||
|
|
||||||
Rom_Data rom;
|
Rom_Data rom;
|
||||||
int tempo;
|
int tempo;
|
||||||
time_t end_time;
|
time_t end_time;
|
||||||
time_t play_period_;
|
time_t play_period_;
|
||||||
time_t next_play;
|
time_t next_play;
|
||||||
header_t header_;
|
header_t header_;
|
||||||
Gb_Cpu cpu;
|
Gb_Cpu cpu;
|
||||||
Gb_Apu apu_;
|
Gb_Apu apu_;
|
||||||
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
|
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
|
||||||
|
|
||||||
void update_timer();
|
void update_timer();
|
||||||
void jsr_then_stop( byte const [] );
|
void jsr_then_stop( byte const [] );
|
||||||
void set_bank( int n );
|
void set_bank( int n );
|
||||||
void write_io_inline( int offset, int data, int base );
|
void write_io_inline( int offset, int data, int base );
|
||||||
void write_io_( int offset, int data );
|
void write_io_( int offset, int data );
|
||||||
int read_io( int offset );
|
int read_io( int offset );
|
||||||
void write_io( int offset, int data );
|
void write_io( int offset, int data );
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,134 +1,134 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Gbs_Core.h"
|
#include "Gbs_Core.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
//#include "gb_cpu_log.h"
|
//#include "gb_cpu_log.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
#ifndef LOG_MEM
|
#ifndef LOG_MEM
|
||||||
#define LOG_MEM( addr, str, data ) data
|
#define LOG_MEM( addr, str, data ) data
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int Gbs_Core::read_mem( addr_t addr )
|
int Gbs_Core::read_mem( addr_t addr )
|
||||||
{
|
{
|
||||||
int result = *cpu.get_code( addr );
|
int result = *cpu.get_code( addr );
|
||||||
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
|
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
|
||||||
result = apu_.read_register( time(), addr );
|
result = apu_.read_register( time(), addr );
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
|
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
|
||||||
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
|
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
|
||||||
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 )
|
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 )
|
||||||
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
|
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return LOG_MEM( addr, ">", result );
|
return LOG_MEM( addr, ">", result );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Gbs_Core::write_io_inline( int offset, int data, int base )
|
inline void Gbs_Core::write_io_inline( int offset, int data, int base )
|
||||||
{
|
{
|
||||||
if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size )
|
if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size )
|
||||||
apu_.write_register( time(), offset + base, data & 0xFF );
|
apu_.write_register( time(), offset + base, data & 0xFF );
|
||||||
else if ( (unsigned) (offset - (0xFF06 - base)) < 2 )
|
else if ( (unsigned) (offset - (0xFF06 - base)) < 2 )
|
||||||
update_timer();
|
update_timer();
|
||||||
else if ( offset == io_base - base )
|
else if ( offset == io_base - base )
|
||||||
ram [base - ram_addr + offset] = 0; // keep joypad return value 0
|
ram [base - ram_addr + offset] = 0; // keep joypad return value 0
|
||||||
else
|
else
|
||||||
ram [base - ram_addr + offset] = 0xFF;
|
ram [base - ram_addr + offset] = 0xFF;
|
||||||
|
|
||||||
//if ( offset == 0xFFFF - base )
|
//if ( offset == 0xFFFF - base )
|
||||||
// dprintf( "Wrote interrupt mask\n" );
|
// dprintf( "Wrote interrupt mask\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Core::write_mem( addr_t addr, int data )
|
void Gbs_Core::write_mem( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
(void) LOG_MEM( addr, "<", data );
|
(void) LOG_MEM( addr, "<", data );
|
||||||
|
|
||||||
int offset = addr - ram_addr;
|
int offset = addr - ram_addr;
|
||||||
if ( (unsigned) offset < 0x10000 - ram_addr )
|
if ( (unsigned) offset < 0x10000 - ram_addr )
|
||||||
{
|
{
|
||||||
ram [offset] = data;
|
ram [offset] = data;
|
||||||
|
|
||||||
offset -= 0xE000 - ram_addr;
|
offset -= 0xE000 - ram_addr;
|
||||||
if ( (unsigned) offset < 0x1F80 )
|
if ( (unsigned) offset < 0x1F80 )
|
||||||
write_io_inline( offset, data, 0xE000 );
|
write_io_inline( offset, data, 0xE000 );
|
||||||
}
|
}
|
||||||
else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 )
|
else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 )
|
||||||
{
|
{
|
||||||
set_bank( data & 0xFF );
|
set_bank( data & 0xFF );
|
||||||
}
|
}
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
|
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
|
||||||
{
|
{
|
||||||
dprintf( "Unmapped write $%04X\n", (unsigned) addr );
|
dprintf( "Unmapped write $%04X\n", (unsigned) addr );
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Core::write_io_( int offset, int data )
|
void Gbs_Core::write_io_( int offset, int data )
|
||||||
{
|
{
|
||||||
write_io_inline( offset, data, io_base );
|
write_io_inline( offset, data, io_base );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Gbs_Core::write_io( int offset, int data )
|
inline void Gbs_Core::write_io( int offset, int data )
|
||||||
{
|
{
|
||||||
(void) LOG_MEM( offset + io_base, "<", data );
|
(void) LOG_MEM( offset + io_base, "<", data );
|
||||||
|
|
||||||
ram [io_base - ram_addr + offset] = data;
|
ram [io_base - ram_addr + offset] = data;
|
||||||
if ( (unsigned) offset < 0x80 )
|
if ( (unsigned) offset < 0x80 )
|
||||||
write_io_( offset, data );
|
write_io_( offset, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Gbs_Core::read_io( int offset )
|
int Gbs_Core::read_io( int offset )
|
||||||
{
|
{
|
||||||
int const io_base = 0xFF00;
|
int const io_base = 0xFF00;
|
||||||
int result = ram [io_base - ram_addr + offset];
|
int result = ram [io_base - ram_addr + offset];
|
||||||
|
|
||||||
if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size )
|
if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size )
|
||||||
{
|
{
|
||||||
result = apu_.read_register( time(), offset + io_base );
|
result = apu_.read_register( time(), offset + io_base );
|
||||||
(void) LOG_MEM( offset + io_base, ">", result );
|
(void) LOG_MEM( offset + io_base, ">", result );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
check( result == read_mem( offset + io_base ) );
|
check( result == read_mem( offset + io_base ) );
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define READ_FAST( addr, out ) \
|
#define READ_FAST( addr, out ) \
|
||||||
{\
|
{\
|
||||||
out = READ_CODE( addr );\
|
out = READ_CODE( addr );\
|
||||||
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\
|
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\
|
||||||
out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\
|
out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\
|
||||||
else\
|
else\
|
||||||
check( out == read_mem( addr ) );\
|
check( out == read_mem( addr ) );\
|
||||||
}
|
}
|
||||||
|
|
||||||
#define READ_MEM( addr ) read_mem( addr )
|
#define READ_MEM( addr ) read_mem( addr )
|
||||||
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
||||||
|
|
||||||
#define WRITE_IO( addr, data ) write_io( addr, data )
|
#define WRITE_IO( addr, data ) write_io( addr, data )
|
||||||
#define READ_IO( addr, out ) out = read_io( addr )
|
#define READ_IO( addr, out ) out = read_io( addr )
|
||||||
|
|
||||||
#define CPU cpu
|
#define CPU cpu
|
||||||
|
|
||||||
#define CPU_BEGIN \
|
#define CPU_BEGIN \
|
||||||
void Gbs_Core::run_cpu()\
|
void Gbs_Core::run_cpu()\
|
||||||
{
|
{
|
||||||
#include "Gb_Cpu_run.h"
|
#include "Gb_Cpu_run.h"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,167 +1,167 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Gbs_Emu.h"
|
#include "Gbs_Emu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000, 0,0,0,0,0,0,0,0 };
|
Gbs_Emu::equalizer_t const Gbs_Emu::handheld_eq = { -47.0, 2000, 0,0,0,0,0,0,0,0 };
|
||||||
Gbs_Emu::equalizer_t const Gbs_Emu::cgb_eq = { 0.0, 300, 0,0,0,0,0,0,0,0 };
|
Gbs_Emu::equalizer_t const Gbs_Emu::cgb_eq = { 0.0, 300, 0,0,0,0,0,0,0,0 };
|
||||||
Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 30, 0,0,0,0,0,0,0,0 }; // DMG
|
Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq = { 0.0, 30, 0,0,0,0,0,0,0,0 }; // DMG
|
||||||
|
|
||||||
Gbs_Emu::Gbs_Emu()
|
Gbs_Emu::Gbs_Emu()
|
||||||
{
|
{
|
||||||
sound_hardware = sound_gbs;
|
sound_hardware = sound_gbs;
|
||||||
enable_clicking( false );
|
enable_clicking( false );
|
||||||
set_type( gme_gbs_type );
|
set_type( gme_gbs_type );
|
||||||
set_silence_lookahead( 6 );
|
set_silence_lookahead( 6 );
|
||||||
set_max_initial_silence( 21 );
|
set_max_initial_silence( 21 );
|
||||||
set_gain( 1.2 );
|
set_gain( 1.2 );
|
||||||
|
|
||||||
// kind of midway between headphones and speaker
|
// kind of midway between headphones and speaker
|
||||||
static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 };
|
static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 };
|
||||||
set_equalizer( eq );
|
set_equalizer( eq );
|
||||||
}
|
}
|
||||||
|
|
||||||
Gbs_Emu::~Gbs_Emu() { }
|
Gbs_Emu::~Gbs_Emu() { }
|
||||||
|
|
||||||
void Gbs_Emu::unload()
|
void Gbs_Emu::unload()
|
||||||
{
|
{
|
||||||
core_.unload();
|
core_.unload();
|
||||||
Music_Emu::unload();
|
Music_Emu::unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track info
|
// Track info
|
||||||
|
|
||||||
static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
|
static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
|
||||||
{
|
{
|
||||||
GME_COPY_FIELD( h, out, game );
|
GME_COPY_FIELD( h, out, game );
|
||||||
GME_COPY_FIELD( h, out, author );
|
GME_COPY_FIELD( h, out, author );
|
||||||
GME_COPY_FIELD( h, out, copyright );
|
GME_COPY_FIELD( h, out, copyright );
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hash_gbs_file( Gbs_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
|
static void hash_gbs_file( Gbs_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
|
||||||
{
|
{
|
||||||
out.hash_( &h.vers, sizeof(h.vers) );
|
out.hash_( &h.vers, sizeof(h.vers) );
|
||||||
out.hash_( &h.track_count, sizeof(h.track_count) );
|
out.hash_( &h.track_count, sizeof(h.track_count) );
|
||||||
out.hash_( &h.first_track, sizeof(h.first_track) );
|
out.hash_( &h.first_track, sizeof(h.first_track) );
|
||||||
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
|
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
|
||||||
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
|
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
|
||||||
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
|
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
|
||||||
out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) );
|
out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) );
|
||||||
out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) );
|
out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) );
|
||||||
out.hash_( &h.timer_mode, sizeof(h.timer_mode) );
|
out.hash_( &h.timer_mode, sizeof(h.timer_mode) );
|
||||||
out.hash_( data, data_size );
|
out.hash_( data, data_size );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
|
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
|
||||||
{
|
{
|
||||||
copy_gbs_fields( header(), out );
|
copy_gbs_fields( header(), out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Gbs_File : Gme_Info_
|
struct Gbs_File : Gme_Info_
|
||||||
{
|
{
|
||||||
Gbs_Emu::header_t const* h;
|
Gbs_Emu::header_t const* h;
|
||||||
|
|
||||||
Gbs_File() { set_type( gme_gbs_type ); }
|
Gbs_File() { set_type( gme_gbs_type ); }
|
||||||
|
|
||||||
blargg_err_t load_mem_( byte const begin [], int size )
|
blargg_err_t load_mem_( byte const begin [], int size )
|
||||||
{
|
{
|
||||||
h = ( Gbs_Emu::header_t * ) begin;
|
h = ( Gbs_Emu::header_t * ) begin;
|
||||||
|
|
||||||
set_track_count( h->track_count );
|
set_track_count( h->track_count );
|
||||||
if ( !h->valid_tag() )
|
if ( !h->valid_tag() )
|
||||||
return blargg_err_file_type;
|
return blargg_err_file_type;
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t track_info_( track_info_t* out, int ) const
|
blargg_err_t track_info_( track_info_t* out, int ) const
|
||||||
{
|
{
|
||||||
copy_gbs_fields( Gbs_Emu::header_t( *h ), out );
|
copy_gbs_fields( Gbs_Emu::header_t( *h ), out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& out ) const
|
blargg_err_t hash_( Hash_Function& out ) const
|
||||||
{
|
{
|
||||||
hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
|
hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
|
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
|
||||||
static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
|
static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
|
||||||
|
|
||||||
gme_type_t_ const gme_gbs_type [1] = {{ "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }};
|
gme_type_t_ const gme_gbs_type [1] = {{ "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 }};
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
blargg_err_t Gbs_Emu::load_( Data_Reader& in )
|
blargg_err_t Gbs_Emu::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
RETURN_ERR( core_.load( in ) );
|
RETURN_ERR( core_.load( in ) );
|
||||||
set_warning( core_.warning() );
|
set_warning( core_.warning() );
|
||||||
set_track_count( header().track_count );
|
set_track_count( header().track_count );
|
||||||
set_voice_count( Gb_Apu::osc_count );
|
set_voice_count( Gb_Apu::osc_count );
|
||||||
core_.apu().volume( gain() );
|
core_.apu().volume( gain() );
|
||||||
|
|
||||||
static const char* const names [Gb_Apu::osc_count] = {
|
static const char* const names [Gb_Apu::osc_count] = {
|
||||||
"Square 1", "Square 2", "Wave", "Noise"
|
"Square 1", "Square 2", "Wave", "Noise"
|
||||||
};
|
};
|
||||||
set_voice_names( names );
|
set_voice_names( names );
|
||||||
|
|
||||||
static int const types [Gb_Apu::osc_count] = {
|
static int const types [Gb_Apu::osc_count] = {
|
||||||
wave_type+1, wave_type+2, wave_type+3, mixed_type+1
|
wave_type+1, wave_type+2, wave_type+3, mixed_type+1
|
||||||
};
|
};
|
||||||
set_voice_types( types );
|
set_voice_types( types );
|
||||||
|
|
||||||
return setup_buffer( 4194304 );
|
return setup_buffer( 4194304 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Emu::update_eq( blip_eq_t const& eq )
|
void Gbs_Emu::update_eq( blip_eq_t const& eq )
|
||||||
{
|
{
|
||||||
core_.apu().treble_eq( eq );
|
core_.apu().treble_eq( eq );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
||||||
{
|
{
|
||||||
core_.apu().set_output( i, c, l, r );
|
core_.apu().set_output( i, c, l, r );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gbs_Emu::set_tempo_( double t )
|
void Gbs_Emu::set_tempo_( double t )
|
||||||
{
|
{
|
||||||
core_.set_tempo( t );
|
core_.set_tempo( t );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Emu::start_track_( int track )
|
blargg_err_t Gbs_Emu::start_track_( int track )
|
||||||
{
|
{
|
||||||
sound_t mode = sound_hardware;
|
sound_t mode = sound_hardware;
|
||||||
if ( mode == sound_gbs )
|
if ( mode == sound_gbs )
|
||||||
mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg;
|
mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg;
|
||||||
|
|
||||||
RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) );
|
RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) );
|
||||||
|
|
||||||
// clear buffer AFTER track is started, eliminating initial click
|
// clear buffer AFTER track is started, eliminating initial click
|
||||||
return Classic_Emu::start_track_( track );
|
return Classic_Emu::start_track_( track );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
|
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
|
||||||
{
|
{
|
||||||
return core_.end_frame( duration );
|
return core_.end_frame( duration );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const
|
blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const
|
||||||
{
|
{
|
||||||
hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
|
hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
|
@ -1,63 +1,63 @@
|
||||||
// Nintendo Game Boy GBS music file emulator
|
// Nintendo Game Boy GBS music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef GBS_EMU_H
|
#ifndef GBS_EMU_H
|
||||||
#define GBS_EMU_H
|
#define GBS_EMU_H
|
||||||
|
|
||||||
#include "Classic_Emu.h"
|
#include "Classic_Emu.h"
|
||||||
#include "Gbs_Core.h"
|
#include "Gbs_Core.h"
|
||||||
|
|
||||||
class Gbs_Emu : public Classic_Emu {
|
class Gbs_Emu : public Classic_Emu {
|
||||||
public:
|
public:
|
||||||
// Equalizer profiles for Game Boy speaker and headphones
|
// Equalizer profiles for Game Boy speaker and headphones
|
||||||
static equalizer_t const handheld_eq;
|
static equalizer_t const handheld_eq;
|
||||||
static equalizer_t const headphones_eq;
|
static equalizer_t const headphones_eq;
|
||||||
static equalizer_t const cgb_eq; // Game Boy Color headphones have less bass
|
static equalizer_t const cgb_eq; // Game Boy Color headphones have less bass
|
||||||
|
|
||||||
// GBS file header (see Gbs_Core.h)
|
// GBS file header (see Gbs_Core.h)
|
||||||
typedef Gbs_Core::header_t header_t;
|
typedef Gbs_Core::header_t header_t;
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return core_.header(); }
|
header_t const& header() const { return core_.header(); }
|
||||||
|
|
||||||
// Selects which sound hardware to use. AGB hardware is cleaner than the
|
// Selects which sound hardware to use. AGB hardware is cleaner than the
|
||||||
// others. Doesn't take effect until next start_track().
|
// others. Doesn't take effect until next start_track().
|
||||||
enum sound_t {
|
enum sound_t {
|
||||||
sound_dmg = Gb_Apu::mode_dmg, // Game Boy monochrome
|
sound_dmg = Gb_Apu::mode_dmg, // Game Boy monochrome
|
||||||
sound_cgb = Gb_Apu::mode_cgb, // Game Boy Color
|
sound_cgb = Gb_Apu::mode_cgb, // Game Boy Color
|
||||||
sound_agb = Gb_Apu::mode_agb, // Game Boy Advance
|
sound_agb = Gb_Apu::mode_agb, // Game Boy Advance
|
||||||
sound_gbs // Use DMG/CGB based on GBS (default)
|
sound_gbs // Use DMG/CGB based on GBS (default)
|
||||||
};
|
};
|
||||||
void set_sound( sound_t s ) { sound_hardware = s; }
|
void set_sound( sound_t s ) { sound_hardware = s; }
|
||||||
|
|
||||||
// If true, makes APU more accurate, which results in more clicking.
|
// If true, makes APU more accurate, which results in more clicking.
|
||||||
void enable_clicking( bool enable = true ) { core_.apu().reduce_clicks( !enable ); }
|
void enable_clicking( bool enable = true ) { core_.apu().reduce_clicks( !enable ); }
|
||||||
|
|
||||||
static gme_type_t static_type() { return gme_gbs_type; }
|
static gme_type_t static_type() { return gme_gbs_type; }
|
||||||
|
|
||||||
Gbs_Core& core() { return core_; }
|
Gbs_Core& core() { return core_; }
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& ) const;
|
blargg_err_t hash_( Hash_Function& ) const;
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
public:
|
public:
|
||||||
Gbs_Emu();
|
Gbs_Emu();
|
||||||
~Gbs_Emu();
|
~Gbs_Emu();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Overrides
|
// Overrides
|
||||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual blargg_err_t start_track_( int );
|
virtual blargg_err_t start_track_( int );
|
||||||
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
||||||
virtual void set_tempo_( double );
|
virtual void set_tempo_( double );
|
||||||
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
||||||
virtual void update_eq( blip_eq_t const& );
|
virtual void update_eq( blip_eq_t const& );
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
sound_t sound_hardware;
|
sound_t sound_hardware;
|
||||||
Gbs_Core core_;
|
Gbs_Core core_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,86 +1,86 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Gme_Loader.h"
|
#include "Gme_Loader.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
void Gme_Loader::unload()
|
void Gme_Loader::unload()
|
||||||
{
|
{
|
||||||
file_begin_ = NULL;
|
file_begin_ = NULL;
|
||||||
file_end_ = NULL;
|
file_end_ = NULL;
|
||||||
file_data.clear();
|
file_data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gme_Loader::Gme_Loader()
|
Gme_Loader::Gme_Loader()
|
||||||
{
|
{
|
||||||
warning_ = NULL;
|
warning_ = NULL;
|
||||||
Gme_Loader::unload();
|
Gme_Loader::unload();
|
||||||
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
|
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
|
||||||
}
|
}
|
||||||
|
|
||||||
Gme_Loader::~Gme_Loader() { }
|
Gme_Loader::~Gme_Loader() { }
|
||||||
|
|
||||||
blargg_err_t Gme_Loader::load_mem_( byte const data [], int size )
|
blargg_err_t Gme_Loader::load_mem_( byte const data [], int size )
|
||||||
{
|
{
|
||||||
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
|
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
|
||||||
Mem_File_Reader in( data, size );
|
Mem_File_Reader in( data, size );
|
||||||
return load_( in );
|
return load_( in );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size )
|
inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size )
|
||||||
{
|
{
|
||||||
file_begin_ = data;
|
file_begin_ = data;
|
||||||
file_end_ = data + size;
|
file_end_ = data + size;
|
||||||
return load_mem_( data, size );
|
return load_mem_( data, size );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gme_Loader::load_( Data_Reader& in )
|
blargg_err_t Gme_Loader::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
RETURN_ERR( file_data.resize( in.remain() ) );
|
RETURN_ERR( file_data.resize( in.remain() ) );
|
||||||
RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
|
RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
|
||||||
return load_mem_wrapper( file_data.begin(), file_data.size() );
|
return load_mem_wrapper( file_data.begin(), file_data.size() );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gme_Loader::post_load_( blargg_err_t err )
|
blargg_err_t Gme_Loader::post_load_( blargg_err_t err )
|
||||||
{
|
{
|
||||||
if ( err )
|
if ( err )
|
||||||
{
|
{
|
||||||
unload();
|
unload();
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return post_load();
|
return post_load();
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gme_Loader::load_mem( void const* in, long size )
|
blargg_err_t Gme_Loader::load_mem( void const* in, long size )
|
||||||
{
|
{
|
||||||
pre_load();
|
pre_load();
|
||||||
return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) );
|
return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gme_Loader::load( Data_Reader& in )
|
blargg_err_t Gme_Loader::load( Data_Reader& in )
|
||||||
{
|
{
|
||||||
pre_load();
|
pre_load();
|
||||||
return post_load_( load_( in ) );
|
return post_load_( load_( in ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Gme_Loader::load_file( const char path [] )
|
blargg_err_t Gme_Loader::load_file( const char path [] )
|
||||||
{
|
{
|
||||||
pre_load();
|
pre_load();
|
||||||
GME_FILE_READER in;
|
GME_FILE_READER in;
|
||||||
RETURN_ERR( in.open( path ) );
|
RETURN_ERR( in.open( path ) );
|
||||||
return post_load_( load_( in ) );
|
return post_load_( load_( in ) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,92 +1,92 @@
|
||||||
// Common interface for loading file data from various sources
|
// Common interface for loading file data from various sources
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef GME_LOADER_H
|
#ifndef GME_LOADER_H
|
||||||
#define GME_LOADER_H
|
#define GME_LOADER_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Data_Reader.h"
|
#include "Data_Reader.h"
|
||||||
|
|
||||||
class Gme_Loader {
|
class Gme_Loader {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Each loads game music data from a file and returns an error if
|
// Each loads game music data from a file and returns an error if
|
||||||
// file is wrong type or is seriously corrupt. Minor problems are
|
// file is wrong type or is seriously corrupt. Minor problems are
|
||||||
// reported using warning().
|
// reported using warning().
|
||||||
|
|
||||||
// Loads from file on disk
|
// Loads from file on disk
|
||||||
blargg_err_t load_file( const char path [] );
|
blargg_err_t load_file( const char path [] );
|
||||||
|
|
||||||
// Loads from custom data source (see Data_Reader.h)
|
// Loads from custom data source (see Data_Reader.h)
|
||||||
blargg_err_t load( Data_Reader& );
|
blargg_err_t load( Data_Reader& );
|
||||||
|
|
||||||
// Loads from file already read into memory. Object might keep pointer to
|
// Loads from file already read into memory. Object might keep pointer to
|
||||||
// data; if it does, you MUST NOT free it until you're done with the file.
|
// data; if it does, you MUST NOT free it until you're done with the file.
|
||||||
blargg_err_t load_mem( void const* data, long size );
|
blargg_err_t load_mem( void const* data, long size );
|
||||||
|
|
||||||
// Most recent warning string, or NULL if none. Clears current warning after
|
// Most recent warning string, or NULL if none. Clears current warning after
|
||||||
// returning.
|
// returning.
|
||||||
const char* warning();
|
const char* warning();
|
||||||
|
|
||||||
// Unloads file from memory
|
// Unloads file from memory
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
virtual ~Gme_Loader();
|
virtual ~Gme_Loader();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
|
|
||||||
// File data in memory, or 0 if data was loaded with load_()
|
// File data in memory, or 0 if data was loaded with load_()
|
||||||
byte const* file_begin() const { return file_begin_; }
|
byte const* file_begin() const { return file_begin_; }
|
||||||
byte const* file_end() const { return file_end_; }
|
byte const* file_end() const { return file_end_; }
|
||||||
int file_size() const { return (int) (file_end_ - file_begin_); }
|
int file_size() const { return (int) (file_end_ - file_begin_); }
|
||||||
|
|
||||||
// Sets warning string
|
// Sets warning string
|
||||||
void set_warning( const char s [] ) { warning_ = s; }
|
void set_warning( const char s [] ) { warning_ = s; }
|
||||||
|
|
||||||
// At least one must be overridden
|
// At least one must be overridden
|
||||||
virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
|
virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
|
||||||
virtual blargg_err_t load_mem_( byte const data [], int size ); // use data in memory
|
virtual blargg_err_t load_mem_( byte const data [], int size ); // use data in memory
|
||||||
|
|
||||||
// Optionally overridden
|
// Optionally overridden
|
||||||
virtual void pre_load() { unload(); } // called before load_()/load_mem_()
|
virtual void pre_load() { unload(); } // called before load_()/load_mem_()
|
||||||
virtual blargg_err_t post_load() { return blargg_ok; } // called after load_()/load_mem_() succeeds
|
virtual blargg_err_t post_load() { return blargg_ok; } // called after load_()/load_mem_() succeeds
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Gme_Loader( const Gme_Loader& );
|
Gme_Loader( const Gme_Loader& );
|
||||||
Gme_Loader& operator = ( const Gme_Loader& );
|
Gme_Loader& operator = ( const Gme_Loader& );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Gme_Loader();
|
Gme_Loader();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
|
|
||||||
blargg_vector<byte> file_data; // used only when loading from file to load_mem_()
|
blargg_vector<byte> file_data; // used only when loading from file to load_mem_()
|
||||||
byte const* file_begin_;
|
byte const* file_begin_;
|
||||||
byte const* file_end_;
|
byte const* file_end_;
|
||||||
const char* warning_;
|
const char* warning_;
|
||||||
|
|
||||||
blargg_err_t load_mem_wrapper( byte const [], int );
|
blargg_err_t load_mem_wrapper( byte const [], int );
|
||||||
blargg_err_t post_load_( blargg_err_t err );
|
blargg_err_t post_load_( blargg_err_t err );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Files are read with GME_FILE_READER. Default supports gzip if zlib is available.
|
// Files are read with GME_FILE_READER. Default supports gzip if zlib is available.
|
||||||
#ifndef GME_FILE_READER
|
#ifndef GME_FILE_READER
|
||||||
#ifdef HAVE_ZLIB_H
|
#ifdef HAVE_ZLIB_H
|
||||||
#define GME_FILE_READER Gzip_File_Reader
|
#define GME_FILE_READER Gzip_File_Reader
|
||||||
#else
|
#else
|
||||||
#define GME_FILE_READER Std_File_Reader
|
#define GME_FILE_READER Std_File_Reader
|
||||||
#endif
|
#endif
|
||||||
#elif defined (GME_FILE_READER_INCLUDE)
|
#elif defined (GME_FILE_READER_INCLUDE)
|
||||||
#include GME_FILE_READER_INCLUDE
|
#include GME_FILE_READER_INCLUDE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline const char* Gme_Loader::warning()
|
inline const char* Gme_Loader::warning()
|
||||||
{
|
{
|
||||||
const char* s = warning_;
|
const char* s = warning_;
|
||||||
warning_ = NULL;
|
warning_ = NULL;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,361 +1,361 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Hes_Apu.h"
|
#include "Hes_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
bool const center_waves = true; // reduces asymmetry and clamping when starting notes
|
bool const center_waves = true; // reduces asymmetry and clamping when starting notes
|
||||||
|
|
||||||
Hes_Apu::Hes_Apu()
|
Hes_Apu::Hes_Apu()
|
||||||
{
|
{
|
||||||
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
||||||
{
|
{
|
||||||
osc--;
|
osc--;
|
||||||
osc->output [0] = NULL;
|
osc->output [0] = NULL;
|
||||||
osc->output [1] = NULL;
|
osc->output [1] = NULL;
|
||||||
osc->outputs [0] = NULL;
|
osc->outputs [0] = NULL;
|
||||||
osc->outputs [1] = NULL;
|
osc->outputs [1] = NULL;
|
||||||
osc->outputs [2] = NULL;
|
osc->outputs [2] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Apu::reset()
|
void Hes_Apu::reset()
|
||||||
{
|
{
|
||||||
latch = 0;
|
latch = 0;
|
||||||
balance = 0xFF;
|
balance = 0xFF;
|
||||||
|
|
||||||
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
||||||
{
|
{
|
||||||
osc--;
|
osc--;
|
||||||
memset( osc, 0, offsetof (Osc,output) );
|
memset( osc, 0, offsetof (Osc,output) );
|
||||||
osc->lfsr = 0;
|
osc->lfsr = 0;
|
||||||
osc->control = 0x40;
|
osc->control = 0x40;
|
||||||
osc->balance = 0xFF;
|
osc->balance = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only last two oscs support noise
|
// Only last two oscs support noise
|
||||||
oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR
|
oscs [osc_count - 2].lfsr = 0x200C3; // equivalent to 1 in Fibonacci LFSR
|
||||||
oscs [osc_count - 1].lfsr = 0x200C3;
|
oscs [osc_count - 1].lfsr = 0x200C3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
void Hes_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
||||||
{
|
{
|
||||||
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
|
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
|
||||||
require( !center || (center && !left && !right) || (center && left && right) );
|
require( !center || (center && !left && !right) || (center && left && right) );
|
||||||
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
|
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
|
||||||
|
|
||||||
if ( !center || !left || !right )
|
if ( !center || !left || !right )
|
||||||
{
|
{
|
||||||
left = center;
|
left = center;
|
||||||
right = center;
|
right = center;
|
||||||
}
|
}
|
||||||
|
|
||||||
Osc& o = oscs [i];
|
Osc& o = oscs [i];
|
||||||
o.outputs [0] = center;
|
o.outputs [0] = center;
|
||||||
o.outputs [1] = left;
|
o.outputs [1] = left;
|
||||||
o.outputs [2] = right;
|
o.outputs [2] = right;
|
||||||
balance_changed( o );
|
balance_changed( o );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Apu::run_osc( Blip_Synth_Fast& syn, Osc& o, blip_time_t end_time )
|
void Hes_Apu::run_osc( Blip_Synth_Fast& syn, Osc& o, blip_time_t end_time )
|
||||||
{
|
{
|
||||||
int vol0 = o.volume [0];
|
int vol0 = o.volume [0];
|
||||||
int vol1 = o.volume [1];
|
int vol1 = o.volume [1];
|
||||||
int dac = o.dac;
|
int dac = o.dac;
|
||||||
|
|
||||||
Blip_Buffer* out0 = o.output [0]; // cache often-used values
|
Blip_Buffer* out0 = o.output [0]; // cache often-used values
|
||||||
Blip_Buffer* out1 = o.output [1];
|
Blip_Buffer* out1 = o.output [1];
|
||||||
if ( !(o.control & 0x80) )
|
if ( !(o.control & 0x80) )
|
||||||
out0 = NULL;
|
out0 = NULL;
|
||||||
|
|
||||||
if ( out0 )
|
if ( out0 )
|
||||||
{
|
{
|
||||||
// Update amplitudes
|
// Update amplitudes
|
||||||
if ( out1 )
|
if ( out1 )
|
||||||
{
|
{
|
||||||
int delta = dac * vol1 - o.last_amp [1];
|
int delta = dac * vol1 - o.last_amp [1];
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
syn.offset( o.last_time, delta, out1 );
|
syn.offset( o.last_time, delta, out1 );
|
||||||
out1->set_modified();
|
out1->set_modified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int delta = dac * vol0 - o.last_amp [0];
|
int delta = dac * vol0 - o.last_amp [0];
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
syn.offset( o.last_time, delta, out0 );
|
syn.offset( o.last_time, delta, out0 );
|
||||||
out0->set_modified();
|
out0->set_modified();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't generate if silent
|
// Don't generate if silent
|
||||||
if ( !(vol0 | vol1) )
|
if ( !(vol0 | vol1) )
|
||||||
out0 = NULL;
|
out0 = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate noise
|
// Generate noise
|
||||||
int noise = 0;
|
int noise = 0;
|
||||||
if ( o.lfsr )
|
if ( o.lfsr )
|
||||||
{
|
{
|
||||||
noise = o.noise & 0x80;
|
noise = o.noise & 0x80;
|
||||||
|
|
||||||
blip_time_t time = o.last_time + o.noise_delay;
|
blip_time_t time = o.last_time + o.noise_delay;
|
||||||
if ( time < end_time )
|
if ( time < end_time )
|
||||||
{
|
{
|
||||||
int period = (~o.noise & 0x1F) * 128;
|
int period = (~o.noise & 0x1F) * 128;
|
||||||
if ( !period )
|
if ( !period )
|
||||||
period = 64;
|
period = 64;
|
||||||
|
|
||||||
if ( noise && out0 )
|
if ( noise && out0 )
|
||||||
{
|
{
|
||||||
unsigned lfsr = o.lfsr;
|
unsigned lfsr = o.lfsr;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int new_dac = -(lfsr & 1);
|
int new_dac = -(lfsr & 1);
|
||||||
lfsr = (lfsr >> 1) ^ (0x30061 & new_dac);
|
lfsr = (lfsr >> 1) ^ (0x30061 & new_dac);
|
||||||
|
|
||||||
int delta = (new_dac &= 0x1F) - dac;
|
int delta = (new_dac &= 0x1F) - dac;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
dac = new_dac;
|
dac = new_dac;
|
||||||
syn.offset( time, delta * vol0, out0 );
|
syn.offset( time, delta * vol0, out0 );
|
||||||
if ( out1 )
|
if ( out1 )
|
||||||
syn.offset( time, delta * vol1, out1 );
|
syn.offset( time, delta * vol1, out1 );
|
||||||
}
|
}
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
|
|
||||||
if ( !lfsr )
|
if ( !lfsr )
|
||||||
{
|
{
|
||||||
lfsr = 1;
|
lfsr = 1;
|
||||||
check( false );
|
check( false );
|
||||||
}
|
}
|
||||||
o.lfsr = lfsr;
|
o.lfsr = lfsr;
|
||||||
|
|
||||||
out0->set_modified();
|
out0->set_modified();
|
||||||
if ( out1 )
|
if ( out1 )
|
||||||
out1->set_modified();
|
out1->set_modified();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Maintain phase when silent
|
// Maintain phase when silent
|
||||||
int count = (end_time - time + period - 1) / period;
|
int count = (end_time - time + period - 1) / period;
|
||||||
time += count * period;
|
time += count * period;
|
||||||
|
|
||||||
// not worth it
|
// not worth it
|
||||||
//while ( count-- )
|
//while ( count-- )
|
||||||
// o.lfsr = (o.lfsr >> 1) ^ (0x30061 * (o.lfsr & 1));
|
// o.lfsr = (o.lfsr >> 1) ^ (0x30061 * (o.lfsr & 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.noise_delay = time - end_time;
|
o.noise_delay = time - end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate wave
|
// Generate wave
|
||||||
blip_time_t time = o.last_time + o.delay;
|
blip_time_t time = o.last_time + o.delay;
|
||||||
if ( time < end_time )
|
if ( time < end_time )
|
||||||
{
|
{
|
||||||
int phase = (o.phase + 1) & 0x1F; // pre-advance for optimal inner loop
|
int phase = (o.phase + 1) & 0x1F; // pre-advance for optimal inner loop
|
||||||
int period = o.period * 2;
|
int period = o.period * 2;
|
||||||
|
|
||||||
if ( period >= 14 && out0 && !((o.control & 0x40) | noise) )
|
if ( period >= 14 && out0 && !((o.control & 0x40) | noise) )
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int new_dac = o.wave [phase];
|
int new_dac = o.wave [phase];
|
||||||
phase = (phase + 1) & 0x1F;
|
phase = (phase + 1) & 0x1F;
|
||||||
int delta = new_dac - dac;
|
int delta = new_dac - dac;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
dac = new_dac;
|
dac = new_dac;
|
||||||
syn.offset( time, delta * vol0, out0 );
|
syn.offset( time, delta * vol0, out0 );
|
||||||
if ( out1 )
|
if ( out1 )
|
||||||
syn.offset( time, delta * vol1, out1 );
|
syn.offset( time, delta * vol1, out1 );
|
||||||
}
|
}
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
out0->set_modified();
|
out0->set_modified();
|
||||||
if ( out1 )
|
if ( out1 )
|
||||||
out1->set_modified();
|
out1->set_modified();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Maintain phase when silent
|
// Maintain phase when silent
|
||||||
int count = end_time - time;
|
int count = end_time - time;
|
||||||
if ( !period )
|
if ( !period )
|
||||||
period = 1;
|
period = 1;
|
||||||
count = (count + period - 1) / period;
|
count = (count + period - 1) / period;
|
||||||
|
|
||||||
phase += count; // phase will be masked below
|
phase += count; // phase will be masked below
|
||||||
time += count * period;
|
time += count * period;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Find whether phase increments even when both volumes are zero.
|
// TODO: Find whether phase increments even when both volumes are zero.
|
||||||
// CAN'T simply check for out0 being non-NULL, since it could be NULL
|
// CAN'T simply check for out0 being non-NULL, since it could be NULL
|
||||||
// if channel is muted in player, but still has non-zero volume.
|
// if channel is muted in player, but still has non-zero volume.
|
||||||
// City Hunter breaks when this check is removed.
|
// City Hunter breaks when this check is removed.
|
||||||
if ( !(o.control & 0x40) && (vol0 | vol1) )
|
if ( !(o.control & 0x40) && (vol0 | vol1) )
|
||||||
o.phase = (phase - 1) & 0x1F; // undo pre-advance
|
o.phase = (phase - 1) & 0x1F; // undo pre-advance
|
||||||
}
|
}
|
||||||
o.delay = time - end_time;
|
o.delay = time - end_time;
|
||||||
check( o.delay >= 0 );
|
check( o.delay >= 0 );
|
||||||
|
|
||||||
o.last_time = end_time;
|
o.last_time = end_time;
|
||||||
o.dac = dac;
|
o.dac = dac;
|
||||||
o.last_amp [0] = dac * vol0;
|
o.last_amp [0] = dac * vol0;
|
||||||
o.last_amp [1] = dac * vol1;
|
o.last_amp [1] = dac * vol1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Apu::balance_changed( Osc& osc )
|
void Hes_Apu::balance_changed( Osc& osc )
|
||||||
{
|
{
|
||||||
static short const log_table [32] = { // ~1.5 db per step
|
static short const log_table [32] = { // ~1.5 db per step
|
||||||
#define ENTRY( factor ) short (factor * amp_range / 31.0 + 0.5)
|
#define ENTRY( factor ) short (factor * amp_range / 31.0 + 0.5)
|
||||||
ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ),
|
ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ),
|
||||||
ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
|
ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
|
||||||
ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ),
|
ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ),
|
||||||
ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ),
|
ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ),
|
||||||
ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ),
|
ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ),
|
||||||
ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ),
|
ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ),
|
||||||
ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ),
|
ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ),
|
||||||
ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ),
|
ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ),
|
||||||
#undef ENTRY
|
#undef ENTRY
|
||||||
};
|
};
|
||||||
|
|
||||||
int vol = (osc.control & 0x1F) - 0x1E * 2;
|
int vol = (osc.control & 0x1F) - 0x1E * 2;
|
||||||
|
|
||||||
int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E);
|
int left = vol + (osc.balance >> 3 & 0x1E) + (balance >> 3 & 0x1E);
|
||||||
if ( left < 0 ) left = 0;
|
if ( left < 0 ) left = 0;
|
||||||
|
|
||||||
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
|
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
|
||||||
if ( right < 0 ) right = 0;
|
if ( right < 0 ) right = 0;
|
||||||
|
|
||||||
// optimizing for the common case of being centered also allows easy
|
// optimizing for the common case of being centered also allows easy
|
||||||
// panning using Effects_Buffer
|
// panning using Effects_Buffer
|
||||||
|
|
||||||
// Separate balance into center volume and additional on either left or right
|
// Separate balance into center volume and additional on either left or right
|
||||||
osc.output [0] = osc.outputs [0]; // center
|
osc.output [0] = osc.outputs [0]; // center
|
||||||
osc.output [1] = osc.outputs [2]; // right
|
osc.output [1] = osc.outputs [2]; // right
|
||||||
int base = log_table [left ];
|
int base = log_table [left ];
|
||||||
int side = log_table [right] - base;
|
int side = log_table [right] - base;
|
||||||
if ( side < 0 )
|
if ( side < 0 )
|
||||||
{
|
{
|
||||||
base += side;
|
base += side;
|
||||||
side = -side;
|
side = -side;
|
||||||
osc.output [1] = osc.outputs [1]; // left
|
osc.output [1] = osc.outputs [1]; // left
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize when output is far left, center, or far right
|
// Optimize when output is far left, center, or far right
|
||||||
if ( !base || osc.output [0] == osc.output [1] )
|
if ( !base || osc.output [0] == osc.output [1] )
|
||||||
{
|
{
|
||||||
base += side;
|
base += side;
|
||||||
side = 0;
|
side = 0;
|
||||||
osc.output [0] = osc.output [1];
|
osc.output [0] = osc.output [1];
|
||||||
osc.output [1] = NULL;
|
osc.output [1] = NULL;
|
||||||
osc.last_amp [1] = 0;
|
osc.last_amp [1] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( center_waves )
|
if ( center_waves )
|
||||||
{
|
{
|
||||||
// TODO: this can leave a non-zero level in a buffer (minor)
|
// TODO: this can leave a non-zero level in a buffer (minor)
|
||||||
osc.last_amp [0] += (base - osc.volume [0]) * 16;
|
osc.last_amp [0] += (base - osc.volume [0]) * 16;
|
||||||
osc.last_amp [1] += (side - osc.volume [1]) * 16;
|
osc.last_amp [1] += (side - osc.volume [1]) * 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
osc.volume [0] = base;
|
osc.volume [0] = base;
|
||||||
osc.volume [1] = side;
|
osc.volume [1] = side;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Apu::write_data( blip_time_t time, int addr, int data )
|
void Hes_Apu::write_data( blip_time_t time, int addr, int data )
|
||||||
{
|
{
|
||||||
if ( addr == 0x800 )
|
if ( addr == 0x800 )
|
||||||
{
|
{
|
||||||
latch = data & 7;
|
latch = data & 7;
|
||||||
}
|
}
|
||||||
else if ( addr == 0x801 )
|
else if ( addr == 0x801 )
|
||||||
{
|
{
|
||||||
if ( balance != data )
|
if ( balance != data )
|
||||||
{
|
{
|
||||||
balance = data;
|
balance = data;
|
||||||
|
|
||||||
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
||||||
{
|
{
|
||||||
osc--;
|
osc--;
|
||||||
run_osc( synth, *osc, time );
|
run_osc( synth, *osc, time );
|
||||||
balance_changed( *oscs );
|
balance_changed( *oscs );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( latch < osc_count )
|
else if ( latch < osc_count )
|
||||||
{
|
{
|
||||||
Osc& osc = oscs [latch];
|
Osc& osc = oscs [latch];
|
||||||
run_osc( synth, osc, time );
|
run_osc( synth, osc, time );
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x802:
|
case 0x802:
|
||||||
osc.period = (osc.period & 0xF00) | data;
|
osc.period = (osc.period & 0xF00) | data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x803:
|
case 0x803:
|
||||||
osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8);
|
osc.period = (osc.period & 0x0FF) | ((data & 0x0F) << 8);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x804:
|
case 0x804:
|
||||||
if ( osc.control & 0x40 & ~data )
|
if ( osc.control & 0x40 & ~data )
|
||||||
osc.phase = 0;
|
osc.phase = 0;
|
||||||
osc.control = data;
|
osc.control = data;
|
||||||
balance_changed( osc );
|
balance_changed( osc );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x805:
|
case 0x805:
|
||||||
osc.balance = data;
|
osc.balance = data;
|
||||||
balance_changed( osc );
|
balance_changed( osc );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x806:
|
case 0x806:
|
||||||
data &= 0x1F;
|
data &= 0x1F;
|
||||||
if ( !(osc.control & 0x40) )
|
if ( !(osc.control & 0x40) )
|
||||||
{
|
{
|
||||||
osc.wave [osc.phase] = data;
|
osc.wave [osc.phase] = data;
|
||||||
osc.phase = (osc.phase + 1) & 0x1F;
|
osc.phase = (osc.phase + 1) & 0x1F;
|
||||||
}
|
}
|
||||||
else if ( osc.control & 0x80 )
|
else if ( osc.control & 0x80 )
|
||||||
{
|
{
|
||||||
osc.dac = data;
|
osc.dac = data;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x807:
|
case 0x807:
|
||||||
osc.noise = data;
|
osc.noise = data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x809:
|
case 0x809:
|
||||||
if ( !(data & 0x80) && (data & 0x03) != 0 )
|
if ( !(data & 0x80) && (data & 0x03) != 0 )
|
||||||
dprintf( "HES LFO not supported\n" );
|
dprintf( "HES LFO not supported\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Apu::end_frame( blip_time_t end_time )
|
void Hes_Apu::end_frame( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
for ( Osc* osc = &oscs [osc_count]; osc != oscs; )
|
||||||
{
|
{
|
||||||
osc--;
|
osc--;
|
||||||
if ( end_time > osc->last_time )
|
if ( end_time > osc->last_time )
|
||||||
run_osc( synth, *osc, end_time );
|
run_osc( synth, *osc, end_time );
|
||||||
osc->last_time -= end_time;
|
osc->last_time -= end_time;
|
||||||
check( osc->last_time >= 0 );
|
check( osc->last_time >= 0 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +1,87 @@
|
||||||
// Turbo Grafx 16 (PC Engine) PSG sound chip emulator
|
// Turbo Grafx 16 (PC Engine) PSG sound chip emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef HES_APU_H
|
#ifndef HES_APU_H
|
||||||
#define HES_APU_H
|
#define HES_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Hes_Apu {
|
class Hes_Apu {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
|
|
||||||
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
|
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
|
||||||
// output is mono.
|
// output is mono.
|
||||||
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||||
|
|
||||||
// Emulates to time t, then writes data to addr
|
// Emulates to time t, then writes data to addr
|
||||||
void write_data( blip_time_t t, int addr, int data );
|
void write_data( blip_time_t t, int addr, int data );
|
||||||
|
|
||||||
// Emulates to time t, then subtracts t from the current time.
|
// Emulates to time t, then subtracts t from the current time.
|
||||||
// OK if previous write call had time slightly after t.
|
// OK if previous write call had time slightly after t.
|
||||||
void end_frame( blip_time_t t );
|
void end_frame( blip_time_t t );
|
||||||
|
|
||||||
// More features
|
// More features
|
||||||
|
|
||||||
// Resets sound chip
|
// Resets sound chip
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
enum { osc_count = 6 }; // 0 <= chan < osc_count
|
enum { osc_count = 6 }; // 0 <= chan < osc_count
|
||||||
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||||
|
|
||||||
// Sets treble equalization
|
// Sets treble equalization
|
||||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||||
|
|
||||||
// Sets overall volume, where 1.0 is normal
|
// Sets overall volume, where 1.0 is normal
|
||||||
void volume( double v ) { synth.volume( 1.8 / osc_count / amp_range * v ); }
|
void volume( double v ) { synth.volume( 1.8 / osc_count / amp_range * v ); }
|
||||||
|
|
||||||
// Registers are at io_addr to io_addr+io_size-1
|
// Registers are at io_addr to io_addr+io_size-1
|
||||||
enum { io_addr = 0x0800 };
|
enum { io_addr = 0x0800 };
|
||||||
enum { io_size = 10 };
|
enum { io_size = 10 };
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Hes_Apu();
|
Hes_Apu();
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { amp_range = 0x8000 };
|
enum { amp_range = 0x8000 };
|
||||||
struct Osc
|
struct Osc
|
||||||
{
|
{
|
||||||
byte wave [32];
|
byte wave [32];
|
||||||
int delay;
|
int delay;
|
||||||
int period;
|
int period;
|
||||||
int phase;
|
int phase;
|
||||||
|
|
||||||
int noise_delay;
|
int noise_delay;
|
||||||
byte noise;
|
byte noise;
|
||||||
unsigned lfsr;
|
unsigned lfsr;
|
||||||
|
|
||||||
byte control;
|
byte control;
|
||||||
byte balance;
|
byte balance;
|
||||||
byte dac;
|
byte dac;
|
||||||
short volume [2];
|
short volume [2];
|
||||||
int last_amp [2];
|
int last_amp [2];
|
||||||
|
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
Blip_Buffer* output [2];
|
Blip_Buffer* output [2];
|
||||||
Blip_Buffer* outputs [3];
|
Blip_Buffer* outputs [3];
|
||||||
};
|
};
|
||||||
Osc oscs [osc_count];
|
Osc oscs [osc_count];
|
||||||
int latch;
|
int latch;
|
||||||
int balance;
|
int balance;
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
void balance_changed( Osc& );
|
void balance_changed( Osc& );
|
||||||
static void run_osc( Blip_Synth_Fast&, Osc&, blip_time_t );
|
static void run_osc( Blip_Synth_Fast&, Osc&, blip_time_t );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Hes_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
inline void Hes_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
||||||
{
|
{
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
set_output( i, c, l, r );
|
set_output( i, c, l, r );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,94 +1,94 @@
|
||||||
// Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator
|
// Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef HES_APU_ADPCM_H
|
#ifndef HES_APU_ADPCM_H
|
||||||
#define HES_APU_ADPCM_H
|
#define HES_APU_ADPCM_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Hes_Apu_Adpcm {
|
class Hes_Apu_Adpcm {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
|
|
||||||
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
|
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
|
||||||
// output is mono.
|
// output is mono.
|
||||||
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||||
|
|
||||||
// Emulates to time t, then writes data to addr
|
// Emulates to time t, then writes data to addr
|
||||||
void write_data( blip_time_t t, int addr, int data );
|
void write_data( blip_time_t t, int addr, int data );
|
||||||
|
|
||||||
// Emulates to time t, then reads from addr
|
// Emulates to time t, then reads from addr
|
||||||
int read_data( blip_time_t t, int addr );
|
int read_data( blip_time_t t, int addr );
|
||||||
|
|
||||||
// Emulates to time t, then subtracts t from the current time.
|
// Emulates to time t, then subtracts t from the current time.
|
||||||
// OK if previous write call had time slightly after t.
|
// OK if previous write call had time slightly after t.
|
||||||
void end_frame( blip_time_t t );
|
void end_frame( blip_time_t t );
|
||||||
|
|
||||||
// More features
|
// More features
|
||||||
|
|
||||||
// Resets sound chip
|
// Resets sound chip
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
enum { osc_count = 1 }; // 0 <= chan < osc_count
|
enum { osc_count = 1 }; // 0 <= chan < osc_count
|
||||||
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||||
|
|
||||||
// Sets treble equalization
|
// Sets treble equalization
|
||||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||||
|
|
||||||
// Sets overall volume, where 1.0 is normal
|
// Sets overall volume, where 1.0 is normal
|
||||||
void volume( double v ) { synth.volume( 0.6 / osc_count / amp_range * v ); }
|
void volume( double v ) { synth.volume( 0.6 / osc_count / amp_range * v ); }
|
||||||
|
|
||||||
// Registers are at io_addr to io_addr+io_size-1
|
// Registers are at io_addr to io_addr+io_size-1
|
||||||
enum { io_addr = 0x1800 };
|
enum { io_addr = 0x1800 };
|
||||||
enum { io_size = 0x400 };
|
enum { io_size = 0x400 };
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Hes_Apu_Adpcm();
|
Hes_Apu_Adpcm();
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { amp_range = 2048 };
|
enum { amp_range = 2048 };
|
||||||
|
|
||||||
struct State
|
struct State
|
||||||
{
|
{
|
||||||
byte pcmbuf [0x10000];
|
byte pcmbuf [0x10000];
|
||||||
byte port [0x10];
|
byte port [0x10];
|
||||||
int ad_sample;
|
int ad_sample;
|
||||||
int ad_ref_index;
|
int ad_ref_index;
|
||||||
bool ad_low_nibble;
|
bool ad_low_nibble;
|
||||||
int freq;
|
int freq;
|
||||||
unsigned short addr;
|
unsigned short addr;
|
||||||
unsigned short writeptr;
|
unsigned short writeptr;
|
||||||
unsigned short readptr;
|
unsigned short readptr;
|
||||||
unsigned short playptr;
|
unsigned short playptr;
|
||||||
byte playflag;
|
byte playflag;
|
||||||
byte repeatflag;
|
byte repeatflag;
|
||||||
int length;
|
int length;
|
||||||
int playlength;
|
int playlength;
|
||||||
int playedsamplecount;
|
int playedsamplecount;
|
||||||
int volume;
|
int volume;
|
||||||
int fadetimer;
|
int fadetimer;
|
||||||
int fadecount;
|
int fadecount;
|
||||||
};
|
};
|
||||||
State state;
|
State state;
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
double next_timer;
|
double next_timer;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
|
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
|
|
||||||
int adpcm_decode( int );
|
int adpcm_decode( int );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Hes_Apu_Adpcm::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
inline void Hes_Apu_Adpcm::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
||||||
{
|
{
|
||||||
set_output( 0, c, l, r );
|
set_output( 0, c, l, r );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,408 +1,408 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Hes_Core.h"
|
#include "Hes_Core.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const timer_mask = 0x04;
|
int const timer_mask = 0x04;
|
||||||
int const vdp_mask = 0x02;
|
int const vdp_mask = 0x02;
|
||||||
int const i_flag_mask = 0x04;
|
int const i_flag_mask = 0x04;
|
||||||
int const unmapped = 0xFF;
|
int const unmapped = 0xFF;
|
||||||
|
|
||||||
int const period_60hz = 262 * 455; // scanlines * clocks per scanline
|
int const period_60hz = 262 * 455; // scanlines * clocks per scanline
|
||||||
|
|
||||||
Hes_Core::Hes_Core() : rom( Hes_Cpu::page_size )
|
Hes_Core::Hes_Core() : rom( Hes_Cpu::page_size )
|
||||||
{
|
{
|
||||||
timer.raw_load = 0;
|
timer.raw_load = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Hes_Core::~Hes_Core() { }
|
Hes_Core::~Hes_Core() { }
|
||||||
|
|
||||||
void Hes_Core::unload()
|
void Hes_Core::unload()
|
||||||
{
|
{
|
||||||
rom.clear();
|
rom.clear();
|
||||||
Gme_Loader::unload();
|
Gme_Loader::unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Hes_Core::header_t::valid_tag() const
|
bool Hes_Core::header_t::valid_tag() const
|
||||||
{
|
{
|
||||||
return 0 == memcmp( tag, "HESM", 4 );
|
return 0 == memcmp( tag, "HESM", 4 );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Hes_Core::load_( Data_Reader& in )
|
blargg_err_t Hes_Core::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
assert( offsetof (header_t,unused [4]) == header_t::size );
|
assert( offsetof (header_t,unused [4]) == header_t::size );
|
||||||
RETURN_ERR( rom.load( in, header_t::size, &header_, unmapped ) );
|
RETURN_ERR( rom.load( in, header_t::size, &header_, unmapped ) );
|
||||||
|
|
||||||
if ( !header_.valid_tag() )
|
if ( !header_.valid_tag() )
|
||||||
return blargg_err_file_type;
|
return blargg_err_file_type;
|
||||||
|
|
||||||
if ( header_.vers != 0 )
|
if ( header_.vers != 0 )
|
||||||
set_warning( "Unknown file version" );
|
set_warning( "Unknown file version" );
|
||||||
|
|
||||||
if ( memcmp( header_.data_tag, "DATA", 4 ) )
|
if ( memcmp( header_.data_tag, "DATA", 4 ) )
|
||||||
set_warning( "Data header missing" );
|
set_warning( "Data header missing" );
|
||||||
|
|
||||||
if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
|
if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
|
||||||
set_warning( "Unknown header data" );
|
set_warning( "Unknown header data" );
|
||||||
|
|
||||||
// File spec supports multiple blocks, but I haven't found any, and
|
// File spec supports multiple blocks, but I haven't found any, and
|
||||||
// many files have bad sizes in the only block, so it's simpler to
|
// many files have bad sizes in the only block, so it's simpler to
|
||||||
// just try to load the damn data as best as possible.
|
// just try to load the damn data as best as possible.
|
||||||
|
|
||||||
int addr = get_le32( header_.addr );
|
int addr = get_le32( header_.addr );
|
||||||
int size = get_le32( header_.data_size );
|
int size = get_le32( header_.data_size );
|
||||||
int const rom_max = 0x100000;
|
int const rom_max = 0x100000;
|
||||||
if ( (unsigned) addr >= (unsigned) rom_max )
|
if ( (unsigned) addr >= (unsigned) rom_max )
|
||||||
{
|
{
|
||||||
set_warning( "Invalid address" );
|
set_warning( "Invalid address" );
|
||||||
addr &= rom_max - 1;
|
addr &= rom_max - 1;
|
||||||
}
|
}
|
||||||
if ( (unsigned) (addr + size) > (unsigned) rom_max )
|
if ( (unsigned) (addr + size) > (unsigned) rom_max )
|
||||||
set_warning( "Invalid size" );
|
set_warning( "Invalid size" );
|
||||||
|
|
||||||
if ( size != rom.file_size() )
|
if ( size != rom.file_size() )
|
||||||
{
|
{
|
||||||
if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
|
if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
|
||||||
set_warning( "Multiple DATA not supported" );
|
set_warning( "Multiple DATA not supported" );
|
||||||
else if ( size < rom.file_size() )
|
else if ( size < rom.file_size() )
|
||||||
set_warning( "Extra file data" );
|
set_warning( "Extra file data" );
|
||||||
else
|
else
|
||||||
set_warning( "Missing file data" );
|
set_warning( "Missing file data" );
|
||||||
}
|
}
|
||||||
|
|
||||||
rom.set_addr( addr );
|
rom.set_addr( addr );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::recalc_timer_load()
|
void Hes_Core::recalc_timer_load()
|
||||||
{
|
{
|
||||||
timer.load = timer.raw_load * timer_base + 1;
|
timer.load = timer.raw_load * timer_base + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::set_tempo( double t )
|
void Hes_Core::set_tempo( double t )
|
||||||
{
|
{
|
||||||
play_period = (time_t) (period_60hz / t);
|
play_period = (time_t) (period_60hz / t);
|
||||||
timer_base = (int) (1024 / t);
|
timer_base = (int) (1024 / t);
|
||||||
recalc_timer_load();
|
recalc_timer_load();
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Hes_Core::start_track( int track )
|
blargg_err_t Hes_Core::start_track( int track )
|
||||||
{
|
{
|
||||||
memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
|
memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
|
||||||
memset( sgx, 0, sizeof sgx );
|
memset( sgx, 0, sizeof sgx );
|
||||||
|
|
||||||
apu_.reset();
|
apu_.reset();
|
||||||
adpcm_.reset();
|
adpcm_.reset();
|
||||||
cpu.reset();
|
cpu.reset();
|
||||||
|
|
||||||
for ( int i = 0; i < (int) sizeof header_.banks; i++ )
|
for ( int i = 0; i < (int) sizeof header_.banks; i++ )
|
||||||
set_mmr( i, header_.banks [i] );
|
set_mmr( i, header_.banks [i] );
|
||||||
set_mmr( cpu.page_count, 0xFF ); // unmapped beyond end of address space
|
set_mmr( cpu.page_count, 0xFF ); // unmapped beyond end of address space
|
||||||
|
|
||||||
irq.disables = timer_mask | vdp_mask;
|
irq.disables = timer_mask | vdp_mask;
|
||||||
irq.timer = cpu.future_time;
|
irq.timer = cpu.future_time;
|
||||||
irq.vdp = cpu.future_time;
|
irq.vdp = cpu.future_time;
|
||||||
|
|
||||||
timer.enabled = false;
|
timer.enabled = false;
|
||||||
timer.raw_load = 0x80;
|
timer.raw_load = 0x80;
|
||||||
timer.count = timer.load;
|
timer.count = timer.load;
|
||||||
timer.fired = false;
|
timer.fired = false;
|
||||||
timer.last_time = 0;
|
timer.last_time = 0;
|
||||||
|
|
||||||
vdp.latch = 0;
|
vdp.latch = 0;
|
||||||
vdp.control = 0;
|
vdp.control = 0;
|
||||||
vdp.next_vbl = 0;
|
vdp.next_vbl = 0;
|
||||||
|
|
||||||
ram [0x1FF] = (idle_addr - 1) >> 8;
|
ram [0x1FF] = (idle_addr - 1) >> 8;
|
||||||
ram [0x1FE] = (idle_addr - 1) & 0xFF;
|
ram [0x1FE] = (idle_addr - 1) & 0xFF;
|
||||||
cpu.r.sp = 0xFD;
|
cpu.r.sp = 0xFD;
|
||||||
cpu.r.pc = get_le16( header_.init_addr );
|
cpu.r.pc = get_le16( header_.init_addr );
|
||||||
cpu.r.a = track;
|
cpu.r.a = track;
|
||||||
|
|
||||||
recalc_timer_load();
|
recalc_timer_load();
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardware
|
// Hardware
|
||||||
|
|
||||||
void Hes_Core::run_until( time_t present )
|
void Hes_Core::run_until( time_t present )
|
||||||
{
|
{
|
||||||
while ( vdp.next_vbl < present )
|
while ( vdp.next_vbl < present )
|
||||||
vdp.next_vbl += play_period;
|
vdp.next_vbl += play_period;
|
||||||
|
|
||||||
time_t elapsed = present - timer.last_time;
|
time_t elapsed = present - timer.last_time;
|
||||||
if ( elapsed > 0 )
|
if ( elapsed > 0 )
|
||||||
{
|
{
|
||||||
if ( timer.enabled )
|
if ( timer.enabled )
|
||||||
{
|
{
|
||||||
timer.count -= elapsed;
|
timer.count -= elapsed;
|
||||||
if ( timer.count <= 0 )
|
if ( timer.count <= 0 )
|
||||||
timer.count += timer.load;
|
timer.count += timer.load;
|
||||||
}
|
}
|
||||||
timer.last_time = present;
|
timer.last_time = present;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::write_vdp( int addr, int data )
|
void Hes_Core::write_vdp( int addr, int data )
|
||||||
{
|
{
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
vdp.latch = data & 0x1F;
|
vdp.latch = data & 0x1F;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if ( vdp.latch == 5 )
|
if ( vdp.latch == 5 )
|
||||||
{
|
{
|
||||||
if ( data & 0x04 )
|
if ( data & 0x04 )
|
||||||
set_warning( "Scanline interrupt unsupported" );
|
set_warning( "Scanline interrupt unsupported" );
|
||||||
run_until( cpu.time() );
|
run_until( cpu.time() );
|
||||||
vdp.control = data;
|
vdp.control = data;
|
||||||
irq_changed();
|
irq_changed();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
|
dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
|
dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::write_mem_( addr_t addr, int data )
|
void Hes_Core::write_mem_( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
time_t time = cpu.time();
|
time_t time = cpu.time();
|
||||||
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
|
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
|
||||||
{
|
{
|
||||||
// Avoid going way past end when a long block xfer is writing to I/O space.
|
// Avoid going way past end when a long block xfer is writing to I/O space.
|
||||||
// Not a problem for other registers below because they don't write to
|
// Not a problem for other registers below because they don't write to
|
||||||
// Blip_Buffer.
|
// Blip_Buffer.
|
||||||
time_t t = min( time, cpu.end_time() + 8 );
|
time_t t = min( time, cpu.end_time() + 8 );
|
||||||
apu_.write_data( t, addr, data );
|
apu_.write_data( t, addr, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( (unsigned) (addr - adpcm_.io_addr) < adpcm_.io_size )
|
if ( (unsigned) (addr - adpcm_.io_addr) < adpcm_.io_size )
|
||||||
{
|
{
|
||||||
time_t t = min( time, cpu.end_time() + 6 );
|
time_t t = min( time, cpu.end_time() + 6 );
|
||||||
adpcm_.write_data( t, addr, data );
|
adpcm_.write_data( t, addr, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x0000:
|
case 0x0000:
|
||||||
case 0x0002:
|
case 0x0002:
|
||||||
case 0x0003:
|
case 0x0003:
|
||||||
write_vdp( addr, data );
|
write_vdp( addr, data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0x0C00: {
|
case 0x0C00: {
|
||||||
run_until( time );
|
run_until( time );
|
||||||
timer.raw_load = (data & 0x7F) + 1;
|
timer.raw_load = (data & 0x7F) + 1;
|
||||||
recalc_timer_load();
|
recalc_timer_load();
|
||||||
timer.count = timer.load;
|
timer.count = timer.load;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x0C01:
|
case 0x0C01:
|
||||||
data &= 1;
|
data &= 1;
|
||||||
if ( timer.enabled == data )
|
if ( timer.enabled == data )
|
||||||
return;
|
return;
|
||||||
run_until( time );
|
run_until( time );
|
||||||
timer.enabled = data;
|
timer.enabled = data;
|
||||||
if ( data )
|
if ( data )
|
||||||
timer.count = timer.load;
|
timer.count = timer.load;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x1402:
|
case 0x1402:
|
||||||
run_until( time );
|
run_until( time );
|
||||||
irq.disables = data;
|
irq.disables = data;
|
||||||
if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
|
if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
|
||||||
dprintf( "Int mask: $%02X\n", data );
|
dprintf( "Int mask: $%02X\n", data );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x1403:
|
case 0x1403:
|
||||||
run_until( time );
|
run_until( time );
|
||||||
if ( timer.enabled )
|
if ( timer.enabled )
|
||||||
timer.count = timer.load;
|
timer.count = timer.load;
|
||||||
timer.fired = false;
|
timer.fired = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
case 0x1000: // I/O port
|
case 0x1000: // I/O port
|
||||||
case 0x0402: // palette
|
case 0x0402: // palette
|
||||||
case 0x0403:
|
case 0x0403:
|
||||||
case 0x0404:
|
case 0x0404:
|
||||||
case 0x0405:
|
case 0x0405:
|
||||||
return;
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
dprintf( "unmapped write $%04X <- $%02X\n", addr, data );
|
dprintf( "unmapped write $%04X <- $%02X\n", addr, data );
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
irq_changed();
|
irq_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Hes_Core::read_mem_( addr_t addr )
|
int Hes_Core::read_mem_( addr_t addr )
|
||||||
{
|
{
|
||||||
time_t time = cpu.time();
|
time_t time = cpu.time();
|
||||||
addr &= cpu.page_size - 1;
|
addr &= cpu.page_size - 1;
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x0000:
|
case 0x0000:
|
||||||
if ( irq.vdp > time )
|
if ( irq.vdp > time )
|
||||||
return 0;
|
return 0;
|
||||||
irq.vdp = cpu.future_time;
|
irq.vdp = cpu.future_time;
|
||||||
run_until( time );
|
run_until( time );
|
||||||
irq_changed();
|
irq_changed();
|
||||||
return 0x20;
|
return 0x20;
|
||||||
|
|
||||||
case 0x0002:
|
case 0x0002:
|
||||||
case 0x0003:
|
case 0x0003:
|
||||||
dprintf( "VDP read not supported: %d\n", addr );
|
dprintf( "VDP read not supported: %d\n", addr );
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
case 0x0C01:
|
case 0x0C01:
|
||||||
//return timer.enabled; // TODO: remove?
|
//return timer.enabled; // TODO: remove?
|
||||||
case 0x0C00:
|
case 0x0C00:
|
||||||
run_until( time );
|
run_until( time );
|
||||||
dprintf( "Timer count read\n" );
|
dprintf( "Timer count read\n" );
|
||||||
return (unsigned) (timer.count - 1) / timer_base;
|
return (unsigned) (timer.count - 1) / timer_base;
|
||||||
|
|
||||||
case 0x1402:
|
case 0x1402:
|
||||||
return irq.disables;
|
return irq.disables;
|
||||||
|
|
||||||
case 0x1403:
|
case 0x1403:
|
||||||
{
|
{
|
||||||
int status = 0;
|
int status = 0;
|
||||||
if ( irq.timer <= time ) status |= timer_mask;
|
if ( irq.timer <= time ) status |= timer_mask;
|
||||||
if ( irq.vdp <= time ) status |= vdp_mask;
|
if ( irq.vdp <= time ) status |= vdp_mask;
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x180A:
|
case 0x180A:
|
||||||
case 0x180B:
|
case 0x180B:
|
||||||
case 0x180C:
|
case 0x180C:
|
||||||
case 0x180D:
|
case 0x180D:
|
||||||
return adpcm_.read_data( time, addr );
|
return adpcm_.read_data( time, addr );
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
case 0x1000: // I/O port
|
case 0x1000: // I/O port
|
||||||
//case 0x180C: // CD-ROM
|
//case 0x180C: // CD-ROM
|
||||||
//case 0x180D:
|
//case 0x180D:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
dprintf( "unmapped read $%04X\n", addr );
|
dprintf( "unmapped read $%04X\n", addr );
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return unmapped;
|
return unmapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::irq_changed()
|
void Hes_Core::irq_changed()
|
||||||
{
|
{
|
||||||
time_t present = cpu.time();
|
time_t present = cpu.time();
|
||||||
|
|
||||||
if ( irq.timer > present )
|
if ( irq.timer > present )
|
||||||
{
|
{
|
||||||
irq.timer = cpu.future_time;
|
irq.timer = cpu.future_time;
|
||||||
if ( timer.enabled && !timer.fired )
|
if ( timer.enabled && !timer.fired )
|
||||||
irq.timer = present + timer.count;
|
irq.timer = present + timer.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( irq.vdp > present )
|
if ( irq.vdp > present )
|
||||||
{
|
{
|
||||||
irq.vdp = cpu.future_time;
|
irq.vdp = cpu.future_time;
|
||||||
if ( vdp.control & 0x08 )
|
if ( vdp.control & 0x08 )
|
||||||
irq.vdp = vdp.next_vbl;
|
irq.vdp = vdp.next_vbl;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t time = cpu.future_time;
|
time_t time = cpu.future_time;
|
||||||
if ( !(irq.disables & timer_mask) ) time = irq.timer;
|
if ( !(irq.disables & timer_mask) ) time = irq.timer;
|
||||||
if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp );
|
if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp );
|
||||||
|
|
||||||
cpu.set_irq_time( time );
|
cpu.set_irq_time( time );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Hes_Core::cpu_done()
|
int Hes_Core::cpu_done()
|
||||||
{
|
{
|
||||||
check( cpu.time() >= cpu.end_time() ||
|
check( cpu.time() >= cpu.end_time() ||
|
||||||
(!(cpu.r.flags & i_flag_mask) && cpu.time() >= cpu.irq_time()) );
|
(!(cpu.r.flags & i_flag_mask) && cpu.time() >= cpu.irq_time()) );
|
||||||
|
|
||||||
if ( !(cpu.r.flags & i_flag_mask) )
|
if ( !(cpu.r.flags & i_flag_mask) )
|
||||||
{
|
{
|
||||||
time_t present = cpu.time();
|
time_t present = cpu.time();
|
||||||
|
|
||||||
if ( irq.timer <= present && !(irq.disables & timer_mask) )
|
if ( irq.timer <= present && !(irq.disables & timer_mask) )
|
||||||
{
|
{
|
||||||
timer.fired = true;
|
timer.fired = true;
|
||||||
irq.timer = cpu.future_time;
|
irq.timer = cpu.future_time;
|
||||||
irq_changed(); // overkill, but not worth writing custom code
|
irq_changed(); // overkill, but not worth writing custom code
|
||||||
return 0x0A;
|
return 0x0A;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
|
if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
|
||||||
{
|
{
|
||||||
// work around for bugs with music not acknowledging VDP
|
// work around for bugs with music not acknowledging VDP
|
||||||
//run_until( present );
|
//run_until( present );
|
||||||
//irq.vdp = cpu.future_time;
|
//irq.vdp = cpu.future_time;
|
||||||
//irq_changed();
|
//irq_changed();
|
||||||
return 0x08;
|
return 0x08;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void adjust_time( Hes_Core::time_t& time, Hes_Core::time_t delta )
|
static void adjust_time( Hes_Core::time_t& time, Hes_Core::time_t delta )
|
||||||
{
|
{
|
||||||
if ( time < Hes_Cpu::future_time )
|
if ( time < Hes_Cpu::future_time )
|
||||||
{
|
{
|
||||||
time -= delta;
|
time -= delta;
|
||||||
if ( time < 0 )
|
if ( time < 0 )
|
||||||
time = 0;
|
time = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Hes_Core::end_frame( time_t duration )
|
blargg_err_t Hes_Core::end_frame( time_t duration )
|
||||||
{
|
{
|
||||||
if ( run_cpu( duration ) )
|
if ( run_cpu( duration ) )
|
||||||
set_warning( "Emulation error (illegal instruction)" );
|
set_warning( "Emulation error (illegal instruction)" );
|
||||||
|
|
||||||
check( cpu.time() >= duration );
|
check( cpu.time() >= duration );
|
||||||
//check( time() - duration < 20 ); // Txx instruction could cause going way over
|
//check( time() - duration < 20 ); // Txx instruction could cause going way over
|
||||||
|
|
||||||
run_until( duration );
|
run_until( duration );
|
||||||
|
|
||||||
// end time frame
|
// end time frame
|
||||||
timer.last_time -= duration;
|
timer.last_time -= duration;
|
||||||
vdp.next_vbl -= duration;
|
vdp.next_vbl -= duration;
|
||||||
cpu.end_frame( duration );
|
cpu.end_frame( duration );
|
||||||
::adjust_time( irq.timer, duration );
|
::adjust_time( irq.timer, duration );
|
||||||
::adjust_time( irq.vdp, duration );
|
::adjust_time( irq.vdp, duration );
|
||||||
apu_.end_frame( duration );
|
apu_.end_frame( duration );
|
||||||
adpcm_.end_frame( duration );
|
adpcm_.end_frame( duration );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,120 +1,120 @@
|
||||||
// TurboGrafx-16/PC Engine HES music file emulator core
|
// TurboGrafx-16/PC Engine HES music file emulator core
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef HES_CORE_H
|
#ifndef HES_CORE_H
|
||||||
#define HES_CORE_H
|
#define HES_CORE_H
|
||||||
|
|
||||||
#include "Gme_Loader.h"
|
#include "Gme_Loader.h"
|
||||||
#include "Rom_Data.h"
|
#include "Rom_Data.h"
|
||||||
#include "Hes_Apu.h"
|
#include "Hes_Apu.h"
|
||||||
#include "Hes_Apu_Adpcm.h"
|
#include "Hes_Apu_Adpcm.h"
|
||||||
#include "Hes_Cpu.h"
|
#include "Hes_Cpu.h"
|
||||||
|
|
||||||
class Hes_Core : public Gme_Loader {
|
class Hes_Core : public Gme_Loader {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// HES file header
|
// HES file header
|
||||||
enum { info_offset = 0x20 };
|
enum { info_offset = 0x20 };
|
||||||
struct header_t
|
struct header_t
|
||||||
{
|
{
|
||||||
enum { size = 0x20 };
|
enum { size = 0x20 };
|
||||||
|
|
||||||
byte tag [4];
|
byte tag [4];
|
||||||
byte vers;
|
byte vers;
|
||||||
byte first_track;
|
byte first_track;
|
||||||
byte init_addr [2];
|
byte init_addr [2];
|
||||||
byte banks [8];
|
byte banks [8];
|
||||||
byte data_tag [4];
|
byte data_tag [4];
|
||||||
byte data_size [4];
|
byte data_size [4];
|
||||||
byte addr [4];
|
byte addr [4];
|
||||||
byte unused [4];
|
byte unused [4];
|
||||||
|
|
||||||
// True if header has valid file signature
|
// True if header has valid file signature
|
||||||
bool valid_tag() const;
|
bool valid_tag() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return header_; }
|
header_t const& header() const { return header_; }
|
||||||
|
|
||||||
// Pointer to ROM data, for getting track information from
|
// Pointer to ROM data, for getting track information from
|
||||||
byte const* data() const { return rom.begin(); }
|
byte const* data() const { return rom.begin(); }
|
||||||
int data_size() const { return rom.file_size(); }
|
int data_size() const { return rom.file_size(); }
|
||||||
|
|
||||||
// Adjusts rate play routine is called at, where 1.0 is normal.
|
// Adjusts rate play routine is called at, where 1.0 is normal.
|
||||||
// Can be changed while track is playing.
|
// Can be changed while track is playing.
|
||||||
void set_tempo( double );
|
void set_tempo( double );
|
||||||
|
|
||||||
// Sound chip
|
// Sound chip
|
||||||
Hes_Apu& apu() { return apu_; }
|
Hes_Apu& apu() { return apu_; }
|
||||||
|
|
||||||
Hes_Apu_Adpcm& adpcm() { return adpcm_; }
|
Hes_Apu_Adpcm& adpcm() { return adpcm_; }
|
||||||
|
|
||||||
// Starts track
|
// Starts track
|
||||||
blargg_err_t start_track( int );
|
blargg_err_t start_track( int );
|
||||||
|
|
||||||
// Ends time frame at time t
|
// Ends time frame at time t
|
||||||
typedef int time_t;
|
typedef int time_t;
|
||||||
blargg_err_t end_frame( time_t );
|
blargg_err_t end_frame( time_t );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Hes_Core();
|
Hes_Core();
|
||||||
~Hes_Core();
|
~Hes_Core();
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { idle_addr = 0x1FFF };
|
enum { idle_addr = 0x1FFF };
|
||||||
|
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
Hes_Cpu cpu;
|
Hes_Cpu cpu;
|
||||||
Rom_Data rom;
|
Rom_Data rom;
|
||||||
header_t header_;
|
header_t header_;
|
||||||
time_t play_period;
|
time_t play_period;
|
||||||
int timer_base;
|
int timer_base;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
time_t last_time;
|
time_t last_time;
|
||||||
int count;
|
int count;
|
||||||
int load;
|
int load;
|
||||||
int raw_load;
|
int raw_load;
|
||||||
byte enabled;
|
byte enabled;
|
||||||
byte fired;
|
byte fired;
|
||||||
} timer;
|
} timer;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
time_t next_vbl;
|
time_t next_vbl;
|
||||||
byte latch;
|
byte latch;
|
||||||
byte control;
|
byte control;
|
||||||
} vdp;
|
} vdp;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
time_t timer;
|
time_t timer;
|
||||||
time_t vdp;
|
time_t vdp;
|
||||||
byte disables;
|
byte disables;
|
||||||
} irq;
|
} irq;
|
||||||
|
|
||||||
void recalc_timer_load();
|
void recalc_timer_load();
|
||||||
|
|
||||||
// large items
|
// large items
|
||||||
byte* write_pages [Hes_Cpu::page_count + 1]; // 0 if unmapped or I/O space
|
byte* write_pages [Hes_Cpu::page_count + 1]; // 0 if unmapped or I/O space
|
||||||
Hes_Apu apu_;
|
Hes_Apu apu_;
|
||||||
Hes_Apu_Adpcm adpcm_;
|
Hes_Apu_Adpcm adpcm_;
|
||||||
byte ram [Hes_Cpu::page_size];
|
byte ram [Hes_Cpu::page_size];
|
||||||
byte sgx [3 * Hes_Cpu::page_size + Hes_Cpu::cpu_padding];
|
byte sgx [3 * Hes_Cpu::page_size + Hes_Cpu::cpu_padding];
|
||||||
|
|
||||||
void irq_changed();
|
void irq_changed();
|
||||||
void run_until( time_t );
|
void run_until( time_t );
|
||||||
bool run_cpu( time_t end );
|
bool run_cpu( time_t end );
|
||||||
int read_mem_( addr_t );
|
int read_mem_( addr_t );
|
||||||
int read_mem( addr_t );
|
int read_mem( addr_t );
|
||||||
void write_mem_( addr_t, int data );
|
void write_mem_( addr_t, int data );
|
||||||
void write_mem( addr_t, int );
|
void write_mem( addr_t, int );
|
||||||
void write_vdp( int addr, int data );
|
void write_vdp( int addr, int data );
|
||||||
void set_mmr( int reg, int bank );
|
void set_mmr( int reg, int bank );
|
||||||
int cpu_done();
|
int cpu_done();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,123 +1,123 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Hes_Cpu.h"
|
#include "Hes_Cpu.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
#include "Hes_Core.h"
|
#include "Hes_Core.h"
|
||||||
|
|
||||||
//#include "hes_cpu_log.h"
|
//#include "hes_cpu_log.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
#define PAGE HES_CPU_PAGE
|
#define PAGE HES_CPU_PAGE
|
||||||
|
|
||||||
int Hes_Core::read_mem( addr_t addr )
|
int Hes_Core::read_mem( addr_t addr )
|
||||||
{
|
{
|
||||||
check( addr < 0x10000 );
|
check( addr < 0x10000 );
|
||||||
int result = *cpu.get_code( addr );
|
int result = *cpu.get_code( addr );
|
||||||
if ( cpu.mmr [PAGE( addr )] == 0xFF )
|
if ( cpu.mmr [PAGE( addr )] == 0xFF )
|
||||||
result = read_mem_( addr );
|
result = read_mem_( addr );
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::write_mem( addr_t addr, int data )
|
void Hes_Core::write_mem( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
check( addr < 0x10000 );
|
check( addr < 0x10000 );
|
||||||
byte* out = write_pages [PAGE( addr )];
|
byte* out = write_pages [PAGE( addr )];
|
||||||
if ( out )
|
if ( out )
|
||||||
out [addr & (cpu.page_size - 1)] = data;
|
out [addr & (cpu.page_size - 1)] = data;
|
||||||
else if ( cpu.mmr [PAGE( addr )] == 0xFF )
|
else if ( cpu.mmr [PAGE( addr )] == 0xFF )
|
||||||
write_mem_( addr, data );
|
write_mem_( addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hes_Core::set_mmr( int page, int bank )
|
void Hes_Core::set_mmr( int page, int bank )
|
||||||
{
|
{
|
||||||
write_pages [page] = 0;
|
write_pages [page] = 0;
|
||||||
byte* data = rom.at_addr( bank * cpu.page_size );
|
byte* data = rom.at_addr( bank * cpu.page_size );
|
||||||
if ( bank >= 0x80 )
|
if ( bank >= 0x80 )
|
||||||
{
|
{
|
||||||
data = 0;
|
data = 0;
|
||||||
switch ( bank )
|
switch ( bank )
|
||||||
{
|
{
|
||||||
case 0xF8:
|
case 0xF8:
|
||||||
data = ram;
|
data = ram;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xF9:
|
case 0xF9:
|
||||||
case 0xFA:
|
case 0xFA:
|
||||||
case 0xFB:
|
case 0xFB:
|
||||||
data = &sgx [(bank - 0xF9) * cpu.page_size];
|
data = &sgx [(bank - 0xF9) * cpu.page_size];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if ( bank != 0xFF )
|
if ( bank != 0xFF )
|
||||||
dprintf( "Unmapped bank $%02X\n", bank );
|
dprintf( "Unmapped bank $%02X\n", bank );
|
||||||
data = rom.unmapped();
|
data = rom.unmapped();
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
write_pages [page] = data;
|
write_pages [page] = data;
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
cpu.set_mmr( page, bank, data );
|
cpu.set_mmr( page, bank, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
#define READ_FAST( addr, out ) \
|
#define READ_FAST( addr, out ) \
|
||||||
{\
|
{\
|
||||||
out = READ_CODE( addr );\
|
out = READ_CODE( addr );\
|
||||||
if ( CPU.mmr [PAGE( addr )] == 0xFF )\
|
if ( CPU.mmr [PAGE( addr )] == 0xFF )\
|
||||||
{\
|
{\
|
||||||
FLUSH_TIME();\
|
FLUSH_TIME();\
|
||||||
out = read_mem_( addr );\
|
out = read_mem_( addr );\
|
||||||
CACHE_TIME();\
|
CACHE_TIME();\
|
||||||
}\
|
}\
|
||||||
}
|
}
|
||||||
|
|
||||||
#define WRITE_FAST( addr, data ) \
|
#define WRITE_FAST( addr, data ) \
|
||||||
{\
|
{\
|
||||||
int page = PAGE( addr );\
|
int page = PAGE( addr );\
|
||||||
byte* out = write_pages [page];\
|
byte* out = write_pages [page];\
|
||||||
addr &= CPU.page_size - 1;\
|
addr &= CPU.page_size - 1;\
|
||||||
if ( out )\
|
if ( out )\
|
||||||
{\
|
{\
|
||||||
out [addr] = data;\
|
out [addr] = data;\
|
||||||
}\
|
}\
|
||||||
else if ( CPU.mmr [page] == 0xFF )\
|
else if ( CPU.mmr [page] == 0xFF )\
|
||||||
{\
|
{\
|
||||||
FLUSH_TIME();\
|
FLUSH_TIME();\
|
||||||
write_mem_( addr, data );\
|
write_mem_( addr, data );\
|
||||||
CACHE_TIME();\
|
CACHE_TIME();\
|
||||||
}\
|
}\
|
||||||
}
|
}
|
||||||
|
|
||||||
#define READ_LOW( addr ) (ram [addr])
|
#define READ_LOW( addr ) (ram [addr])
|
||||||
#define WRITE_LOW( addr, data ) (ram [addr] = data)
|
#define WRITE_LOW( addr, data ) (ram [addr] = data)
|
||||||
#define READ_MEM( addr ) read_mem( addr )
|
#define READ_MEM( addr ) read_mem( addr )
|
||||||
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
||||||
#define WRITE_VDP( addr, data ) write_vdp( addr, data )
|
#define WRITE_VDP( addr, data ) write_vdp( addr, data )
|
||||||
#define CPU_DONE( result_out ) { FLUSH_TIME(); result_out = cpu_done(); CACHE_TIME(); }
|
#define CPU_DONE( result_out ) { FLUSH_TIME(); result_out = cpu_done(); CACHE_TIME(); }
|
||||||
#define SET_MMR( reg, bank ) set_mmr( reg, bank )
|
#define SET_MMR( reg, bank ) set_mmr( reg, bank )
|
||||||
|
|
||||||
#define CPU cpu
|
#define CPU cpu
|
||||||
#define IDLE_ADDR idle_addr
|
#define IDLE_ADDR idle_addr
|
||||||
|
|
||||||
#define CPU_BEGIN \
|
#define CPU_BEGIN \
|
||||||
bool Hes_Core::run_cpu( time_t end_time )\
|
bool Hes_Core::run_cpu( time_t end_time )\
|
||||||
{\
|
{\
|
||||||
cpu.set_end_time( end_time );
|
cpu.set_end_time( end_time );
|
||||||
|
|
||||||
#include "Hes_Cpu_run.h"
|
#include "Hes_Cpu_run.h"
|
||||||
|
|
||||||
return illegal_encountered;
|
return illegal_encountered;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,139 +1,139 @@
|
||||||
// PC Engine CPU emulator for use with HES music files
|
// PC Engine CPU emulator for use with HES music files
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef HES_CPU_H
|
#ifndef HES_CPU_H
|
||||||
#define HES_CPU_H
|
#define HES_CPU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
|
|
||||||
class Hes_Cpu {
|
class Hes_Cpu {
|
||||||
public:
|
public:
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
typedef int time_t;
|
typedef int time_t;
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
enum { future_time = INT_MAX/2 + 1 };
|
enum { future_time = INT_MAX/2 + 1 };
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
enum { page_bits = 13 };
|
enum { page_bits = 13 };
|
||||||
enum { page_size = 1 << page_bits };
|
enum { page_size = 1 << page_bits };
|
||||||
enum { page_count = 0x10000 / page_size };
|
enum { page_count = 0x10000 / page_size };
|
||||||
void set_mmr( int reg, int bank, void const* code );
|
void set_mmr( int reg, int bank, void const* code );
|
||||||
|
|
||||||
byte const* get_code( addr_t );
|
byte const* get_code( addr_t );
|
||||||
|
|
||||||
// NOT kept updated during emulation.
|
// NOT kept updated during emulation.
|
||||||
struct registers_t {
|
struct registers_t {
|
||||||
BOOST::uint16_t pc;
|
BOOST::uint16_t pc;
|
||||||
byte a;
|
byte a;
|
||||||
byte x;
|
byte x;
|
||||||
byte y;
|
byte y;
|
||||||
byte flags;
|
byte flags;
|
||||||
byte sp;
|
byte sp;
|
||||||
};
|
};
|
||||||
registers_t r;
|
registers_t r;
|
||||||
|
|
||||||
// page mapping registers
|
// page mapping registers
|
||||||
byte mmr [page_count + 1];
|
byte mmr [page_count + 1];
|
||||||
|
|
||||||
// Time of beginning of next instruction to be executed
|
// Time of beginning of next instruction to be executed
|
||||||
time_t time() const { return cpu_state->time + cpu_state->base; }
|
time_t time() const { return cpu_state->time + cpu_state->base; }
|
||||||
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; }
|
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; }
|
||||||
void adjust_time( int delta ) { cpu_state->time += delta; }
|
void adjust_time( int delta ) { cpu_state->time += delta; }
|
||||||
|
|
||||||
// Clocks past end (negative if before)
|
// Clocks past end (negative if before)
|
||||||
int time_past_end() const { return cpu_state->time; }
|
int time_past_end() const { return cpu_state->time; }
|
||||||
|
|
||||||
// Time of next IRQ
|
// Time of next IRQ
|
||||||
time_t irq_time() const { return irq_time_; }
|
time_t irq_time() const { return irq_time_; }
|
||||||
void set_irq_time( time_t );
|
void set_irq_time( time_t );
|
||||||
|
|
||||||
// Emulation stops once time >= end_time
|
// Emulation stops once time >= end_time
|
||||||
time_t end_time() const { return end_time_; }
|
time_t end_time() const { return end_time_; }
|
||||||
void set_end_time( time_t );
|
void set_end_time( time_t );
|
||||||
|
|
||||||
// Subtracts t from all times
|
// Subtracts t from all times
|
||||||
void end_frame( time_t t );
|
void end_frame( time_t t );
|
||||||
|
|
||||||
// Can read this many bytes past end of a page
|
// Can read this many bytes past end of a page
|
||||||
enum { cpu_padding = 8 };
|
enum { cpu_padding = 8 };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Hes_Cpu( const Hes_Cpu& );
|
Hes_Cpu( const Hes_Cpu& );
|
||||||
Hes_Cpu& operator = ( const Hes_Cpu& );
|
Hes_Cpu& operator = ( const Hes_Cpu& );
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Hes_Cpu() { cpu_state = &cpu_state_; }
|
Hes_Cpu() { cpu_state = &cpu_state_; }
|
||||||
enum { irq_inhibit_mask = 0x04 };
|
enum { irq_inhibit_mask = 0x04 };
|
||||||
|
|
||||||
struct cpu_state_t {
|
struct cpu_state_t {
|
||||||
byte const* code_map [page_count + 1];
|
byte const* code_map [page_count + 1];
|
||||||
time_t base;
|
time_t base;
|
||||||
int time;
|
int time;
|
||||||
};
|
};
|
||||||
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
|
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
|
||||||
cpu_state_t cpu_state_;
|
cpu_state_t cpu_state_;
|
||||||
time_t irq_time_;
|
time_t irq_time_;
|
||||||
time_t end_time_;
|
time_t end_time_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_code_page( int, void const* );
|
void set_code_page( int, void const* );
|
||||||
inline void update_end_time( time_t end, time_t irq );
|
inline void update_end_time( time_t end, time_t irq );
|
||||||
};
|
};
|
||||||
|
|
||||||
#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits)
|
#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits)
|
||||||
|
|
||||||
#if BLARGG_NONPORTABLE
|
#if BLARGG_NONPORTABLE
|
||||||
#define HES_CPU_OFFSET( addr ) (addr)
|
#define HES_CPU_OFFSET( addr ) (addr)
|
||||||
#else
|
#else
|
||||||
#define HES_CPU_OFFSET( addr ) ((addr) & (Hes_Cpu::page_size - 1))
|
#define HES_CPU_OFFSET( addr ) ((addr) & (Hes_Cpu::page_size - 1))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline BOOST::uint8_t const* Hes_Cpu::get_code( addr_t addr )
|
inline BOOST::uint8_t const* Hes_Cpu::get_code( addr_t addr )
|
||||||
{
|
{
|
||||||
return cpu_state_.code_map [HES_CPU_PAGE( addr )] + HES_CPU_OFFSET( addr );
|
return cpu_state_.code_map [HES_CPU_PAGE( addr )] + HES_CPU_OFFSET( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Hes_Cpu::update_end_time( time_t end, time_t irq )
|
inline void Hes_Cpu::update_end_time( time_t end, time_t irq )
|
||||||
{
|
{
|
||||||
if ( end > irq && !(r.flags & irq_inhibit_mask) )
|
if ( end > irq && !(r.flags & irq_inhibit_mask) )
|
||||||
end = irq;
|
end = irq;
|
||||||
|
|
||||||
cpu_state->time += cpu_state->base - end;
|
cpu_state->time += cpu_state->base - end;
|
||||||
cpu_state->base = end;
|
cpu_state->base = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Hes_Cpu::set_irq_time( time_t t )
|
inline void Hes_Cpu::set_irq_time( time_t t )
|
||||||
{
|
{
|
||||||
irq_time_ = t;
|
irq_time_ = t;
|
||||||
update_end_time( end_time_, t );
|
update_end_time( end_time_, t );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Hes_Cpu::set_end_time( time_t t )
|
inline void Hes_Cpu::set_end_time( time_t t )
|
||||||
{
|
{
|
||||||
end_time_ = t;
|
end_time_ = t;
|
||||||
update_end_time( t, irq_time_ );
|
update_end_time( t, irq_time_ );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Hes_Cpu::end_frame( time_t t )
|
inline void Hes_Cpu::end_frame( time_t t )
|
||||||
{
|
{
|
||||||
assert( cpu_state == &cpu_state_ );
|
assert( cpu_state == &cpu_state_ );
|
||||||
cpu_state_.base -= t;
|
cpu_state_.base -= t;
|
||||||
if ( irq_time_ < future_time ) irq_time_ -= t;
|
if ( irq_time_ < future_time ) irq_time_ -= t;
|
||||||
if ( end_time_ < future_time ) end_time_ -= t;
|
if ( end_time_ < future_time ) end_time_ -= t;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Hes_Cpu::set_mmr( int reg, int bank, void const* code )
|
inline void Hes_Cpu::set_mmr( int reg, int bank, void const* code )
|
||||||
{
|
{
|
||||||
assert( (unsigned) reg <= page_count ); // allow page past end to be set
|
assert( (unsigned) reg <= page_count ); // allow page past end to be set
|
||||||
assert( (unsigned) bank < 0x100 );
|
assert( (unsigned) bank < 0x100 );
|
||||||
mmr [reg] = bank;
|
mmr [reg] = bank;
|
||||||
byte const* p = STATIC_CAST(byte const*,code) - HES_CPU_OFFSET( reg << page_bits );
|
byte const* p = STATIC_CAST(byte const*,code) - HES_CPU_OFFSET( reg << page_bits );
|
||||||
cpu_state->code_map [reg] = p;
|
cpu_state->code_map [reg] = p;
|
||||||
cpu_state_.code_map [reg] = p;
|
cpu_state_.code_map [reg] = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,42 +1,42 @@
|
||||||
// TurboGrafx-16/PC Engine HES music file emulator
|
// TurboGrafx-16/PC Engine HES music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef HES_EMU_H
|
#ifndef HES_EMU_H
|
||||||
#define HES_EMU_H
|
#define HES_EMU_H
|
||||||
|
|
||||||
#include "Classic_Emu.h"
|
#include "Classic_Emu.h"
|
||||||
#include "Hes_Core.h"
|
#include "Hes_Core.h"
|
||||||
|
|
||||||
class Hes_Emu : public Classic_Emu {
|
class Hes_Emu : public Classic_Emu {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static gme_type_t static_type() { return gme_hes_type; }
|
static gme_type_t static_type() { return gme_hes_type; }
|
||||||
|
|
||||||
// HES file header (see Hes_Core.h)
|
// HES file header (see Hes_Core.h)
|
||||||
typedef Hes_Core::header_t header_t;
|
typedef Hes_Core::header_t header_t;
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return core.header(); }
|
header_t const& header() const { return core.header(); }
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& ) const;
|
blargg_err_t hash_( Hash_Function& ) const;
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Hes_Emu();
|
Hes_Emu();
|
||||||
~Hes_Emu();
|
~Hes_Emu();
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual blargg_err_t start_track_( int );
|
virtual blargg_err_t start_track_( int );
|
||||||
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
||||||
virtual void set_tempo_( double );
|
virtual void set_tempo_( double );
|
||||||
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
||||||
virtual void update_eq( blip_eq_t const& );
|
virtual void update_eq( blip_eq_t const& );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Hes_Core core;
|
Hes_Core core;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -1,214 +1,214 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Kss_Core.h"
|
#include "Kss_Core.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size )
|
Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size )
|
||||||
{
|
{
|
||||||
memset( unmapped_read, 0xFF, sizeof unmapped_read );
|
memset( unmapped_read, 0xFF, sizeof unmapped_read );
|
||||||
}
|
}
|
||||||
|
|
||||||
Kss_Core::~Kss_Core() { }
|
Kss_Core::~Kss_Core() { }
|
||||||
|
|
||||||
void Kss_Core::unload()
|
void Kss_Core::unload()
|
||||||
{
|
{
|
||||||
rom.clear();
|
rom.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static blargg_err_t check_kss_header( void const* header )
|
static blargg_err_t check_kss_header( void const* header )
|
||||||
{
|
{
|
||||||
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
|
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
|
||||||
return blargg_err_file_type;
|
return blargg_err_file_type;
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Core::load_( Data_Reader& in )
|
blargg_err_t Kss_Core::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
memset( &header_, 0, sizeof header_ );
|
memset( &header_, 0, sizeof header_ );
|
||||||
assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 );
|
assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 );
|
||||||
RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) );
|
RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) );
|
||||||
|
|
||||||
RETURN_ERR( check_kss_header( header_.tag ) );
|
RETURN_ERR( check_kss_header( header_.tag ) );
|
||||||
|
|
||||||
header_.last_track [0] = 255;
|
header_.last_track [0] = 255;
|
||||||
if ( header_.tag [3] == 'C' )
|
if ( header_.tag [3] == 'C' )
|
||||||
{
|
{
|
||||||
if ( header_.extra_header )
|
if ( header_.extra_header )
|
||||||
{
|
{
|
||||||
header_.extra_header = 0;
|
header_.extra_header = 0;
|
||||||
set_warning( "Unknown data in header" );
|
set_warning( "Unknown data in header" );
|
||||||
}
|
}
|
||||||
if ( header_.device_flags & ~0x0F )
|
if ( header_.device_flags & ~0x0F )
|
||||||
{
|
{
|
||||||
header_.device_flags &= 0x0F;
|
header_.device_flags &= 0x0F;
|
||||||
set_warning( "Unknown data in header" );
|
set_warning( "Unknown data in header" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( header_.extra_header )
|
else if ( header_.extra_header )
|
||||||
{
|
{
|
||||||
if ( header_.extra_header != header_.ext_size )
|
if ( header_.extra_header != header_.ext_size )
|
||||||
{
|
{
|
||||||
header_.extra_header = 0;
|
header_.extra_header = 0;
|
||||||
set_warning( "Invalid extra_header_size" );
|
set_warning( "Invalid extra_header_size" );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
memcpy( header_.data_size, rom.begin(), header_.ext_size );
|
memcpy( header_.data_size, rom.begin(), header_.ext_size );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
{
|
{
|
||||||
int ram_mode = header_.device_flags & 0x84; // MSX
|
int ram_mode = header_.device_flags & 0x84; // MSX
|
||||||
if ( header_.device_flags & 0x02 ) // SMS
|
if ( header_.device_flags & 0x02 ) // SMS
|
||||||
ram_mode = (header_.device_flags & 0x88);
|
ram_mode = (header_.device_flags & 0x88);
|
||||||
|
|
||||||
if ( ram_mode )
|
if ( ram_mode )
|
||||||
dprintf( "RAM not supported\n" ); // TODO: support
|
dprintf( "RAM not supported\n" ); // TODO: support
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Core::jsr( byte const (&addr) [2] )
|
void Kss_Core::jsr( byte const (&addr) [2] )
|
||||||
{
|
{
|
||||||
ram [--cpu.r.sp] = idle_addr >> 8;
|
ram [--cpu.r.sp] = idle_addr >> 8;
|
||||||
ram [--cpu.r.sp] = idle_addr & 0xFF;
|
ram [--cpu.r.sp] = idle_addr & 0xFF;
|
||||||
cpu.r.pc = get_le16( addr );
|
cpu.r.pc = get_le16( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Core::start_track( int track )
|
blargg_err_t Kss_Core::start_track( int track )
|
||||||
{
|
{
|
||||||
memset( ram, 0xC9, 0x4000 );
|
memset( ram, 0xC9, 0x4000 );
|
||||||
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
|
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
|
||||||
|
|
||||||
// copy driver code to lo RAM
|
// copy driver code to lo RAM
|
||||||
static byte const bios [] = {
|
static byte const bios [] = {
|
||||||
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
|
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
|
||||||
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
|
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
|
||||||
};
|
};
|
||||||
static byte const vectors [] = {
|
static byte const vectors [] = {
|
||||||
0xC3, 0x01, 0x00, // $0093: WRTPSG vector
|
0xC3, 0x01, 0x00, // $0093: WRTPSG vector
|
||||||
0xC3, 0x09, 0x00, // $0096: RDPSG vector
|
0xC3, 0x09, 0x00, // $0096: RDPSG vector
|
||||||
};
|
};
|
||||||
memcpy( ram + 0x01, bios, sizeof bios );
|
memcpy( ram + 0x01, bios, sizeof bios );
|
||||||
memcpy( ram + 0x93, vectors, sizeof vectors );
|
memcpy( ram + 0x93, vectors, sizeof vectors );
|
||||||
|
|
||||||
// copy non-banked data into RAM
|
// copy non-banked data into RAM
|
||||||
int load_addr = get_le16( header_.load_addr );
|
int load_addr = get_le16( header_.load_addr );
|
||||||
int orig_load_size = get_le16( header_.load_size );
|
int orig_load_size = get_le16( header_.load_size );
|
||||||
int load_size = min( orig_load_size, rom.file_size() );
|
int load_size = min( orig_load_size, rom.file_size() );
|
||||||
load_size = min( load_size, (int) mem_size - load_addr );
|
load_size = min( load_size, (int) mem_size - load_addr );
|
||||||
if ( load_size != orig_load_size )
|
if ( load_size != orig_load_size )
|
||||||
set_warning( "Excessive data size" );
|
set_warning( "Excessive data size" );
|
||||||
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
|
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
|
||||||
|
|
||||||
rom.set_addr( -load_size - header_.extra_header );
|
rom.set_addr( -load_size - header_.extra_header );
|
||||||
|
|
||||||
// check available bank data
|
// check available bank data
|
||||||
int const bank_size = this->bank_size();
|
int const bank_size = this->bank_size();
|
||||||
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
|
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
|
||||||
bank_count = header_.bank_mode & 0x7F;
|
bank_count = header_.bank_mode & 0x7F;
|
||||||
if ( bank_count > max_banks )
|
if ( bank_count > max_banks )
|
||||||
{
|
{
|
||||||
bank_count = max_banks;
|
bank_count = max_banks;
|
||||||
set_warning( "Bank data missing" );
|
set_warning( "Bank data missing" );
|
||||||
}
|
}
|
||||||
//dprintf( "load_size : $%X\n", load_size );
|
//dprintf( "load_size : $%X\n", load_size );
|
||||||
//dprintf( "bank_size : $%X\n", bank_size );
|
//dprintf( "bank_size : $%X\n", bank_size );
|
||||||
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
|
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
|
||||||
|
|
||||||
ram [idle_addr] = 0xFF;
|
ram [idle_addr] = 0xFF;
|
||||||
cpu.reset( unmapped_write, unmapped_read );
|
cpu.reset( unmapped_write, unmapped_read );
|
||||||
cpu.map_mem( 0, mem_size, ram, ram );
|
cpu.map_mem( 0, mem_size, ram, ram );
|
||||||
|
|
||||||
cpu.r.sp = 0xF380;
|
cpu.r.sp = 0xF380;
|
||||||
cpu.r.b.a = track;
|
cpu.r.b.a = track;
|
||||||
cpu.r.b.h = 0;
|
cpu.r.b.h = 0;
|
||||||
next_play = play_period;
|
next_play = play_period;
|
||||||
gain_updated = false;
|
gain_updated = false;
|
||||||
jsr( header_.init_addr );
|
jsr( header_.init_addr );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Core::set_bank( int logical, int physical )
|
void Kss_Core::set_bank( int logical, int physical )
|
||||||
{
|
{
|
||||||
int const bank_size = this->bank_size();
|
int const bank_size = this->bank_size();
|
||||||
|
|
||||||
int addr = 0x8000;
|
int addr = 0x8000;
|
||||||
if ( logical && bank_size == 8 * 1024 )
|
if ( logical && bank_size == 8 * 1024 )
|
||||||
addr = 0xA000;
|
addr = 0xA000;
|
||||||
|
|
||||||
physical -= header_.first_bank;
|
physical -= header_.first_bank;
|
||||||
if ( (unsigned) physical >= (unsigned) bank_count )
|
if ( (unsigned) physical >= (unsigned) bank_count )
|
||||||
{
|
{
|
||||||
byte* data = ram + addr;
|
byte* data = ram + addr;
|
||||||
cpu.map_mem( addr, bank_size, data, data );
|
cpu.map_mem( addr, bank_size, data, data );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int phys = physical * bank_size;
|
int phys = physical * bank_size;
|
||||||
for ( int offset = 0; offset < bank_size; offset += cpu.page_size )
|
for ( int offset = 0; offset < bank_size; offset += cpu.page_size )
|
||||||
cpu.map_mem( addr + offset, cpu.page_size,
|
cpu.map_mem( addr + offset, cpu.page_size,
|
||||||
unmapped_write, rom.at_addr( phys + offset ) );
|
unmapped_write, rom.at_addr( phys + offset ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Core::cpu_out( time_t, addr_t addr, int data )
|
void Kss_Core::cpu_out( time_t, addr_t addr, int data )
|
||||||
{
|
{
|
||||||
dprintf( "OUT $%04X,$%02X\n", addr, data );
|
dprintf( "OUT $%04X,$%02X\n", addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Kss_Core::cpu_in( time_t, addr_t addr )
|
int Kss_Core::cpu_in( time_t, addr_t addr )
|
||||||
{
|
{
|
||||||
dprintf( "IN $%04X\n", addr );
|
dprintf( "IN $%04X\n", addr );
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Core::end_frame( time_t end )
|
blargg_err_t Kss_Core::end_frame( time_t end )
|
||||||
{
|
{
|
||||||
while ( cpu.time() < end )
|
while ( cpu.time() < end )
|
||||||
{
|
{
|
||||||
time_t next = min( end, next_play );
|
time_t next = min( end, next_play );
|
||||||
run_cpu( next );
|
run_cpu( next );
|
||||||
if ( cpu.r.pc == idle_addr )
|
if ( cpu.r.pc == idle_addr )
|
||||||
cpu.set_time( next );
|
cpu.set_time( next );
|
||||||
|
|
||||||
if ( cpu.time() >= next_play )
|
if ( cpu.time() >= next_play )
|
||||||
{
|
{
|
||||||
next_play += play_period;
|
next_play += play_period;
|
||||||
if ( cpu.r.pc == idle_addr )
|
if ( cpu.r.pc == idle_addr )
|
||||||
{
|
{
|
||||||
if ( !gain_updated )
|
if ( !gain_updated )
|
||||||
{
|
{
|
||||||
gain_updated = true;
|
gain_updated = true;
|
||||||
update_gain();
|
update_gain();
|
||||||
}
|
}
|
||||||
|
|
||||||
jsr( header_.play_addr );
|
jsr( header_.play_addr );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next_play -= end;
|
next_play -= end;
|
||||||
check( next_play >= 0 );
|
check( next_play >= 0 );
|
||||||
cpu.adjust_time( -end );
|
cpu.adjust_time( -end );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +1,100 @@
|
||||||
// MSX computer KSS music file emulator
|
// MSX computer KSS music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef KSS_CORE_H
|
#ifndef KSS_CORE_H
|
||||||
#define KSS_CORE_H
|
#define KSS_CORE_H
|
||||||
|
|
||||||
#include "Gme_Loader.h"
|
#include "Gme_Loader.h"
|
||||||
#include "Rom_Data.h"
|
#include "Rom_Data.h"
|
||||||
#include "Z80_Cpu.h"
|
#include "Z80_Cpu.h"
|
||||||
|
|
||||||
class Kss_Core : public Gme_Loader {
|
class Kss_Core : public Gme_Loader {
|
||||||
public:
|
public:
|
||||||
// KSS file header
|
// KSS file header
|
||||||
struct header_t
|
struct header_t
|
||||||
{
|
{
|
||||||
enum { size = 0x20 };
|
enum { size = 0x20 };
|
||||||
enum { base_size = 0x10 };
|
enum { base_size = 0x10 };
|
||||||
enum { ext_size = size - base_size };
|
enum { ext_size = size - base_size };
|
||||||
|
|
||||||
byte tag [4];
|
byte tag [4];
|
||||||
byte load_addr [2];
|
byte load_addr [2];
|
||||||
byte load_size [2];
|
byte load_size [2];
|
||||||
byte init_addr [2];
|
byte init_addr [2];
|
||||||
byte play_addr [2];
|
byte play_addr [2];
|
||||||
byte first_bank;
|
byte first_bank;
|
||||||
byte bank_mode;
|
byte bank_mode;
|
||||||
byte extra_header;
|
byte extra_header;
|
||||||
byte device_flags;
|
byte device_flags;
|
||||||
|
|
||||||
// KSSX extended data, if extra_header==0x10
|
// KSSX extended data, if extra_header==0x10
|
||||||
byte data_size [4];
|
byte data_size [4];
|
||||||
byte unused [4];
|
byte unused [4];
|
||||||
byte first_track [2];
|
byte first_track [2];
|
||||||
byte last_track [2]; // if no extended data, we set this to 0xFF
|
byte last_track [2]; // if no extended data, we set this to 0xFF
|
||||||
byte psg_vol;
|
byte psg_vol;
|
||||||
byte scc_vol;
|
byte scc_vol;
|
||||||
byte msx_music_vol;
|
byte msx_music_vol;
|
||||||
byte msx_audio_vol;
|
byte msx_audio_vol;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return header_; }
|
header_t const& header() const { return header_; }
|
||||||
|
|
||||||
// ROM data
|
// ROM data
|
||||||
Rom_Data const& rom_() const { return rom; }
|
Rom_Data const& rom_() const { return rom; }
|
||||||
|
|
||||||
typedef int time_t;
|
typedef int time_t;
|
||||||
void set_play_period( time_t p ) { play_period = p; }
|
void set_play_period( time_t p ) { play_period = p; }
|
||||||
|
|
||||||
blargg_err_t start_track( int );
|
blargg_err_t start_track( int );
|
||||||
|
|
||||||
blargg_err_t end_frame( time_t );
|
blargg_err_t end_frame( time_t );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef Z80_Cpu Kss_Cpu;
|
typedef Z80_Cpu Kss_Cpu;
|
||||||
Kss_Cpu cpu;
|
Kss_Cpu cpu;
|
||||||
|
|
||||||
void set_bank( int logical, int physical );
|
void set_bank( int logical, int physical );
|
||||||
|
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
virtual void cpu_write( addr_t, int ) = 0;
|
virtual void cpu_write( addr_t, int ) = 0;
|
||||||
virtual int cpu_in( time_t, addr_t );
|
virtual int cpu_in( time_t, addr_t );
|
||||||
virtual void cpu_out( time_t, addr_t, int );
|
virtual void cpu_out( time_t, addr_t, int );
|
||||||
|
|
||||||
// Called after one frame of emulation
|
// Called after one frame of emulation
|
||||||
virtual void update_gain() = 0;
|
virtual void update_gain() = 0;
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Kss_Core();
|
Kss_Core();
|
||||||
virtual ~Kss_Core();
|
virtual ~Kss_Core();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { idle_addr = 0xFFFF };
|
enum { idle_addr = 0xFFFF };
|
||||||
|
|
||||||
Rom_Data rom;
|
Rom_Data rom;
|
||||||
header_t header_;
|
header_t header_;
|
||||||
bool gain_updated;
|
bool gain_updated;
|
||||||
int bank_count;
|
int bank_count;
|
||||||
time_t play_period;
|
time_t play_period;
|
||||||
time_t next_play;
|
time_t next_play;
|
||||||
|
|
||||||
// large items
|
// large items
|
||||||
enum { mem_size = 0x10000 };
|
enum { mem_size = 0x10000 };
|
||||||
byte ram [mem_size + Kss_Cpu::cpu_padding];
|
byte ram [mem_size + Kss_Cpu::cpu_padding];
|
||||||
byte unmapped_read [0x100]; // TODO: why isn't this page_size?
|
byte unmapped_read [0x100]; // TODO: why isn't this page_size?
|
||||||
// because CPU can't read beyond this in last page? or because it will spill into unmapped_write?
|
// because CPU can't read beyond this in last page? or because it will spill into unmapped_write?
|
||||||
|
|
||||||
byte unmapped_write [Kss_Cpu::page_size];
|
byte unmapped_write [Kss_Cpu::page_size];
|
||||||
|
|
||||||
int bank_size() const { return (16 * 1024) >> (header_.bank_mode >> 7 & 1); }
|
int bank_size() const { return (16 * 1024) >> (header_.bank_mode >> 7 & 1); }
|
||||||
bool run_cpu( time_t end );
|
bool run_cpu( time_t end );
|
||||||
void jsr( byte const (&addr) [2] );
|
void jsr( byte const (&addr) [2] );
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,493 +1,493 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Kss_Emu.h"
|
#include "Kss_Emu.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
#define IF_PTR( ptr ) if ( ptr ) (ptr)
|
#define IF_PTR( ptr ) if ( ptr ) (ptr)
|
||||||
|
|
||||||
int const clock_rate = 3579545;
|
int const clock_rate = 3579545;
|
||||||
|
|
||||||
#define FOR_EACH_APU( macro )\
|
#define FOR_EACH_APU( macro )\
|
||||||
{\
|
{\
|
||||||
macro( sms.psg );\
|
macro( sms.psg );\
|
||||||
macro( sms.fm );\
|
macro( sms.fm );\
|
||||||
macro( msx.psg );\
|
macro( msx.psg );\
|
||||||
macro( msx.scc );\
|
macro( msx.scc );\
|
||||||
macro( msx.music );\
|
macro( msx.music );\
|
||||||
macro( msx.audio );\
|
macro( msx.audio );\
|
||||||
}
|
}
|
||||||
|
|
||||||
Kss_Emu::Kss_Emu() :
|
Kss_Emu::Kss_Emu() :
|
||||||
core( this )
|
core( this )
|
||||||
{
|
{
|
||||||
#define ACTION( apu ) { core.apu = NULL; }
|
#define ACTION( apu ) { core.apu = NULL; }
|
||||||
FOR_EACH_APU( ACTION );
|
FOR_EACH_APU( ACTION );
|
||||||
#undef ACTION
|
#undef ACTION
|
||||||
|
|
||||||
set_type( gme_kss_type );
|
set_type( gme_kss_type );
|
||||||
}
|
}
|
||||||
|
|
||||||
Kss_Emu::~Kss_Emu()
|
Kss_Emu::~Kss_Emu()
|
||||||
{
|
{
|
||||||
unload();
|
unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Kss_Emu::Core::unload()
|
inline void Kss_Emu::Core::unload()
|
||||||
{
|
{
|
||||||
#define ACTION( ptr ) { delete (ptr); (ptr) = 0; }
|
#define ACTION( ptr ) { delete (ptr); (ptr) = 0; }
|
||||||
FOR_EACH_APU( ACTION );
|
FOR_EACH_APU( ACTION );
|
||||||
#undef ACTION
|
#undef ACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::unload()
|
void Kss_Emu::unload()
|
||||||
{
|
{
|
||||||
core.unload();
|
core.unload();
|
||||||
Classic_Emu::unload();
|
Classic_Emu::unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track info
|
// Track info
|
||||||
|
|
||||||
static void copy_kss_fields( Kss_Core::header_t const& h, track_info_t* out )
|
static void copy_kss_fields( Kss_Core::header_t const& h, track_info_t* out )
|
||||||
{
|
{
|
||||||
const char* system = "MSX";
|
const char* system = "MSX";
|
||||||
|
|
||||||
if ( h.device_flags & 0x02 )
|
if ( h.device_flags & 0x02 )
|
||||||
{
|
{
|
||||||
system = "Sega Master System";
|
system = "Sega Master System";
|
||||||
if ( h.device_flags & 0x04 )
|
if ( h.device_flags & 0x04 )
|
||||||
system = "Game Gear";
|
system = "Game Gear";
|
||||||
|
|
||||||
if ( h.device_flags & 0x01 )
|
if ( h.device_flags & 0x01 )
|
||||||
system = "Sega Mark III";
|
system = "Sega Mark III";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ( h.device_flags & 0x09 )
|
if ( h.device_flags & 0x09 )
|
||||||
system = "MSX + FM Sound";
|
system = "MSX + FM Sound";
|
||||||
}
|
}
|
||||||
Gme_File::copy_field_( out->system, system );
|
Gme_File::copy_field_( out->system, system );
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hash_kss_file( Kss_Core::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
|
static void hash_kss_file( Kss_Core::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
|
||||||
{
|
{
|
||||||
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
|
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
|
||||||
out.hash_( &h.load_size[0], sizeof(h.load_size) );
|
out.hash_( &h.load_size[0], sizeof(h.load_size) );
|
||||||
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
|
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
|
||||||
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
|
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
|
||||||
out.hash_( &h.first_bank, sizeof(h.first_bank) );
|
out.hash_( &h.first_bank, sizeof(h.first_bank) );
|
||||||
out.hash_( &h.bank_mode, sizeof(h.bank_mode) );
|
out.hash_( &h.bank_mode, sizeof(h.bank_mode) );
|
||||||
out.hash_( &h.extra_header, sizeof(h.extra_header) );
|
out.hash_( &h.extra_header, sizeof(h.extra_header) );
|
||||||
out.hash_( &h.device_flags, sizeof(h.device_flags) );
|
out.hash_( &h.device_flags, sizeof(h.device_flags) );
|
||||||
|
|
||||||
out.hash_( data, data_size );
|
out.hash_( data, data_size );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const
|
blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const
|
||||||
{
|
{
|
||||||
copy_kss_fields( header(), out );
|
copy_kss_fields( header(), out );
|
||||||
// TODO: remove
|
// TODO: remove
|
||||||
//if ( msx.music ) strcpy( out->system, "msxmusic" );
|
//if ( msx.music ) strcpy( out->system, "msxmusic" );
|
||||||
//if ( msx.audio ) strcpy( out->system, "msxaudio" );
|
//if ( msx.audio ) strcpy( out->system, "msxaudio" );
|
||||||
//if ( sms.fm ) strcpy( out->system, "fmunit" );
|
//if ( sms.fm ) strcpy( out->system, "fmunit" );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static blargg_err_t check_kss_header( void const* header )
|
static blargg_err_t check_kss_header( void const* header )
|
||||||
{
|
{
|
||||||
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
|
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
|
||||||
return blargg_err_file_type;
|
return blargg_err_file_type;
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Kss_File : Gme_Info_
|
struct Kss_File : Gme_Info_
|
||||||
{
|
{
|
||||||
Kss_Emu::header_t const* header_;
|
Kss_Emu::header_t const* header_;
|
||||||
|
|
||||||
Kss_File() { set_type( gme_kss_type ); }
|
Kss_File() { set_type( gme_kss_type ); }
|
||||||
|
|
||||||
blargg_err_t load_mem_( byte const begin [], int size )
|
blargg_err_t load_mem_( byte const begin [], int size )
|
||||||
{
|
{
|
||||||
header_ = ( Kss_Emu::header_t const* ) begin;
|
header_ = ( Kss_Emu::header_t const* ) begin;
|
||||||
|
|
||||||
if ( header_->tag [3] == 'X' && header_->extra_header == 0x10 )
|
if ( header_->tag [3] == 'X' && header_->extra_header == 0x10 )
|
||||||
set_track_count( get_le16( header_->last_track ) + 1 );
|
set_track_count( get_le16( header_->last_track ) + 1 );
|
||||||
|
|
||||||
return check_kss_header( header_ );
|
return check_kss_header( header_ );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t track_info_( track_info_t* out, int ) const
|
blargg_err_t track_info_( track_info_t* out, int ) const
|
||||||
{
|
{
|
||||||
copy_kss_fields( *header_, out );
|
copy_kss_fields( *header_, out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& out ) const
|
blargg_err_t hash_( Hash_Function& out ) const
|
||||||
{
|
{
|
||||||
hash_kss_file( *header_, file_begin() + Kss_Core::header_t::base_size, file_end() - file_begin() - Kss_Core::header_t::base_size, out );
|
hash_kss_file( *header_, file_begin() + Kss_Core::header_t::base_size, file_end() - file_begin() - Kss_Core::header_t::base_size, out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; }
|
static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; }
|
||||||
static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; }
|
static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; }
|
||||||
|
|
||||||
gme_type_t_ const gme_kss_type [1] = {{
|
gme_type_t_ const gme_kss_type [1] = {{
|
||||||
"MSX",
|
"MSX",
|
||||||
256,
|
256,
|
||||||
&new_kss_emu,
|
&new_kss_emu,
|
||||||
&new_kss_file,
|
&new_kss_file,
|
||||||
"KSS",
|
"KSS",
|
||||||
0x03
|
0x03
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
|
|
||||||
void Kss_Emu::Core::update_gain_()
|
void Kss_Emu::Core::update_gain_()
|
||||||
{
|
{
|
||||||
double g = emu.gain();
|
double g = emu.gain();
|
||||||
if ( msx.music || msx.audio || sms.fm )
|
if ( msx.music || msx.audio || sms.fm )
|
||||||
{
|
{
|
||||||
g *= 0.3;
|
g *= 0.3;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
g *= 1.2;
|
g *= 1.2;
|
||||||
if ( scc_accessed )
|
if ( scc_accessed )
|
||||||
g *= 1.4;
|
g *= 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define ACTION( apu ) IF_PTR( apu )->volume( g )
|
#define ACTION( apu ) IF_PTR( apu )->volume( g )
|
||||||
FOR_EACH_APU( ACTION );
|
FOR_EACH_APU( ACTION );
|
||||||
#undef ACTION
|
#undef ACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
static blargg_err_t new_opl_apu( Opl_Apu::type_t type, Opl_Apu** out )
|
static blargg_err_t new_opl_apu( Opl_Apu::type_t type, Opl_Apu** out )
|
||||||
{
|
{
|
||||||
check( !*out );
|
check( !*out );
|
||||||
CHECK_ALLOC( *out = BLARGG_NEW( Opl_Apu ) );
|
CHECK_ALLOC( *out = BLARGG_NEW( Opl_Apu ) );
|
||||||
blip_time_t const period = 72;
|
blip_time_t const period = 72;
|
||||||
int const rate = clock_rate / period;
|
int const rate = clock_rate / period;
|
||||||
return (*out)->init( rate * period, rate, period, type );
|
return (*out)->init( rate * period, rate, period, type );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Emu::load_( Data_Reader& in )
|
blargg_err_t Kss_Emu::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
RETURN_ERR( core.load( in ) );
|
RETURN_ERR( core.load( in ) );
|
||||||
set_warning( core.warning() );
|
set_warning( core.warning() );
|
||||||
|
|
||||||
set_track_count( get_le16( header().last_track ) + 1 );
|
set_track_count( get_le16( header().last_track ) + 1 );
|
||||||
|
|
||||||
core.scc_enabled = false;
|
core.scc_enabled = false;
|
||||||
if ( header().device_flags & 0x02 ) // Sega Master System
|
if ( header().device_flags & 0x02 ) // Sega Master System
|
||||||
{
|
{
|
||||||
int const osc_count = Sms_Apu::osc_count + Opl_Apu::osc_count;
|
int const osc_count = Sms_Apu::osc_count + Opl_Apu::osc_count;
|
||||||
static const char* const names [osc_count] = {
|
static const char* const names [osc_count] = {
|
||||||
"Square 1", "Square 2", "Square 3", "Noise", "FM"
|
"Square 1", "Square 2", "Square 3", "Noise", "FM"
|
||||||
};
|
};
|
||||||
set_voice_names( names );
|
set_voice_names( names );
|
||||||
|
|
||||||
static int const types [osc_count] = {
|
static int const types [osc_count] = {
|
||||||
wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0
|
wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0
|
||||||
};
|
};
|
||||||
set_voice_types( types );
|
set_voice_types( types );
|
||||||
|
|
||||||
// sms.psg
|
// sms.psg
|
||||||
set_voice_count( Sms_Apu::osc_count );
|
set_voice_count( Sms_Apu::osc_count );
|
||||||
check( !core.sms.psg );
|
check( !core.sms.psg );
|
||||||
CHECK_ALLOC( core.sms.psg = BLARGG_NEW Sms_Apu );
|
CHECK_ALLOC( core.sms.psg = BLARGG_NEW Sms_Apu );
|
||||||
|
|
||||||
// sms.fm
|
// sms.fm
|
||||||
if ( header().device_flags & 0x01 )
|
if ( header().device_flags & 0x01 )
|
||||||
{
|
{
|
||||||
set_voice_count( osc_count );
|
set_voice_count( osc_count );
|
||||||
RETURN_ERR( new_opl_apu( Opl_Apu::type_smsfmunit, &core.sms.fm ) );
|
RETURN_ERR( new_opl_apu( Opl_Apu::type_smsfmunit, &core.sms.fm ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else // MSX
|
else // MSX
|
||||||
{
|
{
|
||||||
int const osc_count = Ay_Apu::osc_count + Opl_Apu::osc_count;
|
int const osc_count = Ay_Apu::osc_count + Opl_Apu::osc_count;
|
||||||
static const char* const names [osc_count] = {
|
static const char* const names [osc_count] = {
|
||||||
"Square 1", "Square 2", "Square 3", "FM"
|
"Square 1", "Square 2", "Square 3", "FM"
|
||||||
};
|
};
|
||||||
set_voice_names( names );
|
set_voice_names( names );
|
||||||
|
|
||||||
static int const types [osc_count] = {
|
static int const types [osc_count] = {
|
||||||
wave_type+1, wave_type+3, wave_type+2, wave_type+0
|
wave_type+1, wave_type+3, wave_type+2, wave_type+0
|
||||||
};
|
};
|
||||||
set_voice_types( types );
|
set_voice_types( types );
|
||||||
|
|
||||||
// msx.psg
|
// msx.psg
|
||||||
set_voice_count( Ay_Apu::osc_count );
|
set_voice_count( Ay_Apu::osc_count );
|
||||||
check( !core.msx.psg );
|
check( !core.msx.psg );
|
||||||
CHECK_ALLOC( core.msx.psg = BLARGG_NEW Ay_Apu );
|
CHECK_ALLOC( core.msx.psg = BLARGG_NEW Ay_Apu );
|
||||||
|
|
||||||
if ( header().device_flags & 0x10 )
|
if ( header().device_flags & 0x10 )
|
||||||
set_warning( "MSX stereo not supported" );
|
set_warning( "MSX stereo not supported" );
|
||||||
|
|
||||||
// msx.music
|
// msx.music
|
||||||
if ( header().device_flags & 0x01 )
|
if ( header().device_flags & 0x01 )
|
||||||
{
|
{
|
||||||
set_voice_count( osc_count );
|
set_voice_count( osc_count );
|
||||||
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxmusic, &core.msx.music ) );
|
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxmusic, &core.msx.music ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// msx.audio
|
// msx.audio
|
||||||
if ( header().device_flags & 0x08 )
|
if ( header().device_flags & 0x08 )
|
||||||
{
|
{
|
||||||
set_voice_count( osc_count );
|
set_voice_count( osc_count );
|
||||||
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxaudio, &core.msx.audio ) );
|
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxaudio, &core.msx.audio ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !(header().device_flags & 0x80) )
|
if ( !(header().device_flags & 0x80) )
|
||||||
{
|
{
|
||||||
if ( !(header().device_flags & 0x84) )
|
if ( !(header().device_flags & 0x84) )
|
||||||
core.scc_enabled = core.scc_enabled_true;
|
core.scc_enabled = core.scc_enabled_true;
|
||||||
|
|
||||||
// msx.scc
|
// msx.scc
|
||||||
check( !core.msx.scc );
|
check( !core.msx.scc );
|
||||||
CHECK_ALLOC( core.msx.scc = BLARGG_NEW Scc_Apu );
|
CHECK_ALLOC( core.msx.scc = BLARGG_NEW Scc_Apu );
|
||||||
|
|
||||||
int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count;
|
int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count;
|
||||||
static const char* const names [osc_count] = {
|
static const char* const names [osc_count] = {
|
||||||
"Square 1", "Square 2", "Square 3",
|
"Square 1", "Square 2", "Square 3",
|
||||||
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5"
|
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5"
|
||||||
};
|
};
|
||||||
set_voice_names( names );
|
set_voice_names( names );
|
||||||
|
|
||||||
static int const types [osc_count] = {
|
static int const types [osc_count] = {
|
||||||
wave_type+1, wave_type+3, wave_type+2,
|
wave_type+1, wave_type+3, wave_type+2,
|
||||||
wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7,
|
wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7,
|
||||||
};
|
};
|
||||||
set_voice_types( types );
|
set_voice_types( types );
|
||||||
|
|
||||||
set_voice_count( osc_count );
|
set_voice_count( osc_count );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_silence_lookahead( 6 );
|
set_silence_lookahead( 6 );
|
||||||
if ( core.sms.fm || core.msx.music || core.msx.audio )
|
if ( core.sms.fm || core.msx.music || core.msx.audio )
|
||||||
{
|
{
|
||||||
if ( !Opl_Apu::supported() )
|
if ( !Opl_Apu::supported() )
|
||||||
set_warning( "FM sound not supported" );
|
set_warning( "FM sound not supported" );
|
||||||
else
|
else
|
||||||
set_silence_lookahead( 3 ); // Opl_Apu is really slow
|
set_silence_lookahead( 3 ); // Opl_Apu is really slow
|
||||||
}
|
}
|
||||||
|
|
||||||
return setup_buffer( ::clock_rate );
|
return setup_buffer( ::clock_rate );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::update_eq( blip_eq_t const& eq )
|
void Kss_Emu::update_eq( blip_eq_t const& eq )
|
||||||
{
|
{
|
||||||
#define ACTION( apu ) IF_PTR( core.apu )->treble_eq( eq )
|
#define ACTION( apu ) IF_PTR( core.apu )->treble_eq( eq )
|
||||||
FOR_EACH_APU( ACTION );
|
FOR_EACH_APU( ACTION );
|
||||||
#undef ACTION
|
#undef ACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
||||||
{
|
{
|
||||||
if ( core.sms.psg ) // Sega Master System
|
if ( core.sms.psg ) // Sega Master System
|
||||||
{
|
{
|
||||||
i -= core.sms.psg->osc_count;
|
i -= core.sms.psg->osc_count;
|
||||||
if ( i < 0 )
|
if ( i < 0 )
|
||||||
{
|
{
|
||||||
core.sms.psg->set_output( i + core.sms.psg->osc_count, center, left, right );
|
core.sms.psg->set_output( i + core.sms.psg->osc_count, center, left, right );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( core.sms.fm && i < core.sms.fm->osc_count )
|
if ( core.sms.fm && i < core.sms.fm->osc_count )
|
||||||
core.sms.fm->set_output( i, center, NULL, NULL );
|
core.sms.fm->set_output( i, center, NULL, NULL );
|
||||||
}
|
}
|
||||||
else if ( core.msx.psg ) // MSX
|
else if ( core.msx.psg ) // MSX
|
||||||
{
|
{
|
||||||
i -= core.msx.psg->osc_count;
|
i -= core.msx.psg->osc_count;
|
||||||
if ( i < 0 )
|
if ( i < 0 )
|
||||||
{
|
{
|
||||||
core.msx.psg->set_output( i + core.msx.psg->osc_count, center );
|
core.msx.psg->set_output( i + core.msx.psg->osc_count, center );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( core.msx.scc && i < core.msx.scc->osc_count ) core.msx.scc ->set_output( i, center );
|
if ( core.msx.scc && i < core.msx.scc->osc_count ) core.msx.scc ->set_output( i, center );
|
||||||
if ( core.msx.music && i < core.msx.music->osc_count ) core.msx.music->set_output( i, center, NULL, NULL );
|
if ( core.msx.music && i < core.msx.music->osc_count ) core.msx.music->set_output( i, center, NULL, NULL );
|
||||||
if ( core.msx.audio && i < core.msx.audio->osc_count ) core.msx.audio->set_output( i, center, NULL, NULL );
|
if ( core.msx.audio && i < core.msx.audio->osc_count ) core.msx.audio->set_output( i, center, NULL, NULL );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::set_tempo_( double t )
|
void Kss_Emu::set_tempo_( double t )
|
||||||
{
|
{
|
||||||
int period = (header().device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60);
|
int period = (header().device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60);
|
||||||
core.set_play_period( (Kss_Core::time_t) (period / t) );
|
core.set_play_period( (Kss_Core::time_t) (period / t) );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Emu::start_track_( int track )
|
blargg_err_t Kss_Emu::start_track_( int track )
|
||||||
{
|
{
|
||||||
RETURN_ERR( Classic_Emu::start_track_( track ) );
|
RETURN_ERR( Classic_Emu::start_track_( track ) );
|
||||||
|
|
||||||
#define ACTION( apu ) IF_PTR( core.apu )->reset()
|
#define ACTION( apu ) IF_PTR( core.apu )->reset()
|
||||||
FOR_EACH_APU( ACTION );
|
FOR_EACH_APU( ACTION );
|
||||||
#undef ACTION
|
#undef ACTION
|
||||||
|
|
||||||
core.scc_accessed = false;
|
core.scc_accessed = false;
|
||||||
core.update_gain_();
|
core.update_gain_();
|
||||||
|
|
||||||
return core.start_track( track );
|
return core.start_track( track );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::Core::cpu_write_( addr_t addr, int data )
|
void Kss_Emu::Core::cpu_write_( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
// TODO: SCC+ support
|
// TODO: SCC+ support
|
||||||
|
|
||||||
data &= 0xFF;
|
data &= 0xFF;
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x9000:
|
case 0x9000:
|
||||||
set_bank( 0, data );
|
set_bank( 0, data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0xB000:
|
case 0xB000:
|
||||||
set_bank( 1, data );
|
set_bank( 1, data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0xBFFE: // selects between mapping areas (we just always enable both)
|
case 0xBFFE: // selects between mapping areas (we just always enable both)
|
||||||
if ( data == 0 || data == 0x20 )
|
if ( data == 0 || data == 0x20 )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int scc_addr = (addr & 0xDFFF) - 0x9800;
|
int scc_addr = (addr & 0xDFFF) - 0x9800;
|
||||||
if ( (unsigned) scc_addr < 0xB0 && msx.scc )
|
if ( (unsigned) scc_addr < 0xB0 && msx.scc )
|
||||||
{
|
{
|
||||||
scc_accessed = true;
|
scc_accessed = true;
|
||||||
//if ( (unsigned) (scc_addr - 0x90) < 0x10 )
|
//if ( (unsigned) (scc_addr - 0x90) < 0x10 )
|
||||||
// scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F
|
// scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F
|
||||||
if ( scc_addr < Scc_Apu::reg_count )
|
if ( scc_addr < Scc_Apu::reg_count )
|
||||||
msx.scc->write( cpu.time(), addr, data );
|
msx.scc->write( cpu.time(), addr, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dprintf( "LD ($%04X),$%02X\n", addr, data );
|
dprintf( "LD ($%04X),$%02X\n", addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::Core::cpu_write( addr_t addr, int data )
|
void Kss_Emu::Core::cpu_write( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
*cpu.write( addr ) = data;
|
*cpu.write( addr ) = data;
|
||||||
if ( (addr & scc_enabled) == 0x8000 )
|
if ( (addr & scc_enabled) == 0x8000 )
|
||||||
cpu_write_( addr, data );
|
cpu_write_( addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::Core::cpu_out( time_t time, addr_t addr, int data )
|
void Kss_Emu::Core::cpu_out( time_t time, addr_t addr, int data )
|
||||||
{
|
{
|
||||||
data &= 0xFF;
|
data &= 0xFF;
|
||||||
switch ( addr & 0xFF )
|
switch ( addr & 0xFF )
|
||||||
{
|
{
|
||||||
case 0xA0:
|
case 0xA0:
|
||||||
if ( msx.psg )
|
if ( msx.psg )
|
||||||
msx.psg->write_addr( data );
|
msx.psg->write_addr( data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0xA1:
|
case 0xA1:
|
||||||
if ( msx.psg )
|
if ( msx.psg )
|
||||||
msx.psg->write_data( time, data );
|
msx.psg->write_data( time, data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0x06:
|
case 0x06:
|
||||||
if ( sms.psg && (header().device_flags & 0x04) )
|
if ( sms.psg && (header().device_flags & 0x04) )
|
||||||
{
|
{
|
||||||
sms.psg->write_ggstereo( time, data );
|
sms.psg->write_ggstereo( time, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x7E:
|
case 0x7E:
|
||||||
case 0x7F:
|
case 0x7F:
|
||||||
if ( sms.psg )
|
if ( sms.psg )
|
||||||
{
|
{
|
||||||
sms.psg->write_data( time, data );
|
sms.psg->write_data( time, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#define OPL_WRITE_HANDLER( base, opl )\
|
#define OPL_WRITE_HANDLER( base, opl )\
|
||||||
case base : if ( opl ) { opl->write_addr( data ); return; } break;\
|
case base : if ( opl ) { opl->write_addr( data ); return; } break;\
|
||||||
case base+1: if ( opl ) { opl->write_data( time, data ); return; } break;
|
case base+1: if ( opl ) { opl->write_data( time, data ); return; } break;
|
||||||
|
|
||||||
OPL_WRITE_HANDLER( 0x7C, msx.music )
|
OPL_WRITE_HANDLER( 0x7C, msx.music )
|
||||||
OPL_WRITE_HANDLER( 0xC0, msx.audio )
|
OPL_WRITE_HANDLER( 0xC0, msx.audio )
|
||||||
OPL_WRITE_HANDLER( 0xF0, sms.fm )
|
OPL_WRITE_HANDLER( 0xF0, sms.fm )
|
||||||
|
|
||||||
case 0xFE:
|
case 0xFE:
|
||||||
set_bank( 0, data );
|
set_bank( 0, data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
case 0xA8: // PPI
|
case 0xA8: // PPI
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Kss_Core::cpu_out( time, addr, data );
|
Kss_Core::cpu_out( time, addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Kss_Emu::Core::cpu_in( time_t time, addr_t addr )
|
int Kss_Emu::Core::cpu_in( time_t time, addr_t addr )
|
||||||
{
|
{
|
||||||
switch ( addr & 0xFF )
|
switch ( addr & 0xFF )
|
||||||
{
|
{
|
||||||
case 0xC0:
|
case 0xC0:
|
||||||
case 0xC1:
|
case 0xC1:
|
||||||
if ( msx.audio )
|
if ( msx.audio )
|
||||||
return msx.audio->read( time, addr & 1 );
|
return msx.audio->read( time, addr & 1 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xA2:
|
case 0xA2:
|
||||||
if ( msx.psg )
|
if ( msx.psg )
|
||||||
return msx.psg->read();
|
return msx.psg->read();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
case 0xA8: // PPI
|
case 0xA8: // PPI
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return Kss_Core::cpu_in( time, addr );
|
return Kss_Core::cpu_in( time, addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kss_Emu::Core::update_gain()
|
void Kss_Emu::Core::update_gain()
|
||||||
{
|
{
|
||||||
if ( scc_accessed )
|
if ( scc_accessed )
|
||||||
{
|
{
|
||||||
dprintf( "SCC accessed\n" );
|
dprintf( "SCC accessed\n" );
|
||||||
update_gain_();
|
update_gain_();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int )
|
blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int )
|
||||||
{
|
{
|
||||||
RETURN_ERR( core.end_frame( duration ) );
|
RETURN_ERR( core.end_frame( duration ) );
|
||||||
|
|
||||||
#define ACTION( apu ) IF_PTR( core.apu )->end_frame( duration )
|
#define ACTION( apu ) IF_PTR( core.apu )->end_frame( duration )
|
||||||
FOR_EACH_APU( ACTION );
|
FOR_EACH_APU( ACTION );
|
||||||
#undef ACTION
|
#undef ACTION
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Kss_Emu::hash_( Hash_Function& out ) const
|
blargg_err_t Kss_Emu::hash_( Hash_Function& out ) const
|
||||||
{
|
{
|
||||||
hash_kss_file( header(), core.rom_().begin(), core.rom_().file_size(), out );
|
hash_kss_file( header(), core.rom_().begin(), core.rom_().file_size(), out );
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
|
@ -1,79 +1,79 @@
|
||||||
// MSX computer KSS music file emulator
|
// MSX computer KSS music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef KSS_EMU_H
|
#ifndef KSS_EMU_H
|
||||||
#define KSS_EMU_H
|
#define KSS_EMU_H
|
||||||
|
|
||||||
#include "Classic_Emu.h"
|
#include "Classic_Emu.h"
|
||||||
#include "Kss_Core.h"
|
#include "Kss_Core.h"
|
||||||
#include "Kss_Scc_Apu.h"
|
#include "Kss_Scc_Apu.h"
|
||||||
#include "Sms_Apu.h"
|
#include "Sms_Apu.h"
|
||||||
#include "Ay_Apu.h"
|
#include "Ay_Apu.h"
|
||||||
#include "Opl_Apu.h"
|
#include "Opl_Apu.h"
|
||||||
|
|
||||||
class Kss_Emu : public Classic_Emu {
|
class Kss_Emu : public Classic_Emu {
|
||||||
public:
|
public:
|
||||||
// KSS file header (see Kss_Core.h)
|
// KSS file header (see Kss_Core.h)
|
||||||
typedef Kss_Core::header_t header_t;
|
typedef Kss_Core::header_t header_t;
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return core.header(); }
|
header_t const& header() const { return core.header(); }
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& ) const;
|
blargg_err_t hash_( Hash_Function& ) const;
|
||||||
|
|
||||||
static gme_type_t static_type() { return gme_kss_type; }
|
static gme_type_t static_type() { return gme_kss_type; }
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Kss_Emu();
|
Kss_Emu();
|
||||||
~Kss_Emu();
|
~Kss_Emu();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual blargg_err_t start_track_( int );
|
virtual blargg_err_t start_track_( int );
|
||||||
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
||||||
virtual void set_tempo_( double );
|
virtual void set_tempo_( double );
|
||||||
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
||||||
virtual void update_eq( blip_eq_t const& );
|
virtual void update_eq( blip_eq_t const& );
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Core;
|
struct Core;
|
||||||
friend struct Core;
|
friend struct Core;
|
||||||
struct Core : Kss_Core {
|
struct Core : Kss_Core {
|
||||||
Kss_Emu& emu;
|
Kss_Emu& emu;
|
||||||
|
|
||||||
// detection of tunes that use SCC so they can be made louder
|
// detection of tunes that use SCC so they can be made louder
|
||||||
bool scc_accessed;
|
bool scc_accessed;
|
||||||
|
|
||||||
enum { scc_enabled_true = 0xC000 };
|
enum { scc_enabled_true = 0xC000 };
|
||||||
unsigned scc_enabled; // 0 or 0xC000
|
unsigned scc_enabled; // 0 or 0xC000
|
||||||
int ay_latch;
|
int ay_latch;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Sms_Apu* psg;
|
Sms_Apu* psg;
|
||||||
Opl_Apu* fm;
|
Opl_Apu* fm;
|
||||||
} sms;
|
} sms;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Ay_Apu* psg;
|
Ay_Apu* psg;
|
||||||
Scc_Apu* scc;
|
Scc_Apu* scc;
|
||||||
Opl_Apu* music;
|
Opl_Apu* music;
|
||||||
Opl_Apu* audio;
|
Opl_Apu* audio;
|
||||||
} msx;
|
} msx;
|
||||||
|
|
||||||
Core( Kss_Emu* e ) : emu( *e ) { }
|
Core( Kss_Emu* e ) : emu( *e ) { }
|
||||||
|
|
||||||
virtual void cpu_write( addr_t, int );
|
virtual void cpu_write( addr_t, int );
|
||||||
virtual int cpu_in( time_t, addr_t );
|
virtual int cpu_in( time_t, addr_t );
|
||||||
virtual void cpu_out( time_t, addr_t, int );
|
virtual void cpu_out( time_t, addr_t, int );
|
||||||
virtual void update_gain();
|
virtual void update_gain();
|
||||||
|
|
||||||
void cpu_write_( addr_t addr, int data );
|
void cpu_write_( addr_t addr, int data );
|
||||||
void update_gain_();
|
void update_gain_();
|
||||||
void unload();
|
void unload();
|
||||||
} core;
|
} core;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,124 +1,124 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Kss_Scc_Apu.h"
|
#include "Kss_Scc_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
// Tones above this frequency are treated as disabled tone at half volume.
|
// Tones above this frequency are treated as disabled tone at half volume.
|
||||||
// Power of two is more efficient (avoids division).
|
// Power of two is more efficient (avoids division).
|
||||||
int const inaudible_freq = 16384;
|
int const inaudible_freq = 16384;
|
||||||
|
|
||||||
int const wave_size = 0x20;
|
int const wave_size = 0x20;
|
||||||
|
|
||||||
void Scc_Apu::set_output( Blip_Buffer* buf )
|
void Scc_Apu::set_output( Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
set_output( i, buf );
|
set_output( i, buf );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scc_Apu::volume( double v )
|
void Scc_Apu::volume( double v )
|
||||||
{
|
{
|
||||||
synth.volume( 0.43 / osc_count / amp_range * v );
|
synth.volume( 0.43 / osc_count / amp_range * v );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scc_Apu::reset()
|
void Scc_Apu::reset()
|
||||||
{
|
{
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
|
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
memset( &oscs [i], 0, offsetof (osc_t,output) );
|
memset( &oscs [i], 0, offsetof (osc_t,output) );
|
||||||
|
|
||||||
memset( regs, 0, sizeof regs );
|
memset( regs, 0, sizeof regs );
|
||||||
}
|
}
|
||||||
|
|
||||||
Scc_Apu::Scc_Apu()
|
Scc_Apu::Scc_Apu()
|
||||||
{
|
{
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scc_Apu::run_until( blip_time_t end_time )
|
void Scc_Apu::run_until( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
for ( int index = 0; index < osc_count; index++ )
|
for ( int index = 0; index < osc_count; index++ )
|
||||||
{
|
{
|
||||||
osc_t& osc = oscs [index];
|
osc_t& osc = oscs [index];
|
||||||
|
|
||||||
Blip_Buffer* const output = osc.output;
|
Blip_Buffer* const output = osc.output;
|
||||||
if ( !output )
|
if ( !output )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 +
|
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 +
|
||||||
regs [0xA0 + index * 2] + 1;
|
regs [0xA0 + index * 2] + 1;
|
||||||
int volume = 0;
|
int volume = 0;
|
||||||
if ( regs [0xAF] & (1 << index) )
|
if ( regs [0xAF] & (1 << index) )
|
||||||
{
|
{
|
||||||
blip_time_t inaudible_period = (unsigned) (output->clock_rate() +
|
blip_time_t inaudible_period = (unsigned) (output->clock_rate() +
|
||||||
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16);
|
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16);
|
||||||
if ( period > inaudible_period )
|
if ( period > inaudible_period )
|
||||||
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15);
|
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size;
|
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size;
|
||||||
/*if ( index == osc_count - 1 )
|
/*if ( index == osc_count - 1 )
|
||||||
wave -= wave_size; // last two oscs share same wave RAM*/
|
wave -= wave_size; // last two oscs share same wave RAM*/
|
||||||
|
|
||||||
{
|
{
|
||||||
int delta = wave [osc.phase] * volume - osc.last_amp;
|
int delta = wave [osc.phase] * volume - osc.last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
osc.last_amp += delta;
|
osc.last_amp += delta;
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
synth.offset( last_time, delta, output );
|
synth.offset( last_time, delta, output );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blip_time_t time = last_time + osc.delay;
|
blip_time_t time = last_time + osc.delay;
|
||||||
if ( time < end_time )
|
if ( time < end_time )
|
||||||
{
|
{
|
||||||
int phase = osc.phase;
|
int phase = osc.phase;
|
||||||
if ( !volume )
|
if ( !volume )
|
||||||
{
|
{
|
||||||
// maintain phase
|
// maintain phase
|
||||||
int count = (end_time - time + period - 1) / period;
|
int count = (end_time - time + period - 1) / period;
|
||||||
phase += count; // will be masked below
|
phase += count; // will be masked below
|
||||||
time += count * period;
|
time += count * period;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int last_wave = wave [phase];
|
int last_wave = wave [phase];
|
||||||
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
|
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int delta = wave [phase] - last_wave;
|
int delta = wave [phase] - last_wave;
|
||||||
phase = (phase + 1) & (wave_size - 1);
|
phase = (phase + 1) & (wave_size - 1);
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
last_wave += delta;
|
last_wave += delta;
|
||||||
synth.offset_inline( time, delta * volume, output );
|
synth.offset_inline( time, delta * volume, output );
|
||||||
}
|
}
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
|
|
||||||
osc.last_amp = last_wave * volume;
|
osc.last_amp = last_wave * volume;
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
phase--; // undo pre-advance
|
phase--; // undo pre-advance
|
||||||
}
|
}
|
||||||
osc.phase = phase & (wave_size - 1);
|
osc.phase = phase & (wave_size - 1);
|
||||||
}
|
}
|
||||||
osc.delay = time - end_time;
|
osc.delay = time - end_time;
|
||||||
}
|
}
|
||||||
last_time = end_time;
|
last_time = end_time;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,111 +1,111 @@
|
||||||
// Konami SCC sound chip emulator
|
// Konami SCC sound chip emulator
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef KSS_SCC_APU_H
|
#ifndef KSS_SCC_APU_H
|
||||||
#define KSS_SCC_APU_H
|
#define KSS_SCC_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Scc_Apu {
|
class Scc_Apu {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
|
|
||||||
// Sets buffer to generate sound into, or 0 to mute.
|
// Sets buffer to generate sound into, or 0 to mute.
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
|
|
||||||
// Emulates to time t, then writes data to reg
|
// Emulates to time t, then writes data to reg
|
||||||
enum { reg_count = 0xB0 }; // 0 <= reg < reg_count
|
enum { reg_count = 0xB0 }; // 0 <= reg < reg_count
|
||||||
void write( blip_time_t t, int reg, int data );
|
void write( blip_time_t t, int reg, int data );
|
||||||
|
|
||||||
// Emulates to time t, then subtracts t from the current time.
|
// Emulates to time t, then subtracts t from the current time.
|
||||||
// OK if previous write call had time slightly after t.
|
// OK if previous write call had time slightly after t.
|
||||||
void end_frame( blip_time_t t );
|
void end_frame( blip_time_t t );
|
||||||
|
|
||||||
// More features
|
// More features
|
||||||
|
|
||||||
// Resets sound chip
|
// Resets sound chip
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
enum { osc_count = 5 };
|
enum { osc_count = 5 };
|
||||||
void set_output( int chan, Blip_Buffer* );
|
void set_output( int chan, Blip_Buffer* );
|
||||||
|
|
||||||
// Set overall volume, where 1.0 is normal
|
// Set overall volume, where 1.0 is normal
|
||||||
void volume( double );
|
void volume( double );
|
||||||
|
|
||||||
// Set treble equalization
|
// Set treble equalization
|
||||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Scc_Apu( const Scc_Apu& );
|
Scc_Apu( const Scc_Apu& );
|
||||||
Scc_Apu& operator = ( const Scc_Apu& );
|
Scc_Apu& operator = ( const Scc_Apu& );
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Scc_Apu();
|
Scc_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { amp_range = 0x8000 };
|
enum { amp_range = 0x8000 };
|
||||||
struct osc_t
|
struct osc_t
|
||||||
{
|
{
|
||||||
int delay;
|
int delay;
|
||||||
int phase;
|
int phase;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
};
|
};
|
||||||
osc_t oscs [osc_count];
|
osc_t oscs [osc_count];
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
unsigned char regs [reg_count];
|
unsigned char regs [reg_count];
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Scc_Apu::set_output( int index, Blip_Buffer* b )
|
inline void Scc_Apu::set_output( int index, Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
assert( (unsigned) index < osc_count );
|
assert( (unsigned) index < osc_count );
|
||||||
oscs [index].output = b;
|
oscs [index].output = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Scc_Apu::write( blip_time_t time, int addr, int data )
|
inline void Scc_Apu::write( blip_time_t time, int addr, int data )
|
||||||
{
|
{
|
||||||
//assert( (unsigned) addr < reg_count );
|
//assert( (unsigned) addr < reg_count );
|
||||||
assert( ( addr >= 0x9800 && addr <= 0x988F ) || ( addr >= 0xB800 && addr <= 0xB8AF ) );
|
assert( ( addr >= 0x9800 && addr <= 0x988F ) || ( addr >= 0xB800 && addr <= 0xB8AF ) );
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
addr -= 0x9800;
|
addr -= 0x9800;
|
||||||
if ( ( unsigned ) addr < 0x90 )
|
if ( ( unsigned ) addr < 0x90 )
|
||||||
{
|
{
|
||||||
if ( ( unsigned ) addr < 0x60 )
|
if ( ( unsigned ) addr < 0x60 )
|
||||||
regs [addr] = data;
|
regs [addr] = data;
|
||||||
else if ( ( unsigned ) addr < 0x80 )
|
else if ( ( unsigned ) addr < 0x80 )
|
||||||
{
|
{
|
||||||
regs [addr] = regs[addr + 0x20] = data;
|
regs [addr] = regs[addr + 0x20] = data;
|
||||||
}
|
}
|
||||||
else if ( ( unsigned ) addr < 0x90 )
|
else if ( ( unsigned ) addr < 0x90 )
|
||||||
{
|
{
|
||||||
regs [addr + 0x20] = data;
|
regs [addr + 0x20] = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
addr -= 0xB800 - 0x9800;
|
addr -= 0xB800 - 0x9800;
|
||||||
if ( ( unsigned ) addr < 0xB0 )
|
if ( ( unsigned ) addr < 0xB0 )
|
||||||
regs [addr] = data;
|
regs [addr] = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Scc_Apu::end_frame( blip_time_t end_time )
|
inline void Scc_Apu::end_frame( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
if ( end_time > last_time )
|
if ( end_time > last_time )
|
||||||
run_until( end_time );
|
run_until( end_time );
|
||||||
|
|
||||||
last_time -= end_time;
|
last_time -= end_time;
|
||||||
assert( last_time >= 0 );
|
assert( last_time >= 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,219 +1,219 @@
|
||||||
// Multi-channel sound buffer interface, and basic mono and stereo buffers
|
// Multi-channel sound buffer interface, and basic mono and stereo buffers
|
||||||
|
|
||||||
// Blip_Buffer $vers
|
// Blip_Buffer $vers
|
||||||
#ifndef MULTI_BUFFER_H
|
#ifndef MULTI_BUFFER_H
|
||||||
#define MULTI_BUFFER_H
|
#define MULTI_BUFFER_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
// Interface to one or more Blip_Buffers mapped to one or more channels
|
// Interface to one or more Blip_Buffers mapped to one or more channels
|
||||||
// consisting of left, center, and right buffers.
|
// consisting of left, center, and right buffers.
|
||||||
class Multi_Buffer {
|
class Multi_Buffer {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// 1=mono, 2=stereo
|
// 1=mono, 2=stereo
|
||||||
Multi_Buffer( int samples_per_frame );
|
Multi_Buffer( int samples_per_frame );
|
||||||
virtual ~Multi_Buffer() { }
|
virtual ~Multi_Buffer() { }
|
||||||
|
|
||||||
// Sets the number of channels available and optionally their types
|
// Sets the number of channels available and optionally their types
|
||||||
// (type information used by Effects_Buffer)
|
// (type information used by Effects_Buffer)
|
||||||
enum { type_index_mask = 0xFF };
|
enum { type_index_mask = 0xFF };
|
||||||
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
|
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
|
||||||
virtual blargg_err_t set_channel_count( int, int const types [] = NULL );
|
virtual blargg_err_t set_channel_count( int, int const types [] = NULL );
|
||||||
int channel_count() const { return channel_count_; }
|
int channel_count() const { return channel_count_; }
|
||||||
|
|
||||||
// Gets indexed channel, from 0 to channel_count()-1
|
// Gets indexed channel, from 0 to channel_count()-1
|
||||||
struct channel_t {
|
struct channel_t {
|
||||||
Blip_Buffer* center;
|
Blip_Buffer* center;
|
||||||
Blip_Buffer* left;
|
Blip_Buffer* left;
|
||||||
Blip_Buffer* right;
|
Blip_Buffer* right;
|
||||||
};
|
};
|
||||||
virtual channel_t channel( int index ) BLARGG_PURE( ; )
|
virtual channel_t channel( int index ) BLARGG_PURE( ; )
|
||||||
|
|
||||||
// Number of samples per output frame (1 = mono, 2 = stereo)
|
// Number of samples per output frame (1 = mono, 2 = stereo)
|
||||||
int samples_per_frame() const;
|
int samples_per_frame() const;
|
||||||
|
|
||||||
// Count of changes to channel configuration. Incremented whenever
|
// Count of changes to channel configuration. Incremented whenever
|
||||||
// a change is made to any of the Blip_Buffers for any channel.
|
// a change is made to any of the Blip_Buffers for any channel.
|
||||||
unsigned channels_changed_count() { return channels_changed_count_; }
|
unsigned channels_changed_count() { return channels_changed_count_; }
|
||||||
|
|
||||||
// See Blip_Buffer.h
|
// See Blip_Buffer.h
|
||||||
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ) BLARGG_PURE( ; )
|
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ) BLARGG_PURE( ; )
|
||||||
int sample_rate() const;
|
int sample_rate() const;
|
||||||
int length() const;
|
int length() const;
|
||||||
virtual void clock_rate( int ) BLARGG_PURE( ; )
|
virtual void clock_rate( int ) BLARGG_PURE( ; )
|
||||||
virtual void bass_freq( int ) BLARGG_PURE( ; )
|
virtual void bass_freq( int ) BLARGG_PURE( ; )
|
||||||
virtual void clear() BLARGG_PURE( ; )
|
virtual void clear() BLARGG_PURE( ; )
|
||||||
virtual void end_frame( blip_time_t ) BLARGG_PURE( ; )
|
virtual void end_frame( blip_time_t ) BLARGG_PURE( ; )
|
||||||
virtual int read_samples( blip_sample_t [], int ) BLARGG_PURE( ; )
|
virtual int read_samples( blip_sample_t [], int ) BLARGG_PURE( ; )
|
||||||
virtual int samples_avail() const BLARGG_PURE( ; )
|
virtual int samples_avail() const BLARGG_PURE( ; )
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Multi_Buffer( const Multi_Buffer& );
|
Multi_Buffer( const Multi_Buffer& );
|
||||||
Multi_Buffer& operator = ( const Multi_Buffer& );
|
Multi_Buffer& operator = ( const Multi_Buffer& );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
void disable_immediate_removal() { immediate_removal_ = false; }
|
void disable_immediate_removal() { immediate_removal_ = false; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool immediate_removal() const { return immediate_removal_; }
|
bool immediate_removal() const { return immediate_removal_; }
|
||||||
int const* channel_types() const { return channel_types_; }
|
int const* channel_types() const { return channel_types_; }
|
||||||
void channels_changed() { channels_changed_count_++; }
|
void channels_changed() { channels_changed_count_++; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned channels_changed_count_;
|
unsigned channels_changed_count_;
|
||||||
int sample_rate_;
|
int sample_rate_;
|
||||||
int length_;
|
int length_;
|
||||||
int channel_count_;
|
int channel_count_;
|
||||||
int const samples_per_frame_;
|
int const samples_per_frame_;
|
||||||
int const* channel_types_;
|
int const* channel_types_;
|
||||||
bool immediate_removal_;
|
bool immediate_removal_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Uses a single buffer and outputs mono samples.
|
// Uses a single buffer and outputs mono samples.
|
||||||
class Mono_Buffer : public Multi_Buffer {
|
class Mono_Buffer : public Multi_Buffer {
|
||||||
public:
|
public:
|
||||||
// Buffer used for all channels
|
// Buffer used for all channels
|
||||||
Blip_Buffer* center() { return &buf; }
|
Blip_Buffer* center() { return &buf; }
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Mono_Buffer();
|
Mono_Buffer();
|
||||||
~Mono_Buffer();
|
~Mono_Buffer();
|
||||||
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length );
|
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length );
|
||||||
virtual void clock_rate( int rate ) { buf.clock_rate( rate ); }
|
virtual void clock_rate( int rate ) { buf.clock_rate( rate ); }
|
||||||
virtual void bass_freq( int freq ) { buf.bass_freq( freq ); }
|
virtual void bass_freq( int freq ) { buf.bass_freq( freq ); }
|
||||||
virtual void clear() { buf.clear(); }
|
virtual void clear() { buf.clear(); }
|
||||||
virtual int samples_avail() const { return buf.samples_avail(); }
|
virtual int samples_avail() const { return buf.samples_avail(); }
|
||||||
virtual int read_samples( blip_sample_t p [], int s ) { return buf.read_samples( p, s ); }
|
virtual int read_samples( blip_sample_t p [], int s ) { return buf.read_samples( p, s ); }
|
||||||
virtual channel_t channel( int ) { return chan; }
|
virtual channel_t channel( int ) { return chan; }
|
||||||
virtual void end_frame( blip_time_t t ) { buf.end_frame( t ); }
|
virtual void end_frame( blip_time_t t ) { buf.end_frame( t ); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Blip_Buffer buf;
|
Blip_Buffer buf;
|
||||||
channel_t chan;
|
channel_t chan;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Tracked_Blip_Buffer : public Blip_Buffer {
|
class Tracked_Blip_Buffer : public Blip_Buffer {
|
||||||
public:
|
public:
|
||||||
// Non-zero if buffer still has non-silent samples in it. Requires that you call
|
// Non-zero if buffer still has non-silent samples in it. Requires that you call
|
||||||
// set_modified() appropriately.
|
// set_modified() appropriately.
|
||||||
unsigned non_silent() const;
|
unsigned non_silent() const;
|
||||||
|
|
||||||
// remove_samples( samples_avail() )
|
// remove_samples( samples_avail() )
|
||||||
void remove_all_samples();
|
void remove_all_samples();
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
int read_samples( blip_sample_t [], int );
|
int read_samples( blip_sample_t [], int );
|
||||||
void remove_silence( int );
|
void remove_silence( int );
|
||||||
void remove_samples( int );
|
void remove_samples( int );
|
||||||
Tracked_Blip_Buffer();
|
Tracked_Blip_Buffer();
|
||||||
void clear();
|
void clear();
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int last_non_silence;
|
int last_non_silence;
|
||||||
|
|
||||||
delta_t unsettled() const { return integrator() >> delta_bits; }
|
delta_t unsettled() const { return integrator() >> delta_bits; }
|
||||||
void remove_( int );
|
void remove_( int );
|
||||||
};
|
};
|
||||||
|
|
||||||
class Stereo_Mixer {
|
class Stereo_Mixer {
|
||||||
public:
|
public:
|
||||||
Tracked_Blip_Buffer* bufs [3];
|
Tracked_Blip_Buffer* bufs [3];
|
||||||
int samples_read;
|
int samples_read;
|
||||||
|
|
||||||
Stereo_Mixer() : samples_read( 0 ) { }
|
Stereo_Mixer() : samples_read( 0 ) { }
|
||||||
void read_pairs( blip_sample_t out [], int count );
|
void read_pairs( blip_sample_t out [], int count );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void mix_mono ( blip_sample_t out [], int pair_count );
|
void mix_mono ( blip_sample_t out [], int pair_count );
|
||||||
void mix_stereo( blip_sample_t out [], int pair_count );
|
void mix_stereo( blip_sample_t out [], int pair_count );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Uses three buffers (one for center) and outputs stereo sample pairs.
|
// Uses three buffers (one for center) and outputs stereo sample pairs.
|
||||||
class Stereo_Buffer : public Multi_Buffer {
|
class Stereo_Buffer : public Multi_Buffer {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Buffers used for all channels
|
// Buffers used for all channels
|
||||||
Blip_Buffer* center() { return &bufs [2]; }
|
Blip_Buffer* center() { return &bufs [2]; }
|
||||||
Blip_Buffer* left() { return &bufs [0]; }
|
Blip_Buffer* left() { return &bufs [0]; }
|
||||||
Blip_Buffer* right() { return &bufs [1]; }
|
Blip_Buffer* right() { return &bufs [1]; }
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Stereo_Buffer();
|
Stereo_Buffer();
|
||||||
~Stereo_Buffer();
|
~Stereo_Buffer();
|
||||||
virtual blargg_err_t set_sample_rate( int, int msec = blip_default_length );
|
virtual blargg_err_t set_sample_rate( int, int msec = blip_default_length );
|
||||||
virtual void clock_rate( int );
|
virtual void clock_rate( int );
|
||||||
virtual void bass_freq( int );
|
virtual void bass_freq( int );
|
||||||
virtual void clear();
|
virtual void clear();
|
||||||
virtual channel_t channel( int ) { return chan; }
|
virtual channel_t channel( int ) { return chan; }
|
||||||
virtual void end_frame( blip_time_t );
|
virtual void end_frame( blip_time_t );
|
||||||
virtual int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
|
virtual int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
|
||||||
virtual int read_samples( blip_sample_t [], int );
|
virtual int read_samples( blip_sample_t [], int );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { bufs_size = 3 };
|
enum { bufs_size = 3 };
|
||||||
typedef Tracked_Blip_Buffer buf_t;
|
typedef Tracked_Blip_Buffer buf_t;
|
||||||
buf_t bufs [bufs_size];
|
buf_t bufs [bufs_size];
|
||||||
Stereo_Mixer mixer;
|
Stereo_Mixer mixer;
|
||||||
channel_t chan;
|
channel_t chan;
|
||||||
int samples_avail_;
|
int samples_avail_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Silent_Buffer generates no samples, useful where no sound is wanted
|
// Silent_Buffer generates no samples, useful where no sound is wanted
|
||||||
class Silent_Buffer : public Multi_Buffer {
|
class Silent_Buffer : public Multi_Buffer {
|
||||||
channel_t chan;
|
channel_t chan;
|
||||||
public:
|
public:
|
||||||
Silent_Buffer();
|
Silent_Buffer();
|
||||||
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length );
|
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length );
|
||||||
virtual void clock_rate( int ) { }
|
virtual void clock_rate( int ) { }
|
||||||
virtual void bass_freq( int ) { }
|
virtual void bass_freq( int ) { }
|
||||||
virtual void clear() { }
|
virtual void clear() { }
|
||||||
virtual channel_t channel( int ) { return chan; }
|
virtual channel_t channel( int ) { return chan; }
|
||||||
virtual void end_frame( blip_time_t ) { }
|
virtual void end_frame( blip_time_t ) { }
|
||||||
virtual int samples_avail() const { return 0; }
|
virtual int samples_avail() const { return 0; }
|
||||||
virtual int read_samples( blip_sample_t [], int ) { return 0; }
|
virtual int read_samples( blip_sample_t [], int ) { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
inline blargg_err_t Multi_Buffer::set_sample_rate( int rate, int msec )
|
inline blargg_err_t Multi_Buffer::set_sample_rate( int rate, int msec )
|
||||||
{
|
{
|
||||||
sample_rate_ = rate;
|
sample_rate_ = rate;
|
||||||
length_ = msec;
|
length_ = msec;
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
|
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
|
||||||
inline int Multi_Buffer::sample_rate() const { return sample_rate_; }
|
inline int Multi_Buffer::sample_rate() const { return sample_rate_; }
|
||||||
inline int Multi_Buffer::length() const { return length_; }
|
inline int Multi_Buffer::length() const { return length_; }
|
||||||
inline void Multi_Buffer::clock_rate( int ) { }
|
inline void Multi_Buffer::clock_rate( int ) { }
|
||||||
inline void Multi_Buffer::bass_freq( int ) { }
|
inline void Multi_Buffer::bass_freq( int ) { }
|
||||||
inline void Multi_Buffer::clear() { }
|
inline void Multi_Buffer::clear() { }
|
||||||
inline void Multi_Buffer::end_frame( blip_time_t ) { }
|
inline void Multi_Buffer::end_frame( blip_time_t ) { }
|
||||||
inline int Multi_Buffer::read_samples( blip_sample_t [], int ) { return 0; }
|
inline int Multi_Buffer::read_samples( blip_sample_t [], int ) { return 0; }
|
||||||
inline int Multi_Buffer::samples_avail() const { return 0; }
|
inline int Multi_Buffer::samples_avail() const { return 0; }
|
||||||
|
|
||||||
inline blargg_err_t Multi_Buffer::set_channel_count( int n, int const types [] )
|
inline blargg_err_t Multi_Buffer::set_channel_count( int n, int const types [] )
|
||||||
{
|
{
|
||||||
channel_count_ = n;
|
channel_count_ = n;
|
||||||
channel_types_ = types;
|
channel_types_ = types;
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline blargg_err_t Silent_Buffer::set_sample_rate( int rate, int msec )
|
inline blargg_err_t Silent_Buffer::set_sample_rate( int rate, int msec )
|
||||||
{
|
{
|
||||||
return Multi_Buffer::set_sample_rate( rate, msec );
|
return Multi_Buffer::set_sample_rate( rate, msec );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -220,7 +220,7 @@ void Music_Emu::set_fade( int start_msec, int length_msec )
|
||||||
fade_set = true;
|
fade_set = true;
|
||||||
this->length_msec = start_msec;
|
this->length_msec = start_msec;
|
||||||
this->fade_msec = length_msec;
|
this->fade_msec = length_msec;
|
||||||
track_filter.set_fade( msec_to_samples( start_msec ),
|
track_filter.set_fade( start_msec < 0 ? Track_Filter::indefinite_count : msec_to_samples( start_msec ),
|
||||||
length_msec * sample_rate() / (1000 / stereo) );
|
length_msec * sample_rate() / (1000 / stereo) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,394 +1,394 @@
|
||||||
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
|
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nes_Apu.h"
|
#include "Nes_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const amp_range = 15;
|
int const amp_range = 15;
|
||||||
|
|
||||||
Nes_Apu::Nes_Apu() :
|
Nes_Apu::Nes_Apu() :
|
||||||
square1( &square_synth ),
|
square1( &square_synth ),
|
||||||
square2( &square_synth )
|
square2( &square_synth )
|
||||||
{
|
{
|
||||||
tempo_ = 1.0;
|
tempo_ = 1.0;
|
||||||
dmc.apu = this;
|
dmc.apu = this;
|
||||||
|
|
||||||
oscs [0] = &square1;
|
oscs [0] = &square1;
|
||||||
oscs [1] = &square2;
|
oscs [1] = &square2;
|
||||||
oscs [2] = ▵
|
oscs [2] = ▵
|
||||||
oscs [3] = &noise;
|
oscs [3] = &noise;
|
||||||
oscs [4] = &dmc;
|
oscs [4] = &dmc;
|
||||||
|
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
dmc.nonlinear = false;
|
dmc.nonlinear = false;
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset( false );
|
reset( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::treble_eq( const blip_eq_t& eq )
|
void Nes_Apu::treble_eq( const blip_eq_t& eq )
|
||||||
{
|
{
|
||||||
square_synth .treble_eq( eq );
|
square_synth .treble_eq( eq );
|
||||||
triangle.synth.treble_eq( eq );
|
triangle.synth.treble_eq( eq );
|
||||||
noise .synth.treble_eq( eq );
|
noise .synth.treble_eq( eq );
|
||||||
dmc .synth.treble_eq( eq );
|
dmc .synth.treble_eq( eq );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::enable_nonlinear_( double sq, double tnd )
|
void Nes_Apu::enable_nonlinear_( double sq, double tnd )
|
||||||
{
|
{
|
||||||
dmc.nonlinear = true;
|
dmc.nonlinear = true;
|
||||||
square_synth.volume( sq );
|
square_synth.volume( sq );
|
||||||
|
|
||||||
triangle.synth.volume( tnd * 2.752 );
|
triangle.synth.volume( tnd * 2.752 );
|
||||||
noise .synth.volume( tnd * 1.849 );
|
noise .synth.volume( tnd * 1.849 );
|
||||||
dmc .synth.volume( tnd );
|
dmc .synth.volume( tnd );
|
||||||
|
|
||||||
square1 .last_amp = 0;
|
square1 .last_amp = 0;
|
||||||
square2 .last_amp = 0;
|
square2 .last_amp = 0;
|
||||||
triangle.last_amp = 0;
|
triangle.last_amp = 0;
|
||||||
noise .last_amp = 0;
|
noise .last_amp = 0;
|
||||||
dmc .last_amp = 0;
|
dmc .last_amp = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::volume( double v )
|
void Nes_Apu::volume( double v )
|
||||||
{
|
{
|
||||||
if ( !dmc.nonlinear )
|
if ( !dmc.nonlinear )
|
||||||
{
|
{
|
||||||
v *= 1.0 / 1.11; // TODO: merge into values below
|
v *= 1.0 / 1.11; // TODO: merge into values below
|
||||||
square_synth .volume( 0.125 / amp_range * v ); // was 0.1128 1.108
|
square_synth .volume( 0.125 / amp_range * v ); // was 0.1128 1.108
|
||||||
triangle.synth.volume( 0.150 / amp_range * v ); // was 0.12765 1.175
|
triangle.synth.volume( 0.150 / amp_range * v ); // was 0.12765 1.175
|
||||||
noise .synth.volume( 0.095 / amp_range * v ); // was 0.0741 1.282
|
noise .synth.volume( 0.095 / amp_range * v ); // was 0.0741 1.282
|
||||||
dmc .synth.volume( 0.450 / 2048 * v ); // was 0.42545 1.058
|
dmc .synth.volume( 0.450 / 2048 * v ); // was 0.42545 1.058
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::set_output( Blip_Buffer* buffer )
|
void Nes_Apu::set_output( Blip_Buffer* buffer )
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
set_output( i, buffer );
|
set_output( i, buffer );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::set_tempo( double t )
|
void Nes_Apu::set_tempo( double t )
|
||||||
{
|
{
|
||||||
tempo_ = t;
|
tempo_ = t;
|
||||||
frame_period = (dmc.pal_mode ? 8314 : 7458);
|
frame_period = (dmc.pal_mode ? 8314 : 7458);
|
||||||
if ( t != 1.0 )
|
if ( t != 1.0 )
|
||||||
frame_period = (int) (frame_period / t) & ~1; // must be even
|
frame_period = (int) (frame_period / t) & ~1; // must be even
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
|
void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
|
||||||
{
|
{
|
||||||
dmc.pal_mode = pal_mode;
|
dmc.pal_mode = pal_mode;
|
||||||
set_tempo( tempo_ );
|
set_tempo( tempo_ );
|
||||||
|
|
||||||
square1.reset();
|
square1.reset();
|
||||||
square2.reset();
|
square2.reset();
|
||||||
triangle.reset();
|
triangle.reset();
|
||||||
noise.reset();
|
noise.reset();
|
||||||
dmc.reset();
|
dmc.reset();
|
||||||
|
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
last_dmc_time = 0;
|
last_dmc_time = 0;
|
||||||
osc_enables = 0;
|
osc_enables = 0;
|
||||||
irq_flag = false;
|
irq_flag = false;
|
||||||
enable_w4011 = true;
|
enable_w4011 = true;
|
||||||
earliest_irq_ = no_irq;
|
earliest_irq_ = no_irq;
|
||||||
frame_delay = 1;
|
frame_delay = 1;
|
||||||
write_register( 0, 0x4017, 0x00 );
|
write_register( 0, 0x4017, 0x00 );
|
||||||
write_register( 0, 0x4015, 0x00 );
|
write_register( 0, 0x4015, 0x00 );
|
||||||
|
|
||||||
for ( int addr = io_addr; addr <= 0x4013; addr++ )
|
for ( int addr = io_addr; addr <= 0x4013; addr++ )
|
||||||
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
|
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
|
||||||
|
|
||||||
dmc.dac = initial_dmc_dac;
|
dmc.dac = initial_dmc_dac;
|
||||||
if ( !dmc.nonlinear )
|
if ( !dmc.nonlinear )
|
||||||
triangle.last_amp = 15;
|
triangle.last_amp = 15;
|
||||||
if ( !dmc.nonlinear ) // TODO: remove?
|
if ( !dmc.nonlinear ) // TODO: remove?
|
||||||
dmc.last_amp = initial_dmc_dac; // prevent output transition
|
dmc.last_amp = initial_dmc_dac; // prevent output transition
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::irq_changed()
|
void Nes_Apu::irq_changed()
|
||||||
{
|
{
|
||||||
blip_time_t new_irq = dmc.next_irq;
|
blip_time_t new_irq = dmc.next_irq;
|
||||||
if ( dmc.irq_flag | irq_flag ) {
|
if ( dmc.irq_flag | irq_flag ) {
|
||||||
new_irq = 0;
|
new_irq = 0;
|
||||||
}
|
}
|
||||||
else if ( new_irq > next_irq ) {
|
else if ( new_irq > next_irq ) {
|
||||||
new_irq = next_irq;
|
new_irq = next_irq;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( new_irq != earliest_irq_ ) {
|
if ( new_irq != earliest_irq_ ) {
|
||||||
earliest_irq_ = new_irq;
|
earliest_irq_ = new_irq;
|
||||||
if ( irq_notifier.f )
|
if ( irq_notifier.f )
|
||||||
irq_notifier.f( irq_notifier.data );
|
irq_notifier.f( irq_notifier.data );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// frames
|
// frames
|
||||||
|
|
||||||
void Nes_Apu::run_until( blip_time_t end_time )
|
void Nes_Apu::run_until( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
require( end_time >= last_dmc_time );
|
require( end_time >= last_dmc_time );
|
||||||
if ( end_time > next_dmc_read_time() )
|
if ( end_time > next_dmc_read_time() )
|
||||||
{
|
{
|
||||||
blip_time_t start = last_dmc_time;
|
blip_time_t start = last_dmc_time;
|
||||||
last_dmc_time = end_time;
|
last_dmc_time = end_time;
|
||||||
dmc.run( start, end_time );
|
dmc.run( start, end_time );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::run_until_( blip_time_t end_time )
|
void Nes_Apu::run_until_( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
require( end_time >= last_time );
|
require( end_time >= last_time );
|
||||||
|
|
||||||
if ( end_time == last_time )
|
if ( end_time == last_time )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( last_dmc_time < end_time )
|
if ( last_dmc_time < end_time )
|
||||||
{
|
{
|
||||||
blip_time_t start = last_dmc_time;
|
blip_time_t start = last_dmc_time;
|
||||||
last_dmc_time = end_time;
|
last_dmc_time = end_time;
|
||||||
dmc.run( start, end_time );
|
dmc.run( start, end_time );
|
||||||
}
|
}
|
||||||
|
|
||||||
while ( true )
|
while ( true )
|
||||||
{
|
{
|
||||||
// earlier of next frame time or end time
|
// earlier of next frame time or end time
|
||||||
blip_time_t time = last_time + frame_delay;
|
blip_time_t time = last_time + frame_delay;
|
||||||
if ( time > end_time )
|
if ( time > end_time )
|
||||||
time = end_time;
|
time = end_time;
|
||||||
frame_delay -= time - last_time;
|
frame_delay -= time - last_time;
|
||||||
|
|
||||||
// run oscs to present
|
// run oscs to present
|
||||||
square1.run( last_time, time );
|
square1.run( last_time, time );
|
||||||
square2.run( last_time, time );
|
square2.run( last_time, time );
|
||||||
triangle.run( last_time, time );
|
triangle.run( last_time, time );
|
||||||
noise.run( last_time, time );
|
noise.run( last_time, time );
|
||||||
last_time = time;
|
last_time = time;
|
||||||
|
|
||||||
if ( time == end_time )
|
if ( time == end_time )
|
||||||
break; // no more frames to run
|
break; // no more frames to run
|
||||||
|
|
||||||
// take frame-specific actions
|
// take frame-specific actions
|
||||||
frame_delay = frame_period;
|
frame_delay = frame_period;
|
||||||
switch ( frame++ )
|
switch ( frame++ )
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
if ( !(frame_mode & 0xC0) ) {
|
if ( !(frame_mode & 0xC0) ) {
|
||||||
next_irq = time + frame_period * 4 + 2;
|
next_irq = time + frame_period * 4 + 2;
|
||||||
irq_flag = true;
|
irq_flag = true;
|
||||||
}
|
}
|
||||||
// fall through
|
// fall through
|
||||||
case 2:
|
case 2:
|
||||||
// clock length and sweep on frames 0 and 2
|
// clock length and sweep on frames 0 and 2
|
||||||
square1.clock_length( 0x20 );
|
square1.clock_length( 0x20 );
|
||||||
square2.clock_length( 0x20 );
|
square2.clock_length( 0x20 );
|
||||||
noise.clock_length( 0x20 );
|
noise.clock_length( 0x20 );
|
||||||
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
|
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
|
||||||
|
|
||||||
square1.clock_sweep( -1 );
|
square1.clock_sweep( -1 );
|
||||||
square2.clock_sweep( 0 );
|
square2.clock_sweep( 0 );
|
||||||
|
|
||||||
// frame 2 is slightly shorter in mode 1
|
// frame 2 is slightly shorter in mode 1
|
||||||
if ( dmc.pal_mode && frame == 3 )
|
if ( dmc.pal_mode && frame == 3 )
|
||||||
frame_delay -= 2;
|
frame_delay -= 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
// frame 1 is slightly shorter in mode 0
|
// frame 1 is slightly shorter in mode 0
|
||||||
if ( !dmc.pal_mode )
|
if ( !dmc.pal_mode )
|
||||||
frame_delay -= 2;
|
frame_delay -= 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
frame = 0;
|
frame = 0;
|
||||||
|
|
||||||
// frame 3 is almost twice as long in mode 1
|
// frame 3 is almost twice as long in mode 1
|
||||||
if ( frame_mode & 0x80 )
|
if ( frame_mode & 0x80 )
|
||||||
frame_delay += frame_period - (dmc.pal_mode ? 2 : 6);
|
frame_delay += frame_period - (dmc.pal_mode ? 2 : 6);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clock envelopes and linear counter every frame
|
// clock envelopes and linear counter every frame
|
||||||
triangle.clock_linear_counter();
|
triangle.clock_linear_counter();
|
||||||
square1.clock_envelope();
|
square1.clock_envelope();
|
||||||
square2.clock_envelope();
|
square2.clock_envelope();
|
||||||
noise.clock_envelope();
|
noise.clock_envelope();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
inline void zero_apu_osc( T* osc, blip_time_t time )
|
inline void zero_apu_osc( T* osc, blip_time_t time )
|
||||||
{
|
{
|
||||||
Blip_Buffer* output = osc->output;
|
Blip_Buffer* output = osc->output;
|
||||||
int last_amp = osc->last_amp;
|
int last_amp = osc->last_amp;
|
||||||
osc->last_amp = 0;
|
osc->last_amp = 0;
|
||||||
if ( output && last_amp )
|
if ( output && last_amp )
|
||||||
osc->synth.offset( time, -last_amp, output );
|
osc->synth.offset( time, -last_amp, output );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Apu::end_frame( blip_time_t end_time )
|
void Nes_Apu::end_frame( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
if ( end_time > last_time )
|
if ( end_time > last_time )
|
||||||
run_until_( end_time );
|
run_until_( end_time );
|
||||||
|
|
||||||
if ( dmc.nonlinear )
|
if ( dmc.nonlinear )
|
||||||
{
|
{
|
||||||
zero_apu_osc( &square1, last_time );
|
zero_apu_osc( &square1, last_time );
|
||||||
zero_apu_osc( &square2, last_time );
|
zero_apu_osc( &square2, last_time );
|
||||||
zero_apu_osc( &triangle, last_time );
|
zero_apu_osc( &triangle, last_time );
|
||||||
zero_apu_osc( &noise, last_time );
|
zero_apu_osc( &noise, last_time );
|
||||||
zero_apu_osc( &dmc, last_time );
|
zero_apu_osc( &dmc, last_time );
|
||||||
}
|
}
|
||||||
|
|
||||||
// make times relative to new frame
|
// make times relative to new frame
|
||||||
last_time -= end_time;
|
last_time -= end_time;
|
||||||
require( last_time >= 0 );
|
require( last_time >= 0 );
|
||||||
|
|
||||||
last_dmc_time -= end_time;
|
last_dmc_time -= end_time;
|
||||||
require( last_dmc_time >= 0 );
|
require( last_dmc_time >= 0 );
|
||||||
|
|
||||||
if ( next_irq != no_irq ) {
|
if ( next_irq != no_irq ) {
|
||||||
next_irq -= end_time;
|
next_irq -= end_time;
|
||||||
check( next_irq >= 0 );
|
check( next_irq >= 0 );
|
||||||
}
|
}
|
||||||
if ( dmc.next_irq != no_irq ) {
|
if ( dmc.next_irq != no_irq ) {
|
||||||
dmc.next_irq -= end_time;
|
dmc.next_irq -= end_time;
|
||||||
check( dmc.next_irq >= 0 );
|
check( dmc.next_irq >= 0 );
|
||||||
}
|
}
|
||||||
if ( earliest_irq_ != no_irq ) {
|
if ( earliest_irq_ != no_irq ) {
|
||||||
earliest_irq_ -= end_time;
|
earliest_irq_ -= end_time;
|
||||||
if ( earliest_irq_ < 0 )
|
if ( earliest_irq_ < 0 )
|
||||||
earliest_irq_ = 0;
|
earliest_irq_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// registers
|
// registers
|
||||||
|
|
||||||
static const unsigned char length_table [0x20] = {
|
static const unsigned char length_table [0x20] = {
|
||||||
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
|
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
|
||||||
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
|
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
|
||||||
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
|
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
|
||||||
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
|
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
|
||||||
};
|
};
|
||||||
|
|
||||||
void Nes_Apu::write_register( blip_time_t time, int addr, int data )
|
void Nes_Apu::write_register( blip_time_t time, int addr, int data )
|
||||||
{
|
{
|
||||||
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
|
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
|
||||||
require( (unsigned) data <= 0xFF );
|
require( (unsigned) data <= 0xFF );
|
||||||
|
|
||||||
// Ignore addresses outside range
|
// Ignore addresses outside range
|
||||||
if ( unsigned (addr - io_addr) >= io_size )
|
if ( unsigned (addr - io_addr) >= io_size )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
run_until_( time );
|
run_until_( time );
|
||||||
|
|
||||||
if ( addr < 0x4014 )
|
if ( addr < 0x4014 )
|
||||||
{
|
{
|
||||||
// Write to channel
|
// Write to channel
|
||||||
int osc_index = (addr - io_addr) >> 2;
|
int osc_index = (addr - io_addr) >> 2;
|
||||||
Nes_Osc* osc = oscs [osc_index];
|
Nes_Osc* osc = oscs [osc_index];
|
||||||
|
|
||||||
int reg = addr & 3;
|
int reg = addr & 3;
|
||||||
osc->regs [reg] = data;
|
osc->regs [reg] = data;
|
||||||
osc->reg_written [reg] = true;
|
osc->reg_written [reg] = true;
|
||||||
|
|
||||||
if ( osc_index == 4 )
|
if ( osc_index == 4 )
|
||||||
{
|
{
|
||||||
// handle DMC specially
|
// handle DMC specially
|
||||||
if ( enable_w4011 || reg != 1 )
|
if ( enable_w4011 || reg != 1 )
|
||||||
dmc.write_register( reg, data );
|
dmc.write_register( reg, data );
|
||||||
}
|
}
|
||||||
else if ( reg == 3 )
|
else if ( reg == 3 )
|
||||||
{
|
{
|
||||||
// load length counter
|
// load length counter
|
||||||
if ( (osc_enables >> osc_index) & 1 )
|
if ( (osc_enables >> osc_index) & 1 )
|
||||||
osc->length_counter = length_table [(data >> 3) & 0x1F];
|
osc->length_counter = length_table [(data >> 3) & 0x1F];
|
||||||
|
|
||||||
// reset square phase
|
// reset square phase
|
||||||
if ( osc_index < 2 )
|
if ( osc_index < 2 )
|
||||||
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
|
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( addr == 0x4015 )
|
else if ( addr == 0x4015 )
|
||||||
{
|
{
|
||||||
// Channel enables
|
// Channel enables
|
||||||
for ( int i = osc_count; i--; )
|
for ( int i = osc_count; i--; )
|
||||||
if ( !((data >> i) & 1) )
|
if ( !((data >> i) & 1) )
|
||||||
oscs [i]->length_counter = 0;
|
oscs [i]->length_counter = 0;
|
||||||
|
|
||||||
bool recalc_irq = dmc.irq_flag;
|
bool recalc_irq = dmc.irq_flag;
|
||||||
dmc.irq_flag = false;
|
dmc.irq_flag = false;
|
||||||
|
|
||||||
int old_enables = osc_enables;
|
int old_enables = osc_enables;
|
||||||
osc_enables = data;
|
osc_enables = data;
|
||||||
if ( !(data & 0x10) ) {
|
if ( !(data & 0x10) ) {
|
||||||
dmc.next_irq = no_irq;
|
dmc.next_irq = no_irq;
|
||||||
recalc_irq = true;
|
recalc_irq = true;
|
||||||
}
|
}
|
||||||
else if ( !(old_enables & 0x10) ) {
|
else if ( !(old_enables & 0x10) ) {
|
||||||
dmc.start(); // dmc just enabled
|
dmc.start(); // dmc just enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( recalc_irq )
|
if ( recalc_irq )
|
||||||
irq_changed();
|
irq_changed();
|
||||||
}
|
}
|
||||||
else if ( addr == 0x4017 )
|
else if ( addr == 0x4017 )
|
||||||
{
|
{
|
||||||
// Frame mode
|
// Frame mode
|
||||||
frame_mode = data;
|
frame_mode = data;
|
||||||
|
|
||||||
bool irq_enabled = !(data & 0x40);
|
bool irq_enabled = !(data & 0x40);
|
||||||
irq_flag &= irq_enabled;
|
irq_flag &= irq_enabled;
|
||||||
next_irq = no_irq;
|
next_irq = no_irq;
|
||||||
|
|
||||||
// mode 1
|
// mode 1
|
||||||
frame_delay = (frame_delay & 1);
|
frame_delay = (frame_delay & 1);
|
||||||
frame = 0;
|
frame = 0;
|
||||||
|
|
||||||
if ( !(data & 0x80) )
|
if ( !(data & 0x80) )
|
||||||
{
|
{
|
||||||
// mode 0
|
// mode 0
|
||||||
frame = 1;
|
frame = 1;
|
||||||
frame_delay += frame_period;
|
frame_delay += frame_period;
|
||||||
if ( irq_enabled )
|
if ( irq_enabled )
|
||||||
next_irq = time + frame_delay + frame_period * 3 + 1;
|
next_irq = time + frame_delay + frame_period * 3 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
irq_changed();
|
irq_changed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Nes_Apu::read_status( blip_time_t time )
|
int Nes_Apu::read_status( blip_time_t time )
|
||||||
{
|
{
|
||||||
run_until_( time - 1 );
|
run_until_( time - 1 );
|
||||||
|
|
||||||
int result = (dmc.irq_flag << 7) | (irq_flag << 6);
|
int result = (dmc.irq_flag << 7) | (irq_flag << 6);
|
||||||
|
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
if ( oscs [i]->length_counter )
|
if ( oscs [i]->length_counter )
|
||||||
result |= 1 << i;
|
result |= 1 << i;
|
||||||
|
|
||||||
run_until_( time );
|
run_until_( time );
|
||||||
|
|
||||||
if ( irq_flag )
|
if ( irq_flag )
|
||||||
{
|
{
|
||||||
result |= 0x40;
|
result |= 0x40;
|
||||||
irq_flag = false;
|
irq_flag = false;
|
||||||
irq_changed();
|
irq_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
//dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
|
//dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,184 +1,184 @@
|
||||||
// NES 2A03 APU sound chip emulator
|
// NES 2A03 APU sound chip emulator
|
||||||
|
|
||||||
// Nes_Snd_Emu $vers
|
// Nes_Snd_Emu $vers
|
||||||
#ifndef NES_APU_H
|
#ifndef NES_APU_H
|
||||||
#define NES_APU_H
|
#define NES_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Nes_Oscs.h"
|
#include "Nes_Oscs.h"
|
||||||
|
|
||||||
struct apu_state_t;
|
struct apu_state_t;
|
||||||
class Nes_Buffer;
|
class Nes_Buffer;
|
||||||
|
|
||||||
class Nes_Apu {
|
class Nes_Apu {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
|
|
||||||
typedef int nes_time_t; // NES CPU clock cycle count
|
typedef int nes_time_t; // NES CPU clock cycle count
|
||||||
|
|
||||||
// Sets memory reader callback used by DMC oscillator to fetch samples.
|
// Sets memory reader callback used by DMC oscillator to fetch samples.
|
||||||
// When callback is invoked, 'user_data' is passed unchanged as the
|
// When callback is invoked, 'user_data' is passed unchanged as the
|
||||||
// first parameter.
|
// first parameter.
|
||||||
//void dmc_reader( int (*callback)( void* user_data, int addr ), void* user_data = NULL );
|
//void dmc_reader( int (*callback)( void* user_data, int addr ), void* user_data = NULL );
|
||||||
|
|
||||||
// Sets buffer to generate sound into, or 0 to mute output (reduces
|
// Sets buffer to generate sound into, or 0 to mute output (reduces
|
||||||
// emulation accuracy).
|
// emulation accuracy).
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
|
|
||||||
// All time values are the number of CPU clock cycles relative to the
|
// All time values are the number of CPU clock cycles relative to the
|
||||||
// beginning of the current time frame. Before resetting the CPU clock
|
// beginning of the current time frame. Before resetting the CPU clock
|
||||||
// count, call end_frame( last_cpu_time ).
|
// count, call end_frame( last_cpu_time ).
|
||||||
|
|
||||||
// Writes to register (0x4000-0x4013, and 0x4015 and 0x4017)
|
// Writes to register (0x4000-0x4013, and 0x4015 and 0x4017)
|
||||||
enum { io_addr = 0x4000 };
|
enum { io_addr = 0x4000 };
|
||||||
enum { io_size = 0x18 };
|
enum { io_size = 0x18 };
|
||||||
void write_register( nes_time_t, int addr, int data );
|
void write_register( nes_time_t, int addr, int data );
|
||||||
|
|
||||||
// Reads from status register (0x4015)
|
// Reads from status register (0x4015)
|
||||||
enum { status_addr = 0x4015 };
|
enum { status_addr = 0x4015 };
|
||||||
int read_status( nes_time_t );
|
int read_status( nes_time_t );
|
||||||
|
|
||||||
// Runs all oscillators up to specified time, ends current time frame, then
|
// Runs all oscillators up to specified time, ends current time frame, then
|
||||||
// starts a new time frame at time 0. Time frames have no effect on emulation
|
// starts a new time frame at time 0. Time frames have no effect on emulation
|
||||||
// and each can be whatever length is convenient.
|
// and each can be whatever length is convenient.
|
||||||
void end_frame( nes_time_t );
|
void end_frame( nes_time_t );
|
||||||
|
|
||||||
// Optional
|
// Optional
|
||||||
|
|
||||||
// Resets internal frame counter, registers, and all oscillators.
|
// Resets internal frame counter, registers, and all oscillators.
|
||||||
// Uses PAL timing if pal_timing is true, otherwise use NTSC timing.
|
// Uses PAL timing if pal_timing is true, otherwise use NTSC timing.
|
||||||
// Sets the DMC oscillator's initial DAC value to initial_dmc_dac without
|
// Sets the DMC oscillator's initial DAC value to initial_dmc_dac without
|
||||||
// any audible click.
|
// any audible click.
|
||||||
void reset( bool pal_mode = false, int initial_dmc_dac = 0 );
|
void reset( bool pal_mode = false, int initial_dmc_dac = 0 );
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
// 0: Square 1, 1: Square 2, 2: Triangle, 3: Noise, 4: DMC
|
// 0: Square 1, 1: Square 2, 2: Triangle, 3: Noise, 4: DMC
|
||||||
enum { osc_count = 5 };
|
enum { osc_count = 5 };
|
||||||
void set_output( int chan, Blip_Buffer* buf );
|
void set_output( int chan, Blip_Buffer* buf );
|
||||||
|
|
||||||
// Adjusts frame period
|
// Adjusts frame period
|
||||||
void set_tempo( double );
|
void set_tempo( double );
|
||||||
|
|
||||||
// Saves/loads exact emulation state
|
// Saves/loads exact emulation state
|
||||||
void save_state( apu_state_t* out ) const;
|
void save_state( apu_state_t* out ) const;
|
||||||
void load_state( apu_state_t const& );
|
void load_state( apu_state_t const& );
|
||||||
|
|
||||||
// Sets overall volume (default is 1.0)
|
// Sets overall volume (default is 1.0)
|
||||||
void volume( double );
|
void volume( double );
|
||||||
|
|
||||||
// Sets treble equalization (see notes.txt)
|
// Sets treble equalization (see notes.txt)
|
||||||
void treble_eq( const blip_eq_t& );
|
void treble_eq( const blip_eq_t& );
|
||||||
|
|
||||||
// Sets IRQ time callback that is invoked when the time of earliest IRQ
|
// Sets IRQ time callback that is invoked when the time of earliest IRQ
|
||||||
// may have changed, or NULL to disable. When callback is invoked,
|
// may have changed, or NULL to disable. When callback is invoked,
|
||||||
// 'user_data' is passed unchanged as the first parameter.
|
// 'user_data' is passed unchanged as the first parameter.
|
||||||
//void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
|
//void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
|
||||||
|
|
||||||
// Gets time that APU-generated IRQ will occur if no further register reads
|
// Gets time that APU-generated IRQ will occur if no further register reads
|
||||||
// or writes occur. If IRQ is already pending, returns irq_waiting. If no
|
// or writes occur. If IRQ is already pending, returns irq_waiting. If no
|
||||||
// IRQ will occur, returns no_irq.
|
// IRQ will occur, returns no_irq.
|
||||||
enum { no_irq = INT_MAX/2 + 1 };
|
enum { no_irq = INT_MAX/2 + 1 };
|
||||||
enum { irq_waiting = 0 };
|
enum { irq_waiting = 0 };
|
||||||
nes_time_t earliest_irq( nes_time_t ) const;
|
nes_time_t earliest_irq( nes_time_t ) const;
|
||||||
|
|
||||||
// Counts number of DMC reads that would occur if 'run_until( t )' were executed.
|
// Counts number of DMC reads that would occur if 'run_until( t )' were executed.
|
||||||
// If last_read is not NULL, set *last_read to the earliest time that
|
// If last_read is not NULL, set *last_read to the earliest time that
|
||||||
// 'count_dmc_reads( time )' would result in the same result.
|
// 'count_dmc_reads( time )' would result in the same result.
|
||||||
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
|
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
|
||||||
|
|
||||||
// Time when next DMC memory read will occur
|
// Time when next DMC memory read will occur
|
||||||
nes_time_t next_dmc_read_time() const;
|
nes_time_t next_dmc_read_time() const;
|
||||||
|
|
||||||
// Runs DMC until specified time, so that any DMC memory reads can be
|
// Runs DMC until specified time, so that any DMC memory reads can be
|
||||||
// accounted for (i.e. inserting CPU wait states).
|
// accounted for (i.e. inserting CPU wait states).
|
||||||
void run_until( nes_time_t );
|
void run_until( nes_time_t );
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Nes_Apu();
|
Nes_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
// Use set_output() in place of these
|
// Use set_output() in place of these
|
||||||
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); )
|
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); )
|
||||||
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ); )
|
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ); )
|
||||||
|
|
||||||
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4000 }; )
|
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4000 }; )
|
||||||
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4017 }; )
|
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4017 }; )
|
||||||
|
|
||||||
blargg_callback<int (*)( void* user_data, int addr )> dmc_reader;
|
blargg_callback<int (*)( void* user_data, int addr )> dmc_reader;
|
||||||
blargg_callback<void (*)( void* user_data )> irq_notifier;
|
blargg_callback<void (*)( void* user_data )> irq_notifier;
|
||||||
|
|
||||||
void enable_nonlinear_( double sq, double tnd );
|
void enable_nonlinear_( double sq, double tnd );
|
||||||
static float tnd_total_() { return 196.015f; }
|
static float tnd_total_() { return 196.015f; }
|
||||||
|
|
||||||
void enable_w4011_( bool enable = true ) { enable_w4011 = enable; }
|
void enable_w4011_( bool enable = true ) { enable_w4011 = enable; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct Nes_Dmc;
|
friend struct Nes_Dmc;
|
||||||
|
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Nes_Apu( const Nes_Apu& );
|
Nes_Apu( const Nes_Apu& );
|
||||||
Nes_Apu& operator = ( const Nes_Apu& );
|
Nes_Apu& operator = ( const Nes_Apu& );
|
||||||
|
|
||||||
Nes_Osc* oscs [osc_count];
|
Nes_Osc* oscs [osc_count];
|
||||||
Nes_Square square1;
|
Nes_Square square1;
|
||||||
Nes_Square square2;
|
Nes_Square square2;
|
||||||
Nes_Noise noise;
|
Nes_Noise noise;
|
||||||
Nes_Triangle triangle;
|
Nes_Triangle triangle;
|
||||||
Nes_Dmc dmc;
|
Nes_Dmc dmc;
|
||||||
|
|
||||||
double tempo_;
|
double tempo_;
|
||||||
nes_time_t last_time; // has been run until this time in current frame
|
nes_time_t last_time; // has been run until this time in current frame
|
||||||
nes_time_t last_dmc_time;
|
nes_time_t last_dmc_time;
|
||||||
nes_time_t earliest_irq_;
|
nes_time_t earliest_irq_;
|
||||||
nes_time_t next_irq;
|
nes_time_t next_irq;
|
||||||
int frame_period;
|
int frame_period;
|
||||||
int frame_delay; // cycles until frame counter runs next
|
int frame_delay; // cycles until frame counter runs next
|
||||||
int frame; // current frame (0-3)
|
int frame; // current frame (0-3)
|
||||||
int osc_enables;
|
int osc_enables;
|
||||||
int frame_mode;
|
int frame_mode;
|
||||||
bool irq_flag;
|
bool irq_flag;
|
||||||
bool enable_w4011;
|
bool enable_w4011;
|
||||||
Nes_Square::Synth square_synth; // shared by squares
|
Nes_Square::Synth square_synth; // shared by squares
|
||||||
|
|
||||||
void irq_changed();
|
void irq_changed();
|
||||||
void state_restored();
|
void state_restored();
|
||||||
void run_until_( nes_time_t );
|
void run_until_( nes_time_t );
|
||||||
|
|
||||||
// TODO: remove
|
// TODO: remove
|
||||||
friend class Nes_Core;
|
friend class Nes_Core;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Nes_Apu::set_output( int osc, Blip_Buffer* buf )
|
inline void Nes_Apu::set_output( int osc, Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
assert( (unsigned) osc < osc_count );
|
assert( (unsigned) osc < osc_count );
|
||||||
oscs [osc]->output = buf;
|
oscs [osc]->output = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Nes_Apu::nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
|
inline Nes_Apu::nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
|
||||||
{
|
{
|
||||||
return earliest_irq_;
|
return earliest_irq_;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const
|
inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const
|
||||||
{
|
{
|
||||||
return dmc.count_reads( time, last_read );
|
return dmc.count_reads( time, last_read );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Nes_Apu::nes_time_t Nes_Dmc::next_read_time() const
|
inline Nes_Apu::nes_time_t Nes_Dmc::next_read_time() const
|
||||||
{
|
{
|
||||||
if ( length_counter == 0 )
|
if ( length_counter == 0 )
|
||||||
return Nes_Apu::no_irq; // not reading
|
return Nes_Apu::no_irq; // not reading
|
||||||
|
|
||||||
return apu->last_dmc_time + delay + (bits_remain - 1) * period;
|
return apu->last_dmc_time + delay + (bits_remain - 1) * period;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Nes_Apu::nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
|
inline Nes_Apu::nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
|
||||||
|
|
||||||
BLARGG_DEPRECATED( typedef int nes_time_t; ) // use your own typedef
|
BLARGG_DEPRECATED( typedef int nes_time_t; ) // use your own typedef
|
||||||
BLARGG_DEPRECATED( typedef unsigned nes_addr_t; ) // use your own typedef
|
BLARGG_DEPRECATED( typedef unsigned nes_addr_t; ) // use your own typedef
|
||||||
|
|
||||||
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::output ( Blip_Buffer* c ) { set_output( c ); } )
|
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::output ( Blip_Buffer* c ) { set_output( c ); } )
|
||||||
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::osc_output( int i, Blip_Buffer* c ) { set_output( i, c ); } )
|
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::osc_output( int i, Blip_Buffer* c ) { set_output( i, c ); } )
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,62 +1,62 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nes_Cpu.h"
|
#include "Nes_Cpu.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
inline void Nes_Cpu::set_code_page( int i, void const* p )
|
inline void Nes_Cpu::set_code_page( int i, void const* p )
|
||||||
{
|
{
|
||||||
byte const* p2 = STATIC_CAST(byte const*,p) - NES_CPU_OFFSET( i * page_size );
|
byte const* p2 = STATIC_CAST(byte const*,p) - NES_CPU_OFFSET( i * page_size );
|
||||||
cpu_state->code_map [i] = p2;
|
cpu_state->code_map [i] = p2;
|
||||||
cpu_state_.code_map [i] = p2;
|
cpu_state_.code_map [i] = p2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Cpu::map_code( addr_t start, int size, void const* data, int mirror_size )
|
void Nes_Cpu::map_code( addr_t start, int size, void const* data, int mirror_size )
|
||||||
{
|
{
|
||||||
// address range must begin and end on page boundaries
|
// address range must begin and end on page boundaries
|
||||||
require( start % page_size == 0 );
|
require( start % page_size == 0 );
|
||||||
require( size % page_size == 0 );
|
require( size % page_size == 0 );
|
||||||
require( start + size <= 0x10000 );
|
require( start + size <= 0x10000 );
|
||||||
require( mirror_size % page_size == 0 );
|
require( mirror_size % page_size == 0 );
|
||||||
|
|
||||||
for ( int offset = 0; offset < size; offset += page_size )
|
for ( int offset = 0; offset < size; offset += page_size )
|
||||||
set_code_page( NES_CPU_PAGE( start + offset ),
|
set_code_page( NES_CPU_PAGE( start + offset ),
|
||||||
STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) );
|
STATIC_CAST(char const*,data) + (offset & ((unsigned) mirror_size - 1)) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Cpu::reset( void const* unmapped_page )
|
void Nes_Cpu::reset( void const* unmapped_page )
|
||||||
{
|
{
|
||||||
check( cpu_state == &cpu_state_ );
|
check( cpu_state == &cpu_state_ );
|
||||||
cpu_state = &cpu_state_;
|
cpu_state = &cpu_state_;
|
||||||
|
|
||||||
r.flags = irq_inhibit_mask;
|
r.flags = irq_inhibit_mask;
|
||||||
r.sp = 0xFF;
|
r.sp = 0xFF;
|
||||||
r.pc = 0;
|
r.pc = 0;
|
||||||
r.a = 0;
|
r.a = 0;
|
||||||
r.x = 0;
|
r.x = 0;
|
||||||
r.y = 0;
|
r.y = 0;
|
||||||
|
|
||||||
cpu_state_.time = 0;
|
cpu_state_.time = 0;
|
||||||
cpu_state_.base = 0;
|
cpu_state_.base = 0;
|
||||||
irq_time_ = future_time;
|
irq_time_ = future_time;
|
||||||
end_time_ = future_time;
|
end_time_ = future_time;
|
||||||
error_count_ = 0;
|
error_count_ = 0;
|
||||||
|
|
||||||
set_code_page( page_count, unmapped_page );
|
set_code_page( page_count, unmapped_page );
|
||||||
map_code( 0, 0x10000, unmapped_page, page_size );
|
map_code( 0, 0x10000, unmapped_page, page_size );
|
||||||
|
|
||||||
blargg_verify_byte_order();
|
blargg_verify_byte_order();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,131 +1,131 @@
|
||||||
// NES CPU emulator
|
// NES CPU emulator
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef NES_CPU_H
|
#ifndef NES_CPU_H
|
||||||
#define NES_CPU_H
|
#define NES_CPU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
|
|
||||||
class Nes_Cpu {
|
class Nes_Cpu {
|
||||||
public:
|
public:
|
||||||
typedef BOOST::uint8_t byte;
|
typedef BOOST::uint8_t byte;
|
||||||
typedef int time_t;
|
typedef int time_t;
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
enum { future_time = INT_MAX/2 + 1 };
|
enum { future_time = INT_MAX/2 + 1 };
|
||||||
|
|
||||||
// Clears registers and maps all pages to unmapped_page
|
// Clears registers and maps all pages to unmapped_page
|
||||||
void reset( void const* unmapped_page = NULL );
|
void reset( void const* unmapped_page = NULL );
|
||||||
|
|
||||||
// Maps code memory (memory accessed via the program counter). Start and size
|
// Maps code memory (memory accessed via the program counter). Start and size
|
||||||
// must be multiple of page_size. If mirror_size is non-zero, the first
|
// must be multiple of page_size. If mirror_size is non-zero, the first
|
||||||
// mirror_size bytes are repeated over the range. mirror_size must be a
|
// mirror_size bytes are repeated over the range. mirror_size must be a
|
||||||
// multiple of page_size.
|
// multiple of page_size.
|
||||||
enum { page_bits = 11 };
|
enum { page_bits = 11 };
|
||||||
enum { page_size = 1 << page_bits };
|
enum { page_size = 1 << page_bits };
|
||||||
void map_code( addr_t start, int size, void const* code, int mirror_size = 0 );
|
void map_code( addr_t start, int size, void const* code, int mirror_size = 0 );
|
||||||
|
|
||||||
// Accesses emulated memory as CPU does
|
// Accesses emulated memory as CPU does
|
||||||
byte const* get_code( addr_t ) const;
|
byte const* get_code( addr_t ) const;
|
||||||
|
|
||||||
// NES 6502 registers. NOT kept updated during emulation.
|
// NES 6502 registers. NOT kept updated during emulation.
|
||||||
struct registers_t {
|
struct registers_t {
|
||||||
BOOST::uint16_t pc;
|
BOOST::uint16_t pc;
|
||||||
byte a;
|
byte a;
|
||||||
byte x;
|
byte x;
|
||||||
byte y;
|
byte y;
|
||||||
byte flags;
|
byte flags;
|
||||||
byte sp;
|
byte sp;
|
||||||
};
|
};
|
||||||
registers_t r;
|
registers_t r;
|
||||||
|
|
||||||
// Time of beginning of next instruction to be executed
|
// Time of beginning of next instruction to be executed
|
||||||
time_t time() const { return cpu_state->time + cpu_state->base; }
|
time_t time() const { return cpu_state->time + cpu_state->base; }
|
||||||
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; }
|
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; }
|
||||||
void adjust_time( int delta ) { cpu_state->time += delta; }
|
void adjust_time( int delta ) { cpu_state->time += delta; }
|
||||||
|
|
||||||
// Clocks past end (negative if before)
|
// Clocks past end (negative if before)
|
||||||
int time_past_end() const { return cpu_state->time; }
|
int time_past_end() const { return cpu_state->time; }
|
||||||
|
|
||||||
// Time of next IRQ
|
// Time of next IRQ
|
||||||
time_t irq_time() const { return irq_time_; }
|
time_t irq_time() const { return irq_time_; }
|
||||||
void set_irq_time( time_t );
|
void set_irq_time( time_t );
|
||||||
|
|
||||||
// Emulation stops once time >= end_time
|
// Emulation stops once time >= end_time
|
||||||
time_t end_time() const { return end_time_; }
|
time_t end_time() const { return end_time_; }
|
||||||
void set_end_time( time_t );
|
void set_end_time( time_t );
|
||||||
|
|
||||||
// Number of unimplemented instructions encountered and skipped
|
// Number of unimplemented instructions encountered and skipped
|
||||||
void clear_error_count() { error_count_ = 0; }
|
void clear_error_count() { error_count_ = 0; }
|
||||||
unsigned error_count() const { return error_count_; }
|
unsigned error_count() const { return error_count_; }
|
||||||
void count_error() { error_count_++; }
|
void count_error() { error_count_++; }
|
||||||
|
|
||||||
// Unmapped page should be filled with this
|
// Unmapped page should be filled with this
|
||||||
enum { halt_opcode = 0x22 };
|
enum { halt_opcode = 0x22 };
|
||||||
|
|
||||||
enum { irq_inhibit_mask = 0x04 };
|
enum { irq_inhibit_mask = 0x04 };
|
||||||
|
|
||||||
// Can read this many bytes past end of a page
|
// Can read this many bytes past end of a page
|
||||||
enum { cpu_padding = 8 };
|
enum { cpu_padding = 8 };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Nes_Cpu( const Nes_Cpu& );
|
Nes_Cpu( const Nes_Cpu& );
|
||||||
Nes_Cpu& operator = ( const Nes_Cpu& );
|
Nes_Cpu& operator = ( const Nes_Cpu& );
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Nes_Cpu() { cpu_state = &cpu_state_; }
|
Nes_Cpu() { cpu_state = &cpu_state_; }
|
||||||
enum { page_count = 0x10000 >> page_bits };
|
enum { page_count = 0x10000 >> page_bits };
|
||||||
|
|
||||||
struct cpu_state_t {
|
struct cpu_state_t {
|
||||||
byte const* code_map [page_count + 1];
|
byte const* code_map [page_count + 1];
|
||||||
time_t base;
|
time_t base;
|
||||||
int time;
|
int time;
|
||||||
};
|
};
|
||||||
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
|
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy
|
||||||
cpu_state_t cpu_state_;
|
cpu_state_t cpu_state_;
|
||||||
time_t irq_time_;
|
time_t irq_time_;
|
||||||
time_t end_time_;
|
time_t end_time_;
|
||||||
unsigned error_count_;
|
unsigned error_count_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_code_page( int, void const* );
|
void set_code_page( int, void const* );
|
||||||
inline void update_end_time( time_t end, time_t irq );
|
inline void update_end_time( time_t end, time_t irq );
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NES_CPU_PAGE( addr ) ((unsigned) (addr) >> Nes_Cpu::page_bits)
|
#define NES_CPU_PAGE( addr ) ((unsigned) (addr) >> Nes_Cpu::page_bits)
|
||||||
|
|
||||||
#if BLARGG_NONPORTABLE
|
#if BLARGG_NONPORTABLE
|
||||||
#define NES_CPU_OFFSET( addr ) (addr)
|
#define NES_CPU_OFFSET( addr ) (addr)
|
||||||
#else
|
#else
|
||||||
#define NES_CPU_OFFSET( addr ) ((addr) & (Nes_Cpu::page_size - 1))
|
#define NES_CPU_OFFSET( addr ) ((addr) & (Nes_Cpu::page_size - 1))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
inline BOOST::uint8_t const* Nes_Cpu::get_code( addr_t addr ) const
|
inline BOOST::uint8_t const* Nes_Cpu::get_code( addr_t addr ) const
|
||||||
{
|
{
|
||||||
return cpu_state_.code_map [NES_CPU_PAGE( addr )] + NES_CPU_OFFSET( addr );
|
return cpu_state_.code_map [NES_CPU_PAGE( addr )] + NES_CPU_OFFSET( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Cpu::update_end_time( time_t end, time_t irq )
|
inline void Nes_Cpu::update_end_time( time_t end, time_t irq )
|
||||||
{
|
{
|
||||||
if ( end > irq && !(r.flags & irq_inhibit_mask) )
|
if ( end > irq && !(r.flags & irq_inhibit_mask) )
|
||||||
end = irq;
|
end = irq;
|
||||||
|
|
||||||
cpu_state->time += cpu_state->base - end;
|
cpu_state->time += cpu_state->base - end;
|
||||||
cpu_state->base = end;
|
cpu_state->base = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Cpu::set_irq_time( time_t t )
|
inline void Nes_Cpu::set_irq_time( time_t t )
|
||||||
{
|
{
|
||||||
irq_time_ = t;
|
irq_time_ = t;
|
||||||
update_end_time( end_time_, t );
|
update_end_time( end_time_, t );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Cpu::set_end_time( time_t t )
|
inline void Nes_Cpu::set_end_time( time_t t )
|
||||||
{
|
{
|
||||||
end_time_ = t;
|
end_time_ = t;
|
||||||
update_end_time( t, irq_time_ );
|
update_end_time( t, irq_time_ );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,280 +1,280 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nes_Fds_Apu.h"
|
#include "Nes_Fds_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006 Shay Green. This module is free software; you
|
/* Copyright (C) 2006 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const fract_range = 65536;
|
int const fract_range = 65536;
|
||||||
|
|
||||||
void Nes_Fds_Apu::reset()
|
void Nes_Fds_Apu::reset()
|
||||||
{
|
{
|
||||||
memset( regs_, 0, sizeof regs_ );
|
memset( regs_, 0, sizeof regs_ );
|
||||||
memset( mod_wave, 0, sizeof mod_wave );
|
memset( mod_wave, 0, sizeof mod_wave );
|
||||||
|
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
env_delay = 0;
|
env_delay = 0;
|
||||||
sweep_delay = 0;
|
sweep_delay = 0;
|
||||||
wave_pos = 0;
|
wave_pos = 0;
|
||||||
last_amp = 0;
|
last_amp = 0;
|
||||||
wave_fract = fract_range;
|
wave_fract = fract_range;
|
||||||
mod_fract = fract_range;
|
mod_fract = fract_range;
|
||||||
mod_pos = 0;
|
mod_pos = 0;
|
||||||
mod_write_pos = 0;
|
mod_write_pos = 0;
|
||||||
|
|
||||||
static byte const initial_regs [0x0B] = {
|
static byte const initial_regs [0x0B] = {
|
||||||
0x80, // disable envelope
|
0x80, // disable envelope
|
||||||
0, 0, 0xC0, // disable wave and lfo
|
0, 0, 0xC0, // disable wave and lfo
|
||||||
0x80, // disable sweep
|
0x80, // disable sweep
|
||||||
0, 0, 0x80, // disable modulation
|
0, 0, 0x80, // disable modulation
|
||||||
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
|
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
|
||||||
};
|
};
|
||||||
for ( int i = 0; i < (int) sizeof initial_regs; i++ )
|
for ( int i = 0; i < (int) sizeof initial_regs; i++ )
|
||||||
{
|
{
|
||||||
// two writes to set both gain and period for envelope registers
|
// two writes to set both gain and period for envelope registers
|
||||||
write_( io_addr + wave_size + i, 0 );
|
write_( io_addr + wave_size + i, 0 );
|
||||||
write_( io_addr + wave_size + i, initial_regs [i] );
|
write_( io_addr + wave_size + i, initial_regs [i] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Fds_Apu::write_( unsigned addr, int data )
|
void Nes_Fds_Apu::write_( unsigned addr, int data )
|
||||||
{
|
{
|
||||||
unsigned reg = addr - io_addr;
|
unsigned reg = addr - io_addr;
|
||||||
if ( reg < io_size )
|
if ( reg < io_size )
|
||||||
{
|
{
|
||||||
if ( reg < wave_size )
|
if ( reg < wave_size )
|
||||||
{
|
{
|
||||||
if ( regs (0x4089) & 0x80 )
|
if ( regs (0x4089) & 0x80 )
|
||||||
regs_ [reg] = data & wave_sample_max;
|
regs_ [reg] = data & wave_sample_max;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
regs_ [reg] = data;
|
regs_ [reg] = data;
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x4080:
|
case 0x4080:
|
||||||
if ( data & 0x80 )
|
if ( data & 0x80 )
|
||||||
env_gain = data & 0x3F;
|
env_gain = data & 0x3F;
|
||||||
else
|
else
|
||||||
env_speed = (data & 0x3F) + 1;
|
env_speed = (data & 0x3F) + 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4084:
|
case 0x4084:
|
||||||
if ( data & 0x80 )
|
if ( data & 0x80 )
|
||||||
sweep_gain = data & 0x3F;
|
sweep_gain = data & 0x3F;
|
||||||
else
|
else
|
||||||
sweep_speed = (data & 0x3F) + 1;
|
sweep_speed = (data & 0x3F) + 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4085:
|
case 0x4085:
|
||||||
mod_pos = mod_write_pos;
|
mod_pos = mod_write_pos;
|
||||||
regs (0x4085) = data & 0x7F;
|
regs (0x4085) = data & 0x7F;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4088:
|
case 0x4088:
|
||||||
if ( regs (0x4087) & 0x80 )
|
if ( regs (0x4087) & 0x80 )
|
||||||
{
|
{
|
||||||
int pos = mod_write_pos;
|
int pos = mod_write_pos;
|
||||||
data &= 0x07;
|
data &= 0x07;
|
||||||
mod_wave [pos ] = data;
|
mod_wave [pos ] = data;
|
||||||
mod_wave [pos + 1] = data;
|
mod_wave [pos + 1] = data;
|
||||||
mod_write_pos = (pos + 2) & (wave_size - 1);
|
mod_write_pos = (pos + 2) & (wave_size - 1);
|
||||||
mod_pos = (mod_pos + 2) & (wave_size - 1);
|
mod_pos = (mod_pos + 2) & (wave_size - 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Fds_Apu::set_tempo( double t )
|
void Nes_Fds_Apu::set_tempo( double t )
|
||||||
{
|
{
|
||||||
lfo_tempo = lfo_base_tempo;
|
lfo_tempo = lfo_base_tempo;
|
||||||
if ( t != 1.0 )
|
if ( t != 1.0 )
|
||||||
{
|
{
|
||||||
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
|
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
|
||||||
if ( lfo_tempo <= 0 )
|
if ( lfo_tempo <= 0 )
|
||||||
lfo_tempo = 1;
|
lfo_tempo = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
|
void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
|
||||||
{
|
{
|
||||||
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
|
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
|
||||||
Blip_Buffer* const output_ = this->output_;
|
Blip_Buffer* const output_ = this->output_;
|
||||||
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
|
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
|
||||||
{
|
{
|
||||||
output_->set_modified();
|
output_->set_modified();
|
||||||
|
|
||||||
// master_volume
|
// master_volume
|
||||||
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
|
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
|
||||||
static unsigned char const master_volumes [4] = {
|
static unsigned char const master_volumes [4] = {
|
||||||
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
|
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
|
||||||
};
|
};
|
||||||
int const master_volume = master_volumes [regs (0x4089) & 0x03];
|
int const master_volume = master_volumes [regs (0x4089) & 0x03];
|
||||||
|
|
||||||
// lfo_period
|
// lfo_period
|
||||||
blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
|
blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
|
||||||
if ( regs (0x4083) & 0x40 )
|
if ( regs (0x4083) & 0x40 )
|
||||||
lfo_period = 0;
|
lfo_period = 0;
|
||||||
|
|
||||||
// sweep setup
|
// sweep setup
|
||||||
blip_time_t sweep_time = last_time + sweep_delay;
|
blip_time_t sweep_time = last_time + sweep_delay;
|
||||||
blip_time_t const sweep_period = lfo_period * sweep_speed;
|
blip_time_t const sweep_period = lfo_period * sweep_speed;
|
||||||
if ( !sweep_period || regs (0x4084) & 0x80 )
|
if ( !sweep_period || regs (0x4084) & 0x80 )
|
||||||
sweep_time = final_end_time;
|
sweep_time = final_end_time;
|
||||||
|
|
||||||
// envelope setup
|
// envelope setup
|
||||||
blip_time_t env_time = last_time + env_delay;
|
blip_time_t env_time = last_time + env_delay;
|
||||||
blip_time_t const env_period = lfo_period * env_speed;
|
blip_time_t const env_period = lfo_period * env_speed;
|
||||||
if ( !env_period || regs (0x4080) & 0x80 )
|
if ( !env_period || regs (0x4080) & 0x80 )
|
||||||
env_time = final_end_time;
|
env_time = final_end_time;
|
||||||
|
|
||||||
// modulation
|
// modulation
|
||||||
int mod_freq = 0;
|
int mod_freq = 0;
|
||||||
if ( !(regs (0x4087) & 0x80) )
|
if ( !(regs (0x4087) & 0x80) )
|
||||||
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
|
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
|
||||||
|
|
||||||
blip_time_t end_time = last_time;
|
blip_time_t end_time = last_time;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// sweep
|
// sweep
|
||||||
if ( sweep_time <= end_time )
|
if ( sweep_time <= end_time )
|
||||||
{
|
{
|
||||||
sweep_time += sweep_period;
|
sweep_time += sweep_period;
|
||||||
int mode = regs (0x4084) >> 5 & 2;
|
int mode = regs (0x4084) >> 5 & 2;
|
||||||
int new_sweep_gain = sweep_gain + mode - 1;
|
int new_sweep_gain = sweep_gain + mode - 1;
|
||||||
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
|
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
|
||||||
sweep_gain = new_sweep_gain;
|
sweep_gain = new_sweep_gain;
|
||||||
else
|
else
|
||||||
regs (0x4084) |= 0x80; // optimization only
|
regs (0x4084) |= 0x80; // optimization only
|
||||||
}
|
}
|
||||||
|
|
||||||
// envelope
|
// envelope
|
||||||
if ( env_time <= end_time )
|
if ( env_time <= end_time )
|
||||||
{
|
{
|
||||||
env_time += env_period;
|
env_time += env_period;
|
||||||
int mode = regs (0x4080) >> 5 & 2;
|
int mode = regs (0x4080) >> 5 & 2;
|
||||||
int new_env_gain = env_gain + mode - 1;
|
int new_env_gain = env_gain + mode - 1;
|
||||||
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
|
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
|
||||||
env_gain = new_env_gain;
|
env_gain = new_env_gain;
|
||||||
else
|
else
|
||||||
regs (0x4080) |= 0x80; // optimization only
|
regs (0x4080) |= 0x80; // optimization only
|
||||||
}
|
}
|
||||||
|
|
||||||
// new end_time
|
// new end_time
|
||||||
blip_time_t const start_time = end_time;
|
blip_time_t const start_time = end_time;
|
||||||
end_time = final_end_time;
|
end_time = final_end_time;
|
||||||
if ( end_time > env_time ) end_time = env_time;
|
if ( end_time > env_time ) end_time = env_time;
|
||||||
if ( end_time > sweep_time ) end_time = sweep_time;
|
if ( end_time > sweep_time ) end_time = sweep_time;
|
||||||
|
|
||||||
// frequency modulation
|
// frequency modulation
|
||||||
int freq = wave_freq;
|
int freq = wave_freq;
|
||||||
if ( mod_freq )
|
if ( mod_freq )
|
||||||
{
|
{
|
||||||
// time of next modulation clock
|
// time of next modulation clock
|
||||||
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
|
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
|
||||||
if ( end_time > mod_time )
|
if ( end_time > mod_time )
|
||||||
end_time = mod_time;
|
end_time = mod_time;
|
||||||
|
|
||||||
// run modulator up to next clock and save old sweep_bias
|
// run modulator up to next clock and save old sweep_bias
|
||||||
int sweep_bias = regs (0x4085);
|
int sweep_bias = regs (0x4085);
|
||||||
mod_fract -= (end_time - start_time) * mod_freq;
|
mod_fract -= (end_time - start_time) * mod_freq;
|
||||||
if ( mod_fract <= 0 )
|
if ( mod_fract <= 0 )
|
||||||
{
|
{
|
||||||
mod_fract += fract_range;
|
mod_fract += fract_range;
|
||||||
check( (unsigned) mod_fract <= fract_range );
|
check( (unsigned) mod_fract <= fract_range );
|
||||||
|
|
||||||
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
|
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
|
||||||
int mod = mod_wave [mod_pos];
|
int mod = mod_wave [mod_pos];
|
||||||
mod_pos = (mod_pos + 1) & (wave_size - 1);
|
mod_pos = (mod_pos + 1) & (wave_size - 1);
|
||||||
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
|
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
|
||||||
if ( mod == 4 )
|
if ( mod == 4 )
|
||||||
new_sweep_bias = 0;
|
new_sweep_bias = 0;
|
||||||
regs (0x4085) = new_sweep_bias;
|
regs (0x4085) = new_sweep_bias;
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply frequency modulation
|
// apply frequency modulation
|
||||||
sweep_bias = (sweep_bias ^ 0x40) - 0x40;
|
sweep_bias = (sweep_bias ^ 0x40) - 0x40;
|
||||||
int factor = sweep_bias * sweep_gain;
|
int factor = sweep_bias * sweep_gain;
|
||||||
int extra = factor & 0x0F;
|
int extra = factor & 0x0F;
|
||||||
factor >>= 4;
|
factor >>= 4;
|
||||||
if ( extra )
|
if ( extra )
|
||||||
{
|
{
|
||||||
factor--;
|
factor--;
|
||||||
if ( sweep_bias >= 0 )
|
if ( sweep_bias >= 0 )
|
||||||
factor += 3;
|
factor += 3;
|
||||||
}
|
}
|
||||||
if ( factor > 193 ) factor -= 258;
|
if ( factor > 193 ) factor -= 258;
|
||||||
if ( factor < -64 ) factor += 256;
|
if ( factor < -64 ) factor += 256;
|
||||||
freq += (freq * factor) >> 6;
|
freq += (freq * factor) >> 6;
|
||||||
if ( freq <= 0 )
|
if ( freq <= 0 )
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// wave
|
// wave
|
||||||
int wave_fract = this->wave_fract;
|
int wave_fract = this->wave_fract;
|
||||||
blip_time_t delay = (wave_fract + freq - 1) / freq;
|
blip_time_t delay = (wave_fract + freq - 1) / freq;
|
||||||
blip_time_t time = start_time + delay;
|
blip_time_t time = start_time + delay;
|
||||||
|
|
||||||
if ( time <= end_time )
|
if ( time <= end_time )
|
||||||
{
|
{
|
||||||
// at least one wave clock within start_time...end_time
|
// at least one wave clock within start_time...end_time
|
||||||
|
|
||||||
blip_time_t const min_delay = fract_range / freq;
|
blip_time_t const min_delay = fract_range / freq;
|
||||||
int wave_pos = this->wave_pos;
|
int wave_pos = this->wave_pos;
|
||||||
|
|
||||||
int volume = env_gain;
|
int volume = env_gain;
|
||||||
if ( volume > vol_max )
|
if ( volume > vol_max )
|
||||||
volume = vol_max;
|
volume = vol_max;
|
||||||
volume *= master_volume;
|
volume *= master_volume;
|
||||||
|
|
||||||
int const min_fract = min_delay * freq;
|
int const min_fract = min_delay * freq;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// clock wave
|
// clock wave
|
||||||
int amp = regs_ [wave_pos] * volume;
|
int amp = regs_ [wave_pos] * volume;
|
||||||
wave_pos = (wave_pos + 1) & (wave_size - 1);
|
wave_pos = (wave_pos + 1) & (wave_size - 1);
|
||||||
int delta = amp - last_amp;
|
int delta = amp - last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
last_amp = amp;
|
last_amp = amp;
|
||||||
synth.offset_inline( time, delta, output_ );
|
synth.offset_inline( time, delta, output_ );
|
||||||
}
|
}
|
||||||
|
|
||||||
wave_fract += fract_range - delay * freq;
|
wave_fract += fract_range - delay * freq;
|
||||||
check( unsigned (fract_range - wave_fract) < freq );
|
check( unsigned (fract_range - wave_fract) < freq );
|
||||||
|
|
||||||
// delay until next clock
|
// delay until next clock
|
||||||
delay = min_delay;
|
delay = min_delay;
|
||||||
if ( wave_fract > min_fract )
|
if ( wave_fract > min_fract )
|
||||||
delay++;
|
delay++;
|
||||||
check( delay && delay == (wave_fract + freq - 1) / freq );
|
check( delay && delay == (wave_fract + freq - 1) / freq );
|
||||||
|
|
||||||
time += delay;
|
time += delay;
|
||||||
}
|
}
|
||||||
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
|
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
|
||||||
|
|
||||||
this->wave_pos = wave_pos;
|
this->wave_pos = wave_pos;
|
||||||
}
|
}
|
||||||
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
|
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
|
||||||
check( this->wave_fract > 0 );
|
check( this->wave_fract > 0 );
|
||||||
}
|
}
|
||||||
while ( end_time < final_end_time );
|
while ( end_time < final_end_time );
|
||||||
|
|
||||||
env_delay = env_time - final_end_time; check( env_delay >= 0 );
|
env_delay = env_time - final_end_time; check( env_delay >= 0 );
|
||||||
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
|
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
|
||||||
}
|
}
|
||||||
last_time = final_end_time;
|
last_time = final_end_time;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,139 +1,139 @@
|
||||||
// NES FDS sound chip emulator
|
// NES FDS sound chip emulator
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef NES_FDS_APU_H
|
#ifndef NES_FDS_APU_H
|
||||||
#define NES_FDS_APU_H
|
#define NES_FDS_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Nes_Fds_Apu {
|
class Nes_Fds_Apu {
|
||||||
public:
|
public:
|
||||||
// setup
|
// setup
|
||||||
void set_tempo( double );
|
void set_tempo( double );
|
||||||
enum { osc_count = 1 };
|
enum { osc_count = 1 };
|
||||||
void set_output( Blip_Buffer* buf );
|
void set_output( Blip_Buffer* buf );
|
||||||
void volume( double );
|
void volume( double );
|
||||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||||
|
|
||||||
// emulation
|
// emulation
|
||||||
void reset();
|
void reset();
|
||||||
enum { io_addr = 0x4040 };
|
enum { io_addr = 0x4040 };
|
||||||
enum { io_size = 0x53 };
|
enum { io_size = 0x53 };
|
||||||
void write( blip_time_t time, unsigned addr, int data );
|
void write( blip_time_t time, unsigned addr, int data );
|
||||||
int read( blip_time_t time, unsigned addr );
|
int read( blip_time_t time, unsigned addr );
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Nes_Fds_Apu();
|
Nes_Fds_Apu();
|
||||||
void write_( unsigned addr, int data );
|
void write_( unsigned addr, int data );
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
|
|
||||||
void set_output( int index, Blip_Buffer* center,
|
void set_output( int index, Blip_Buffer* center,
|
||||||
Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL );
|
Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL );
|
||||||
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; )
|
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; )
|
||||||
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; )
|
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; )
|
||||||
BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; )
|
BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; )
|
||||||
void osc_output( int, Blip_Buffer* );
|
void osc_output( int, Blip_Buffer* );
|
||||||
private:
|
private:
|
||||||
enum { wave_size = 0x40 };
|
enum { wave_size = 0x40 };
|
||||||
enum { master_vol_max = 10 };
|
enum { master_vol_max = 10 };
|
||||||
enum { vol_max = 0x20 };
|
enum { vol_max = 0x20 };
|
||||||
enum { wave_sample_max = 0x3F };
|
enum { wave_sample_max = 0x3F };
|
||||||
|
|
||||||
unsigned char regs_ [io_size];// last written value to registers
|
unsigned char regs_ [io_size];// last written value to registers
|
||||||
|
|
||||||
enum { lfo_base_tempo = 8 };
|
enum { lfo_base_tempo = 8 };
|
||||||
int lfo_tempo; // normally 8; adjusted by set_tempo()
|
int lfo_tempo; // normally 8; adjusted by set_tempo()
|
||||||
|
|
||||||
int env_delay;
|
int env_delay;
|
||||||
int env_speed;
|
int env_speed;
|
||||||
int env_gain;
|
int env_gain;
|
||||||
|
|
||||||
int sweep_delay;
|
int sweep_delay;
|
||||||
int sweep_speed;
|
int sweep_speed;
|
||||||
int sweep_gain;
|
int sweep_gain;
|
||||||
|
|
||||||
int wave_pos;
|
int wave_pos;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
blip_time_t wave_fract;
|
blip_time_t wave_fract;
|
||||||
|
|
||||||
int mod_fract;
|
int mod_fract;
|
||||||
int mod_pos;
|
int mod_pos;
|
||||||
int mod_write_pos;
|
int mod_write_pos;
|
||||||
unsigned char mod_wave [wave_size];
|
unsigned char mod_wave [wave_size];
|
||||||
|
|
||||||
// synthesis
|
// synthesis
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
Blip_Buffer* output_;
|
Blip_Buffer* output_;
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
// allow access to registers by absolute address (i.e. 0x4080)
|
// allow access to registers by absolute address (i.e. 0x4080)
|
||||||
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; }
|
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; }
|
||||||
|
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Nes_Fds_Apu::volume( double v )
|
inline void Nes_Fds_Apu::volume( double v )
|
||||||
{
|
{
|
||||||
synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v );
|
synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fds_Apu::set_output( Blip_Buffer* b )
|
inline void Nes_Fds_Apu::set_output( Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
output_ = b;
|
output_ = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
|
inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
|
||||||
{
|
{
|
||||||
assert( (unsigned) i < osc_count );
|
assert( (unsigned) i < osc_count );
|
||||||
output_ = buf;
|
output_ = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fds_Apu::end_frame( blip_time_t end_time )
|
inline void Nes_Fds_Apu::end_frame( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
if ( end_time > last_time )
|
if ( end_time > last_time )
|
||||||
run_until( end_time );
|
run_until( end_time );
|
||||||
last_time -= end_time;
|
last_time -= end_time;
|
||||||
assert( last_time >= 0 );
|
assert( last_time >= 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data )
|
inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data )
|
||||||
{
|
{
|
||||||
run_until( time );
|
run_until( time );
|
||||||
write_( addr, data );
|
write_( addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
|
inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
|
||||||
{
|
{
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
int result = 0xFF;
|
int result = 0xFF;
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x4090:
|
case 0x4090:
|
||||||
result = env_gain;
|
result = env_gain;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4092:
|
case 0x4092:
|
||||||
result = sweep_gain;
|
result = sweep_gain;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
unsigned i = addr - io_addr;
|
unsigned i = addr - io_addr;
|
||||||
if ( i < wave_size )
|
if ( i < wave_size )
|
||||||
result = regs_ [i];
|
result = regs_ [i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result | 0x40;
|
return result | 0x40;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Nes_Fds_Apu::Nes_Fds_Apu()
|
inline Nes_Fds_Apu::Nes_Fds_Apu()
|
||||||
{
|
{
|
||||||
lfo_tempo = lfo_base_tempo;
|
lfo_tempo = lfo_base_tempo;
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,121 +1,121 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nes_Fme7_Apu.h"
|
#include "Nes_Fme7_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
void Nes_Fme7_Apu::reset()
|
void Nes_Fme7_Apu::reset()
|
||||||
{
|
{
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
|
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
oscs [i].last_amp = 0;
|
oscs [i].last_amp = 0;
|
||||||
|
|
||||||
fme7_apu_state_t* state = this;
|
fme7_apu_state_t* state = this;
|
||||||
memset( state, 0, sizeof *state );
|
memset( state, 0, sizeof *state );
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char const Nes_Fme7_Apu::amp_table [16] =
|
unsigned char const Nes_Fme7_Apu::amp_table [16] =
|
||||||
{
|
{
|
||||||
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
|
#define ENTRY( n ) (unsigned char) (n * amp_range + 0.5)
|
||||||
ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156),
|
ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156),
|
||||||
ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624),
|
ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624),
|
||||||
ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
|
ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498),
|
||||||
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
|
ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000)
|
||||||
#undef ENTRY
|
#undef ENTRY
|
||||||
};
|
};
|
||||||
|
|
||||||
void Nes_Fme7_Apu::run_until( blip_time_t end_time )
|
void Nes_Fme7_Apu::run_until( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
require( end_time >= last_time );
|
require( end_time >= last_time );
|
||||||
|
|
||||||
for ( int index = 0; index < osc_count; index++ )
|
for ( int index = 0; index < osc_count; index++ )
|
||||||
{
|
{
|
||||||
int mode = regs [7] >> index;
|
int mode = regs [7] >> index;
|
||||||
int vol_mode = regs [010 + index];
|
int vol_mode = regs [010 + index];
|
||||||
int volume = amp_table [vol_mode & 0x0F];
|
int volume = amp_table [vol_mode & 0x0F];
|
||||||
|
|
||||||
Blip_Buffer* const osc_output = oscs [index].output;
|
Blip_Buffer* const osc_output = oscs [index].output;
|
||||||
if ( !osc_output )
|
if ( !osc_output )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// check for unsupported mode
|
// check for unsupported mode
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
if ( (mode & 011) <= 001 && vol_mode & 0x1F )
|
if ( (mode & 011) <= 001 && vol_mode & 0x1F )
|
||||||
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
|
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
|
||||||
mode, vol_mode & 0x1F );
|
mode, vol_mode & 0x1F );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ( (mode & 001) | (vol_mode & 0x10) )
|
if ( (mode & 001) | (vol_mode & 0x10) )
|
||||||
volume = 0; // noise and envelope aren't supported
|
volume = 0; // noise and envelope aren't supported
|
||||||
|
|
||||||
// period
|
// period
|
||||||
int const period_factor = 16;
|
int const period_factor = 16;
|
||||||
unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor +
|
unsigned period = (regs [index * 2 + 1] & 0x0F) * 0x100 * period_factor +
|
||||||
regs [index * 2] * period_factor;
|
regs [index * 2] * period_factor;
|
||||||
if ( period < 50 ) // around 22 kHz
|
if ( period < 50 ) // around 22 kHz
|
||||||
{
|
{
|
||||||
volume = 0;
|
volume = 0;
|
||||||
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
|
if ( !period ) // on my AY-3-8910A, period doesn't have extra one added
|
||||||
period = period_factor;
|
period = period_factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// current amplitude
|
// current amplitude
|
||||||
int amp = volume;
|
int amp = volume;
|
||||||
if ( !phases [index] )
|
if ( !phases [index] )
|
||||||
amp = 0;
|
amp = 0;
|
||||||
|
|
||||||
{
|
{
|
||||||
int delta = amp - oscs [index].last_amp;
|
int delta = amp - oscs [index].last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
oscs [index].last_amp = amp;
|
oscs [index].last_amp = amp;
|
||||||
osc_output->set_modified();
|
osc_output->set_modified();
|
||||||
synth.offset( last_time, delta, osc_output );
|
synth.offset( last_time, delta, osc_output );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blip_time_t time = last_time + delays [index];
|
blip_time_t time = last_time + delays [index];
|
||||||
if ( time < end_time )
|
if ( time < end_time )
|
||||||
{
|
{
|
||||||
int delta = amp * 2 - volume;
|
int delta = amp * 2 - volume;
|
||||||
osc_output->set_modified();
|
osc_output->set_modified();
|
||||||
if ( volume )
|
if ( volume )
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
delta = -delta;
|
delta = -delta;
|
||||||
synth.offset_inline( time, delta, osc_output );
|
synth.offset_inline( time, delta, osc_output );
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
|
|
||||||
oscs [index].last_amp = (delta + volume) >> 1;
|
oscs [index].last_amp = (delta + volume) >> 1;
|
||||||
phases [index] = (delta > 0);
|
phases [index] = (delta > 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// maintain phase when silent
|
// maintain phase when silent
|
||||||
int count = (end_time - time + period - 1) / period;
|
int count = (end_time - time + period - 1) / period;
|
||||||
phases [index] ^= count & 1;
|
phases [index] ^= count & 1;
|
||||||
time += count * period;
|
time += count * period;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delays [index] = time - end_time;
|
delays [index] = time - end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_time = end_time;
|
last_time = end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,131 +1,131 @@
|
||||||
// Sunsoft FME-7 sound emulator
|
// Sunsoft FME-7 sound emulator
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef NES_FME7_APU_H
|
#ifndef NES_FME7_APU_H
|
||||||
#define NES_FME7_APU_H
|
#define NES_FME7_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
struct fme7_apu_state_t
|
struct fme7_apu_state_t
|
||||||
{
|
{
|
||||||
enum { reg_count = 14 };
|
enum { reg_count = 14 };
|
||||||
BOOST::uint8_t regs [reg_count];
|
BOOST::uint8_t regs [reg_count];
|
||||||
BOOST::uint8_t phases [3]; // 0 or 1
|
BOOST::uint8_t phases [3]; // 0 or 1
|
||||||
BOOST::uint8_t latch;
|
BOOST::uint8_t latch;
|
||||||
BOOST::uint16_t delays [3]; // a, b, c
|
BOOST::uint16_t delays [3]; // a, b, c
|
||||||
};
|
};
|
||||||
|
|
||||||
class Nes_Fme7_Apu : private fme7_apu_state_t {
|
class Nes_Fme7_Apu : private fme7_apu_state_t {
|
||||||
public:
|
public:
|
||||||
// See Nes_Apu.h for reference
|
// See Nes_Apu.h for reference
|
||||||
void reset();
|
void reset();
|
||||||
void volume( double );
|
void volume( double );
|
||||||
void treble_eq( blip_eq_t const& );
|
void treble_eq( blip_eq_t const& );
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
enum { osc_count = 3 };
|
enum { osc_count = 3 };
|
||||||
void set_output( int index, Blip_Buffer* );
|
void set_output( int index, Blip_Buffer* );
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
void save_state( fme7_apu_state_t* ) const;
|
void save_state( fme7_apu_state_t* ) const;
|
||||||
void load_state( fme7_apu_state_t const& );
|
void load_state( fme7_apu_state_t const& );
|
||||||
|
|
||||||
// Mask and addresses of registers
|
// Mask and addresses of registers
|
||||||
enum { addr_mask = 0xE000 };
|
enum { addr_mask = 0xE000 };
|
||||||
enum { data_addr = 0xE000 };
|
enum { data_addr = 0xE000 };
|
||||||
enum { latch_addr = 0xC000 };
|
enum { latch_addr = 0xC000 };
|
||||||
|
|
||||||
// (addr & addr_mask) == latch_addr
|
// (addr & addr_mask) == latch_addr
|
||||||
void write_latch( int );
|
void write_latch( int );
|
||||||
|
|
||||||
// (addr & addr_mask) == data_addr
|
// (addr & addr_mask) == data_addr
|
||||||
void write_data( blip_time_t, int data );
|
void write_data( blip_time_t, int data );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Nes_Fme7_Apu();
|
Nes_Fme7_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Nes_Fme7_Apu( const Nes_Fme7_Apu& );
|
Nes_Fme7_Apu( const Nes_Fme7_Apu& );
|
||||||
Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& );
|
Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& );
|
||||||
|
|
||||||
static unsigned char const amp_table [16];
|
static unsigned char const amp_table [16];
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
} oscs [osc_count];
|
} oscs [osc_count];
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
|
|
||||||
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
|
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
|
||||||
Blip_Synth_Norm synth;
|
Blip_Synth_Norm synth;
|
||||||
|
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::volume( double v )
|
inline void Nes_Fme7_Apu::volume( double v )
|
||||||
{
|
{
|
||||||
synth.volume( 0.38 / amp_range * v ); // to do: fine-tune
|
synth.volume( 0.38 / amp_range * v ); // to do: fine-tune
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq )
|
inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq )
|
||||||
{
|
{
|
||||||
synth.treble_eq( eq );
|
synth.treble_eq( eq );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::set_output( int i, Blip_Buffer* buf )
|
inline void Nes_Fme7_Apu::set_output( int i, Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
assert( (unsigned) i < osc_count );
|
assert( (unsigned) i < osc_count );
|
||||||
oscs [i].output = buf;
|
oscs [i].output = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::set_output( Blip_Buffer* buf )
|
inline void Nes_Fme7_Apu::set_output( Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
set_output( i, buf );
|
set_output( i, buf );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Nes_Fme7_Apu::Nes_Fme7_Apu()
|
inline Nes_Fme7_Apu::Nes_Fme7_Apu()
|
||||||
{
|
{
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; }
|
inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; }
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data )
|
inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data )
|
||||||
{
|
{
|
||||||
if ( (unsigned) latch >= reg_count )
|
if ( (unsigned) latch >= reg_count )
|
||||||
{
|
{
|
||||||
#ifdef dprintf
|
#ifdef dprintf
|
||||||
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
|
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
run_until( time );
|
run_until( time );
|
||||||
regs [latch] = data;
|
regs [latch] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::end_frame( blip_time_t time )
|
inline void Nes_Fme7_Apu::end_frame( blip_time_t time )
|
||||||
{
|
{
|
||||||
if ( time > last_time )
|
if ( time > last_time )
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
assert( last_time >= time );
|
assert( last_time >= time );
|
||||||
last_time -= time;
|
last_time -= time;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const
|
inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const
|
||||||
{
|
{
|
||||||
*out = *this;
|
*out = *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in )
|
inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in )
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
fme7_apu_state_t* state = this;
|
fme7_apu_state_t* state = this;
|
||||||
*state = in;
|
*state = in;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
// NES MMC5 sound chip emulator
|
// NES MMC5 sound chip emulator
|
||||||
|
|
||||||
// Nes_Snd_Emu $vers
|
// Nes_Snd_Emu $vers
|
||||||
#ifndef NES_MMC5_APU_H
|
#ifndef NES_MMC5_APU_H
|
||||||
#define NES_MMC5_APU_H
|
#define NES_MMC5_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Nes_Apu.h"
|
#include "Nes_Apu.h"
|
||||||
|
|
||||||
class Nes_Mmc5_Apu : public Nes_Apu {
|
class Nes_Mmc5_Apu : public Nes_Apu {
|
||||||
public:
|
public:
|
||||||
enum { regs_addr = 0x5000 };
|
enum { regs_addr = 0x5000 };
|
||||||
enum { regs_size = 0x16 };
|
enum { regs_size = 0x16 };
|
||||||
|
|
||||||
enum { osc_count = 3 };
|
enum { osc_count = 3 };
|
||||||
void write_register( blip_time_t, unsigned addr, int data );
|
void write_register( blip_time_t, unsigned addr, int data );
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
void set_output( int index, Blip_Buffer* );
|
void set_output( int index, Blip_Buffer* );
|
||||||
|
|
||||||
enum { exram_size = 1024 };
|
enum { exram_size = 1024 };
|
||||||
unsigned char exram [exram_size];
|
unsigned char exram [exram_size];
|
||||||
|
|
||||||
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; )
|
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; )
|
||||||
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; )
|
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; )
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b )
|
inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
// in: square 1, square 2, PCM
|
// in: square 1, square 2, PCM
|
||||||
// out: square 1, square 2, skipped, skipped, PCM
|
// out: square 1, square 2, skipped, skipped, PCM
|
||||||
if ( i > 1 )
|
if ( i > 1 )
|
||||||
i += 2;
|
i += 2;
|
||||||
Nes_Apu::set_output( i, b );
|
Nes_Apu::set_output( i, b );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b )
|
inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
set_output( 0, b );
|
set_output( 0, b );
|
||||||
set_output( 1, b );
|
set_output( 1, b );
|
||||||
set_output( 2, b );
|
set_output( 2, b );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )
|
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )
|
||||||
{
|
{
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x5015: // channel enables
|
case 0x5015: // channel enables
|
||||||
data &= 0x03; // enable the square waves only
|
data &= 0x03; // enable the square waves only
|
||||||
// fall through
|
// fall through
|
||||||
case 0x5000: // Square 1
|
case 0x5000: // Square 1
|
||||||
case 0x5002:
|
case 0x5002:
|
||||||
case 0x5003:
|
case 0x5003:
|
||||||
case 0x5004: // Square 2
|
case 0x5004: // Square 2
|
||||||
case 0x5006:
|
case 0x5006:
|
||||||
case 0x5007:
|
case 0x5007:
|
||||||
case 0x5011: // DAC
|
case 0x5011: // DAC
|
||||||
Nes_Apu::write_register( time, addr - 0x1000, data );
|
Nes_Apu::write_register( time, addr - 0x1000, data );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x5010: // some things write to this for some reason
|
case 0x5010: // some things write to this for some reason
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#ifdef BLARGG_DEBUG_H
|
#ifdef BLARGG_DEBUG_H
|
||||||
default:
|
default:
|
||||||
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
|
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,102 +1,102 @@
|
||||||
// Namco 106 sound chip emulator
|
// Namco 106 sound chip emulator
|
||||||
|
|
||||||
// Nes_Snd_Emu $vers
|
// Nes_Snd_Emu $vers
|
||||||
#ifndef NES_NAMCO_APU_H
|
#ifndef NES_NAMCO_APU_H
|
||||||
#define NES_NAMCO_APU_H
|
#define NES_NAMCO_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
struct namco_state_t;
|
struct namco_state_t;
|
||||||
|
|
||||||
class Nes_Namco_Apu {
|
class Nes_Namco_Apu {
|
||||||
public:
|
public:
|
||||||
// See Nes_Apu.h for reference.
|
// See Nes_Apu.h for reference.
|
||||||
void volume( double );
|
void volume( double );
|
||||||
void treble_eq( const blip_eq_t& );
|
void treble_eq( const blip_eq_t& );
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
enum { osc_count = 8 };
|
enum { osc_count = 8 };
|
||||||
void set_output( int index, Blip_Buffer* );
|
void set_output( int index, Blip_Buffer* );
|
||||||
void reset();
|
void reset();
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
|
|
||||||
// Read/write data register is at 0x4800
|
// Read/write data register is at 0x4800
|
||||||
enum { data_reg_addr = 0x4800 };
|
enum { data_reg_addr = 0x4800 };
|
||||||
void write_data( blip_time_t, int );
|
void write_data( blip_time_t, int );
|
||||||
int read_data();
|
int read_data();
|
||||||
|
|
||||||
// Write-only address register is at 0xF800
|
// Write-only address register is at 0xF800
|
||||||
enum { addr_reg_addr = 0xF800 };
|
enum { addr_reg_addr = 0xF800 };
|
||||||
void write_addr( int );
|
void write_addr( int );
|
||||||
|
|
||||||
// to do: implement save/restore
|
// to do: implement save/restore
|
||||||
void save_state( namco_state_t* out ) const;
|
void save_state( namco_state_t* out ) const;
|
||||||
void load_state( namco_state_t const& );
|
void load_state( namco_state_t const& );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Nes_Namco_Apu();
|
Nes_Namco_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Nes_Namco_Apu( const Nes_Namco_Apu& );
|
Nes_Namco_Apu( const Nes_Namco_Apu& );
|
||||||
Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& );
|
Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& );
|
||||||
|
|
||||||
struct Namco_Osc {
|
struct Namco_Osc {
|
||||||
int delay;
|
int delay;
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
short last_amp;
|
short last_amp;
|
||||||
short wave_pos;
|
short wave_pos;
|
||||||
};
|
};
|
||||||
|
|
||||||
Namco_Osc oscs [osc_count];
|
Namco_Osc oscs [osc_count];
|
||||||
|
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
int addr_reg;
|
int addr_reg;
|
||||||
|
|
||||||
enum { reg_count = 0x80 };
|
enum { reg_count = 0x80 };
|
||||||
BOOST::uint8_t reg [reg_count];
|
BOOST::uint8_t reg [reg_count];
|
||||||
Blip_Synth_Norm synth;
|
Blip_Synth_Norm synth;
|
||||||
|
|
||||||
BOOST::uint8_t& access();
|
BOOST::uint8_t& access();
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
struct namco_state_t
|
struct namco_state_t
|
||||||
{
|
{
|
||||||
BOOST::uint8_t regs [0x80];
|
BOOST::uint8_t regs [0x80];
|
||||||
BOOST::uint8_t addr;
|
BOOST::uint8_t addr;
|
||||||
BOOST::uint8_t unused;
|
BOOST::uint8_t unused;
|
||||||
BOOST::uint8_t positions [8];
|
BOOST::uint8_t positions [8];
|
||||||
BOOST::uint32_t delays [8];
|
BOOST::uint32_t delays [8];
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
inline BOOST::uint8_t& Nes_Namco_Apu::access()
|
inline BOOST::uint8_t& Nes_Namco_Apu::access()
|
||||||
{
|
{
|
||||||
int addr = addr_reg & 0x7F;
|
int addr = addr_reg & 0x7F;
|
||||||
if ( addr_reg & 0x80 )
|
if ( addr_reg & 0x80 )
|
||||||
addr_reg = (addr + 1) | 0x80;
|
addr_reg = (addr + 1) | 0x80;
|
||||||
return reg [addr];
|
return reg [addr];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count / 15 * v ); }
|
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count / 15 * v ); }
|
||||||
|
|
||||||
inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
|
inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
|
||||||
|
|
||||||
inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; }
|
inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; }
|
||||||
|
|
||||||
inline int Nes_Namco_Apu::read_data() { return access(); }
|
inline int Nes_Namco_Apu::read_data() { return access(); }
|
||||||
|
|
||||||
inline void Nes_Namco_Apu::set_output( int i, Blip_Buffer* buf )
|
inline void Nes_Namco_Apu::set_output( int i, Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
assert( (unsigned) i < osc_count );
|
assert( (unsigned) i < osc_count );
|
||||||
oscs [i].output = buf;
|
oscs [i].output = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Namco_Apu::write_data( blip_time_t time, int data )
|
inline void Nes_Namco_Apu::write_data( blip_time_t time, int data )
|
||||||
{
|
{
|
||||||
run_until( time );
|
run_until( time );
|
||||||
access() = data;
|
access() = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,147 +1,147 @@
|
||||||
// Private oscillators used by Nes_Apu
|
// Private oscillators used by Nes_Apu
|
||||||
|
|
||||||
// Nes_Snd_Emu $vers
|
// Nes_Snd_Emu $vers
|
||||||
#ifndef NES_OSCS_H
|
#ifndef NES_OSCS_H
|
||||||
#define NES_OSCS_H
|
#define NES_OSCS_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Nes_Apu;
|
class Nes_Apu;
|
||||||
|
|
||||||
struct Nes_Osc
|
struct Nes_Osc
|
||||||
{
|
{
|
||||||
typedef int nes_time_t;
|
typedef int nes_time_t;
|
||||||
|
|
||||||
unsigned char regs [4];
|
unsigned char regs [4];
|
||||||
bool reg_written [4];
|
bool reg_written [4];
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
int length_counter;// length counter (0 if unused by oscillator)
|
int length_counter;// length counter (0 if unused by oscillator)
|
||||||
int delay; // delay until next (potential) transition
|
int delay; // delay until next (potential) transition
|
||||||
int last_amp; // last amplitude oscillator was outputting
|
int last_amp; // last amplitude oscillator was outputting
|
||||||
|
|
||||||
void clock_length( int halt_mask );
|
void clock_length( int halt_mask );
|
||||||
int period() const {
|
int period() const {
|
||||||
return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF);
|
return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF);
|
||||||
}
|
}
|
||||||
void reset() {
|
void reset() {
|
||||||
delay = 0;
|
delay = 0;
|
||||||
last_amp = 0;
|
last_amp = 0;
|
||||||
}
|
}
|
||||||
int update_amp( int amp ) {
|
int update_amp( int amp ) {
|
||||||
int delta = amp - last_amp;
|
int delta = amp - last_amp;
|
||||||
last_amp = amp;
|
last_amp = amp;
|
||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Nes_Envelope : Nes_Osc
|
struct Nes_Envelope : Nes_Osc
|
||||||
{
|
{
|
||||||
int envelope;
|
int envelope;
|
||||||
int env_delay;
|
int env_delay;
|
||||||
|
|
||||||
void clock_envelope();
|
void clock_envelope();
|
||||||
int volume() const;
|
int volume() const;
|
||||||
void reset() {
|
void reset() {
|
||||||
envelope = 0;
|
envelope = 0;
|
||||||
env_delay = 0;
|
env_delay = 0;
|
||||||
Nes_Osc::reset();
|
Nes_Osc::reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nes_Square
|
// Nes_Square
|
||||||
struct Nes_Square : Nes_Envelope
|
struct Nes_Square : Nes_Envelope
|
||||||
{
|
{
|
||||||
enum { negate_flag = 0x08 };
|
enum { negate_flag = 0x08 };
|
||||||
enum { shift_mask = 0x07 };
|
enum { shift_mask = 0x07 };
|
||||||
enum { phase_range = 8 };
|
enum { phase_range = 8 };
|
||||||
int phase;
|
int phase;
|
||||||
int sweep_delay;
|
int sweep_delay;
|
||||||
|
|
||||||
typedef Blip_Synth_Norm Synth;
|
typedef Blip_Synth_Norm Synth;
|
||||||
Synth const& synth; // shared between squares
|
Synth const& synth; // shared between squares
|
||||||
|
|
||||||
Nes_Square( Synth const* s ) : synth( *s ) { }
|
Nes_Square( Synth const* s ) : synth( *s ) { }
|
||||||
|
|
||||||
void clock_sweep( int adjust );
|
void clock_sweep( int adjust );
|
||||||
void run( nes_time_t, nes_time_t );
|
void run( nes_time_t, nes_time_t );
|
||||||
void reset() {
|
void reset() {
|
||||||
sweep_delay = 0;
|
sweep_delay = 0;
|
||||||
Nes_Envelope::reset();
|
Nes_Envelope::reset();
|
||||||
}
|
}
|
||||||
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
|
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
|
||||||
nes_time_t timer_period );
|
nes_time_t timer_period );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nes_Triangle
|
// Nes_Triangle
|
||||||
struct Nes_Triangle : Nes_Osc
|
struct Nes_Triangle : Nes_Osc
|
||||||
{
|
{
|
||||||
enum { phase_range = 16 };
|
enum { phase_range = 16 };
|
||||||
int phase;
|
int phase;
|
||||||
int linear_counter;
|
int linear_counter;
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
int calc_amp() const;
|
int calc_amp() const;
|
||||||
void run( nes_time_t, nes_time_t );
|
void run( nes_time_t, nes_time_t );
|
||||||
void clock_linear_counter();
|
void clock_linear_counter();
|
||||||
void reset() {
|
void reset() {
|
||||||
linear_counter = 0;
|
linear_counter = 0;
|
||||||
phase = 1;
|
phase = 1;
|
||||||
Nes_Osc::reset();
|
Nes_Osc::reset();
|
||||||
}
|
}
|
||||||
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
|
nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time,
|
||||||
nes_time_t timer_period );
|
nes_time_t timer_period );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nes_Noise
|
// Nes_Noise
|
||||||
struct Nes_Noise : Nes_Envelope
|
struct Nes_Noise : Nes_Envelope
|
||||||
{
|
{
|
||||||
int noise;
|
int noise;
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
void run( nes_time_t, nes_time_t );
|
void run( nes_time_t, nes_time_t );
|
||||||
void reset() {
|
void reset() {
|
||||||
noise = 1 << 14;
|
noise = 1 << 14;
|
||||||
Nes_Envelope::reset();
|
Nes_Envelope::reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nes_Dmc
|
// Nes_Dmc
|
||||||
struct Nes_Dmc : Nes_Osc
|
struct Nes_Dmc : Nes_Osc
|
||||||
{
|
{
|
||||||
int address; // address of next byte to read
|
int address; // address of next byte to read
|
||||||
int period;
|
int period;
|
||||||
//int length_counter; // bytes remaining to play (already defined in Nes_Osc)
|
//int length_counter; // bytes remaining to play (already defined in Nes_Osc)
|
||||||
int buf;
|
int buf;
|
||||||
int bits_remain;
|
int bits_remain;
|
||||||
int bits;
|
int bits;
|
||||||
bool buf_full;
|
bool buf_full;
|
||||||
bool silence;
|
bool silence;
|
||||||
|
|
||||||
enum { loop_flag = 0x40 };
|
enum { loop_flag = 0x40 };
|
||||||
|
|
||||||
int dac;
|
int dac;
|
||||||
|
|
||||||
nes_time_t next_irq;
|
nes_time_t next_irq;
|
||||||
bool irq_enabled;
|
bool irq_enabled;
|
||||||
bool irq_flag;
|
bool irq_flag;
|
||||||
bool pal_mode;
|
bool pal_mode;
|
||||||
bool nonlinear;
|
bool nonlinear;
|
||||||
|
|
||||||
Nes_Apu* apu;
|
Nes_Apu* apu;
|
||||||
|
|
||||||
Blip_Synth_Fast synth;
|
Blip_Synth_Fast synth;
|
||||||
|
|
||||||
int update_amp_nonlinear( int dac_in );
|
int update_amp_nonlinear( int dac_in );
|
||||||
void start();
|
void start();
|
||||||
void write_register( int, int );
|
void write_register( int, int );
|
||||||
void run( nes_time_t, nes_time_t );
|
void run( nes_time_t, nes_time_t );
|
||||||
void recalc_irq();
|
void recalc_irq();
|
||||||
void fill_buffer();
|
void fill_buffer();
|
||||||
void reload_sample();
|
void reload_sample();
|
||||||
void reset();
|
void reset();
|
||||||
int count_reads( nes_time_t, nes_time_t* ) const;
|
int count_reads( nes_time_t, nes_time_t* ) const;
|
||||||
nes_time_t next_read_time() const;
|
nes_time_t next_read_time() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,216 +1,216 @@
|
||||||
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
|
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nes_Vrc6_Apu.h"
|
#include "Nes_Vrc6_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf )
|
void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
set_output( i, buf );
|
set_output( i, buf );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::reset()
|
void Nes_Vrc6_Apu::reset()
|
||||||
{
|
{
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
{
|
{
|
||||||
Vrc6_Osc& osc = oscs [i];
|
Vrc6_Osc& osc = oscs [i];
|
||||||
for ( int j = 0; j < reg_count; j++ )
|
for ( int j = 0; j < reg_count; j++ )
|
||||||
osc.regs [j] = 0;
|
osc.regs [j] = 0;
|
||||||
osc.delay = 0;
|
osc.delay = 0;
|
||||||
osc.last_amp = 0;
|
osc.last_amp = 0;
|
||||||
osc.phase = 1;
|
osc.phase = 1;
|
||||||
osc.amp = 0;
|
osc.amp = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Nes_Vrc6_Apu::Nes_Vrc6_Apu()
|
Nes_Vrc6_Apu::Nes_Vrc6_Apu()
|
||||||
{
|
{
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::run_until( blip_time_t time )
|
void Nes_Vrc6_Apu::run_until( blip_time_t time )
|
||||||
{
|
{
|
||||||
require( time >= last_time );
|
require( time >= last_time );
|
||||||
run_square( oscs [0], time );
|
run_square( oscs [0], time );
|
||||||
run_square( oscs [1], time );
|
run_square( oscs [1], time );
|
||||||
run_saw( time );
|
run_saw( time );
|
||||||
last_time = time;
|
last_time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data )
|
void Nes_Vrc6_Apu::write_osc( blip_time_t time, int osc_index, int reg, int data )
|
||||||
{
|
{
|
||||||
require( (unsigned) osc_index < osc_count );
|
require( (unsigned) osc_index < osc_count );
|
||||||
require( (unsigned) reg < reg_count );
|
require( (unsigned) reg < reg_count );
|
||||||
|
|
||||||
run_until( time );
|
run_until( time );
|
||||||
oscs [osc_index].regs [reg] = data;
|
oscs [osc_index].regs [reg] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::end_frame( blip_time_t time )
|
void Nes_Vrc6_Apu::end_frame( blip_time_t time )
|
||||||
{
|
{
|
||||||
if ( time > last_time )
|
if ( time > last_time )
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
assert( last_time >= time );
|
assert( last_time >= time );
|
||||||
last_time -= time;
|
last_time -= time;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
|
void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const
|
||||||
{
|
{
|
||||||
assert( sizeof (vrc6_apu_state_t) == 20 );
|
assert( sizeof (vrc6_apu_state_t) == 20 );
|
||||||
out->saw_amp = oscs [2].amp;
|
out->saw_amp = oscs [2].amp;
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
{
|
{
|
||||||
Vrc6_Osc const& osc = oscs [i];
|
Vrc6_Osc const& osc = oscs [i];
|
||||||
for ( int r = 0; r < reg_count; r++ )
|
for ( int r = 0; r < reg_count; r++ )
|
||||||
out->regs [i] [r] = osc.regs [r];
|
out->regs [i] [r] = osc.regs [r];
|
||||||
|
|
||||||
out->delays [i] = osc.delay;
|
out->delays [i] = osc.delay;
|
||||||
out->phases [i] = osc.phase;
|
out->phases [i] = osc.phase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in )
|
void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in )
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
oscs [2].amp = in.saw_amp;
|
oscs [2].amp = in.saw_amp;
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
{
|
{
|
||||||
Vrc6_Osc& osc = oscs [i];
|
Vrc6_Osc& osc = oscs [i];
|
||||||
for ( int r = 0; r < reg_count; r++ )
|
for ( int r = 0; r < reg_count; r++ )
|
||||||
osc.regs [r] = in.regs [i] [r];
|
osc.regs [r] = in.regs [i] [r];
|
||||||
|
|
||||||
osc.delay = in.delays [i];
|
osc.delay = in.delays [i];
|
||||||
osc.phase = in.phases [i];
|
osc.phase = in.phases [i];
|
||||||
}
|
}
|
||||||
if ( !oscs [2].phase )
|
if ( !oscs [2].phase )
|
||||||
oscs [2].phase = 1;
|
oscs [2].phase = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
|
void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
|
||||||
{
|
{
|
||||||
Blip_Buffer* output = osc.output;
|
Blip_Buffer* output = osc.output;
|
||||||
if ( !output )
|
if ( !output )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int volume = osc.regs [0] & 15;
|
int volume = osc.regs [0] & 15;
|
||||||
if ( !(osc.regs [2] & 0x80) )
|
if ( !(osc.regs [2] & 0x80) )
|
||||||
volume = 0;
|
volume = 0;
|
||||||
|
|
||||||
int gate = osc.regs [0] & 0x80;
|
int gate = osc.regs [0] & 0x80;
|
||||||
int duty = ((osc.regs [0] >> 4) & 7) + 1;
|
int duty = ((osc.regs [0] >> 4) & 7) + 1;
|
||||||
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
|
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
|
||||||
blip_time_t time = last_time;
|
blip_time_t time = last_time;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
osc.last_amp += delta;
|
osc.last_amp += delta;
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
square_synth.offset( time, delta, output );
|
square_synth.offset( time, delta, output );
|
||||||
}
|
}
|
||||||
|
|
||||||
time += osc.delay;
|
time += osc.delay;
|
||||||
osc.delay = 0;
|
osc.delay = 0;
|
||||||
int period = osc.period();
|
int period = osc.period();
|
||||||
if ( volume && !gate && period > 4 )
|
if ( volume && !gate && period > 4 )
|
||||||
{
|
{
|
||||||
if ( time < end_time )
|
if ( time < end_time )
|
||||||
{
|
{
|
||||||
int phase = osc.phase;
|
int phase = osc.phase;
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
phase++;
|
phase++;
|
||||||
if ( phase == 16 )
|
if ( phase == 16 )
|
||||||
{
|
{
|
||||||
phase = 0;
|
phase = 0;
|
||||||
osc.last_amp = volume;
|
osc.last_amp = volume;
|
||||||
square_synth.offset( time, volume, output );
|
square_synth.offset( time, volume, output );
|
||||||
}
|
}
|
||||||
if ( phase == duty )
|
if ( phase == duty )
|
||||||
{
|
{
|
||||||
osc.last_amp = 0;
|
osc.last_amp = 0;
|
||||||
square_synth.offset( time, -volume, output );
|
square_synth.offset( time, -volume, output );
|
||||||
}
|
}
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
|
|
||||||
osc.phase = phase;
|
osc.phase = phase;
|
||||||
}
|
}
|
||||||
osc.delay = time - end_time;
|
osc.delay = time - end_time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
|
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
Vrc6_Osc& osc = oscs [2];
|
Vrc6_Osc& osc = oscs [2];
|
||||||
Blip_Buffer* output = osc.output;
|
Blip_Buffer* output = osc.output;
|
||||||
if ( !output )
|
if ( !output )
|
||||||
return;
|
return;
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
|
|
||||||
int amp = osc.amp;
|
int amp = osc.amp;
|
||||||
int amp_step = osc.regs [0] & 0x3F;
|
int amp_step = osc.regs [0] & 0x3F;
|
||||||
blip_time_t time = last_time;
|
blip_time_t time = last_time;
|
||||||
int last_amp = osc.last_amp;
|
int last_amp = osc.last_amp;
|
||||||
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
|
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
|
||||||
{
|
{
|
||||||
osc.delay = 0;
|
osc.delay = 0;
|
||||||
int delta = (amp >> 3) - last_amp;
|
int delta = (amp >> 3) - last_amp;
|
||||||
last_amp = amp >> 3;
|
last_amp = amp >> 3;
|
||||||
saw_synth.offset( time, delta, output );
|
saw_synth.offset( time, delta, output );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
time += osc.delay;
|
time += osc.delay;
|
||||||
if ( time < end_time )
|
if ( time < end_time )
|
||||||
{
|
{
|
||||||
int period = osc.period() * 2;
|
int period = osc.period() * 2;
|
||||||
int phase = osc.phase;
|
int phase = osc.phase;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if ( --phase == 0 )
|
if ( --phase == 0 )
|
||||||
{
|
{
|
||||||
phase = 7;
|
phase = 7;
|
||||||
amp = 0;
|
amp = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int delta = (amp >> 3) - last_amp;
|
int delta = (amp >> 3) - last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
last_amp = amp >> 3;
|
last_amp = amp >> 3;
|
||||||
saw_synth.offset( time, delta, output );
|
saw_synth.offset( time, delta, output );
|
||||||
}
|
}
|
||||||
|
|
||||||
time += period;
|
time += period;
|
||||||
amp = (amp + amp_step) & 0xFF;
|
amp = (amp + amp_step) & 0xFF;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
|
|
||||||
osc.phase = phase;
|
osc.phase = phase;
|
||||||
osc.amp = amp;
|
osc.amp = amp;
|
||||||
}
|
}
|
||||||
|
|
||||||
osc.delay = time - end_time;
|
osc.delay = time - end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
osc.last_amp = last_amp;
|
osc.last_amp = last_amp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,95 +1,95 @@
|
||||||
// Konami VRC6 sound chip emulator
|
// Konami VRC6 sound chip emulator
|
||||||
|
|
||||||
// Nes_Snd_Emu $vers
|
// Nes_Snd_Emu $vers
|
||||||
#ifndef NES_VRC6_APU_H
|
#ifndef NES_VRC6_APU_H
|
||||||
#define NES_VRC6_APU_H
|
#define NES_VRC6_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
struct vrc6_apu_state_t;
|
struct vrc6_apu_state_t;
|
||||||
|
|
||||||
class Nes_Vrc6_Apu {
|
class Nes_Vrc6_Apu {
|
||||||
public:
|
public:
|
||||||
// See Nes_Apu.h for reference
|
// See Nes_Apu.h for reference
|
||||||
void reset();
|
void reset();
|
||||||
void volume( double );
|
void volume( double );
|
||||||
void treble_eq( blip_eq_t const& );
|
void treble_eq( blip_eq_t const& );
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
enum { osc_count = 3 };
|
enum { osc_count = 3 };
|
||||||
void set_output( int index, Blip_Buffer* );
|
void set_output( int index, Blip_Buffer* );
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
void save_state( vrc6_apu_state_t* ) const;
|
void save_state( vrc6_apu_state_t* ) const;
|
||||||
void load_state( vrc6_apu_state_t const& );
|
void load_state( vrc6_apu_state_t const& );
|
||||||
|
|
||||||
// Oscillator 0 write-only registers are at $9000-$9002
|
// Oscillator 0 write-only registers are at $9000-$9002
|
||||||
// Oscillator 1 write-only registers are at $A000-$A002
|
// Oscillator 1 write-only registers are at $A000-$A002
|
||||||
// Oscillator 2 write-only registers are at $B000-$B002
|
// Oscillator 2 write-only registers are at $B000-$B002
|
||||||
enum { reg_count = 3 };
|
enum { reg_count = 3 };
|
||||||
enum { base_addr = 0x9000 };
|
enum { base_addr = 0x9000 };
|
||||||
enum { addr_step = 0x1000 };
|
enum { addr_step = 0x1000 };
|
||||||
void write_osc( blip_time_t, int osc, int reg, int data );
|
void write_osc( blip_time_t, int osc, int reg, int data );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Nes_Vrc6_Apu();
|
Nes_Vrc6_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Nes_Vrc6_Apu( const Nes_Vrc6_Apu& );
|
Nes_Vrc6_Apu( const Nes_Vrc6_Apu& );
|
||||||
Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& );
|
Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& );
|
||||||
|
|
||||||
struct Vrc6_Osc
|
struct Vrc6_Osc
|
||||||
{
|
{
|
||||||
BOOST::uint8_t regs [3];
|
BOOST::uint8_t regs [3];
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
int delay;
|
int delay;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
int phase;
|
int phase;
|
||||||
int amp; // only used by saw
|
int amp; // only used by saw
|
||||||
|
|
||||||
int period() const
|
int period() const
|
||||||
{
|
{
|
||||||
return (regs [2] & 0x0F) * 0x100 + regs [1] + 1;
|
return (regs [2] & 0x0F) * 0x100 + regs [1] + 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Vrc6_Osc oscs [osc_count];
|
Vrc6_Osc oscs [osc_count];
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
|
|
||||||
Blip_Synth_Fast saw_synth;
|
Blip_Synth_Fast saw_synth;
|
||||||
Blip_Synth_Norm square_synth;
|
Blip_Synth_Norm square_synth;
|
||||||
|
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
void run_square( Vrc6_Osc& osc, blip_time_t );
|
void run_square( Vrc6_Osc& osc, blip_time_t );
|
||||||
void run_saw( blip_time_t );
|
void run_saw( blip_time_t );
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vrc6_apu_state_t
|
struct vrc6_apu_state_t
|
||||||
{
|
{
|
||||||
BOOST::uint8_t regs [3] [3];
|
BOOST::uint8_t regs [3] [3];
|
||||||
BOOST::uint8_t saw_amp;
|
BOOST::uint8_t saw_amp;
|
||||||
BOOST::uint16_t delays [3];
|
BOOST::uint16_t delays [3];
|
||||||
BOOST::uint8_t phases [3];
|
BOOST::uint8_t phases [3];
|
||||||
BOOST::uint8_t unused;
|
BOOST::uint8_t unused;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Nes_Vrc6_Apu::set_output( int i, Blip_Buffer* buf )
|
inline void Nes_Vrc6_Apu::set_output( int i, Blip_Buffer* buf )
|
||||||
{
|
{
|
||||||
assert( (unsigned) i < osc_count );
|
assert( (unsigned) i < osc_count );
|
||||||
oscs [i].output = buf;
|
oscs [i].output = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Vrc6_Apu::volume( double v )
|
inline void Nes_Vrc6_Apu::volume( double v )
|
||||||
{
|
{
|
||||||
double const factor = 0.0967 * 2;
|
double const factor = 0.0967 * 2;
|
||||||
saw_synth.volume( factor / 31 * v );
|
saw_synth.volume( factor / 31 * v );
|
||||||
square_synth.volume( factor * 0.5 / 15 * v );
|
square_synth.volume( factor * 0.5 / 15 * v );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq )
|
inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq )
|
||||||
{
|
{
|
||||||
saw_synth.treble_eq( eq );
|
saw_synth.treble_eq( eq );
|
||||||
square_synth.treble_eq( eq );
|
square_synth.treble_eq( eq );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
#include "Nes_Vrc7_Apu.h"
|
#include "Nes_Vrc7_Apu.h"
|
||||||
|
|
||||||
#include "ym2413.h"
|
extern "C" {
|
||||||
|
#include "../vgmplay/chips/emu2413.h"
|
||||||
|
}
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
|
static unsigned char vrc7_inst[(16 + 3) * 8] =
|
||||||
|
{
|
||||||
|
#include "../vgmplay/chips/vrc7tone.h"
|
||||||
|
};
|
||||||
|
|
||||||
int const period = 36; // NES CPU clocks per FM clock
|
int const period = 36; // NES CPU clocks per FM clock
|
||||||
|
|
||||||
Nes_Vrc7_Apu::Nes_Vrc7_Apu()
|
Nes_Vrc7_Apu::Nes_Vrc7_Apu()
|
||||||
|
@ -14,8 +22,10 @@ Nes_Vrc7_Apu::Nes_Vrc7_Apu()
|
||||||
|
|
||||||
blargg_err_t Nes_Vrc7_Apu::init()
|
blargg_err_t Nes_Vrc7_Apu::init()
|
||||||
{
|
{
|
||||||
CHECK_ALLOC( opll = ym2413_init( 3579545, 3579545 / 72, 1 ) );
|
CHECK_ALLOC( opll = OPLL_new( 3579545, 3579545 / 72 ) );
|
||||||
|
OPLL_SetChipMode((OPLL *) opll, 1);
|
||||||
|
OPLL_setPatch((OPLL *) opll, vrc7_inst);
|
||||||
|
|
||||||
set_output( 0 );
|
set_output( 0 );
|
||||||
volume( 1.0 );
|
volume( 1.0 );
|
||||||
reset();
|
reset();
|
||||||
|
@ -25,7 +35,7 @@ blargg_err_t Nes_Vrc7_Apu::init()
|
||||||
Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
|
Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
|
||||||
{
|
{
|
||||||
if ( opll )
|
if ( opll )
|
||||||
ym2413_shutdown( opll );
|
OPLL_delete( (OPLL *) opll );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
|
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
|
||||||
|
@ -46,7 +56,7 @@ void Nes_Vrc7_Apu::output_changed()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mono.output )
|
if ( mono.output )
|
||||||
{
|
{
|
||||||
for ( int i = osc_count; --i; )
|
for ( int i = osc_count; --i; )
|
||||||
|
@ -62,7 +72,7 @@ void Nes_Vrc7_Apu::reset()
|
||||||
addr = 0;
|
addr = 0;
|
||||||
next_time = 0;
|
next_time = 0;
|
||||||
mono.last_amp = 0;
|
mono.last_amp = 0;
|
||||||
|
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
{
|
{
|
||||||
Vrc7_Osc& osc = oscs [i];
|
Vrc7_Osc& osc = oscs [i];
|
||||||
|
@ -71,7 +81,7 @@ void Nes_Vrc7_Apu::reset()
|
||||||
osc.regs [j] = 0;
|
osc.regs [j] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ym2413_reset_chip( opll );
|
OPLL_reset( (OPLL *) opll );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc7_Apu::write_reg( int data )
|
void Nes_Vrc7_Apu::write_reg( int data )
|
||||||
|
@ -85,21 +95,23 @@ void Nes_Vrc7_Apu::write_data( blip_time_t time, int data )
|
||||||
int chan = addr & 15;
|
int chan = addr & 15;
|
||||||
if ( (unsigned) type < 3 && chan < osc_count )
|
if ( (unsigned) type < 3 && chan < osc_count )
|
||||||
oscs [chan].regs [type] = data;
|
oscs [chan].regs [type] = data;
|
||||||
|
if ( addr < 0x08 )
|
||||||
|
inst [addr] = data;
|
||||||
|
|
||||||
if ( time > next_time )
|
if ( time > next_time )
|
||||||
run_until( time );
|
run_until( time );
|
||||||
ym2413_write( opll, 0, addr );
|
OPLL_writeIO( (OPLL *) opll, 0, addr );
|
||||||
ym2413_write( opll, 1, data );
|
OPLL_writeIO( (OPLL *) opll, 1, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc7_Apu::end_frame( blip_time_t time )
|
void Nes_Vrc7_Apu::end_frame( blip_time_t time )
|
||||||
{
|
{
|
||||||
if ( time > next_time )
|
if ( time > next_time )
|
||||||
run_until( time );
|
run_until( time );
|
||||||
|
|
||||||
next_time -= time;
|
next_time -= time;
|
||||||
assert( next_time >= 0 );
|
assert( next_time >= 0 );
|
||||||
|
|
||||||
for ( int i = osc_count; --i >= 0; )
|
for ( int i = osc_count; --i >= 0; )
|
||||||
{
|
{
|
||||||
Blip_Buffer* output = oscs [i].output;
|
Blip_Buffer* output = oscs [i].output;
|
||||||
|
@ -117,13 +129,13 @@ void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const
|
||||||
for ( int j = 0; j < 3; ++j )
|
for ( int j = 0; j < 3; ++j )
|
||||||
out->regs [i] [j] = oscs [i].regs [j];
|
out->regs [i] [j] = oscs [i].regs [j];
|
||||||
}
|
}
|
||||||
memcpy( out->inst, ym2413_get_inst0( opll ), 8 );
|
memcpy( out->inst, inst, 8 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
|
void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
|
||||||
{
|
{
|
||||||
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
|
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
next_time = in.delay;
|
next_time = in.delay;
|
||||||
write_reg( in.latch );
|
write_reg( in.latch );
|
||||||
|
@ -134,18 +146,19 @@ void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
|
||||||
oscs [i].regs [j] = in.regs [i] [j];
|
oscs [i].regs [j] = in.regs [i] [j];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memcpy( inst, in.inst, 8 );
|
||||||
for ( i = 0; i < 8; ++i )
|
for ( i = 0; i < 8; ++i )
|
||||||
{
|
{
|
||||||
ym2413_write( opll, 0, i );
|
OPLL_writeIO( (OPLL *) opll, 0, i );
|
||||||
ym2413_write( opll, 1, in.inst [i] );
|
OPLL_writeIO( (OPLL *) opll, 1, in.inst [i] );
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( i = 0; i < 3; ++i )
|
for ( i = 0; i < 3; ++i )
|
||||||
{
|
{
|
||||||
for ( int j = 0; j < 6; ++j )
|
for ( int j = 0; j < 6; ++j )
|
||||||
{
|
{
|
||||||
ym2413_write( opll, 0, 0x10 + i * 0x10 + j );
|
OPLL_writeIO( (OPLL *) opll, 0, 0x10 + i * 0x10 + j );
|
||||||
ym2413_write( opll, 1, oscs [j].regs [i] );
|
OPLL_writeIO( (OPLL *) opll, 1, oscs [j].regs [i] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,16 +170,15 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
|
||||||
blip_time_t time = next_time;
|
blip_time_t time = next_time;
|
||||||
void* opll = this->opll; // cache
|
void* opll = this->opll; // cache
|
||||||
Blip_Buffer* const mono_output = mono.output;
|
Blip_Buffer* const mono_output = mono.output;
|
||||||
|
e_int32 buffer [2];
|
||||||
|
e_int32* buffers[2] = {&buffer[0], &buffer[1]};
|
||||||
if ( mono_output )
|
if ( mono_output )
|
||||||
{
|
{
|
||||||
// optimal case
|
// optimal case
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
ym2413_advance_lfo( opll );
|
OPLL_calc_stereo( (OPLL *) opll, buffers, 1, -1 );
|
||||||
int amp = 0;
|
int amp = buffer [0] + buffer [1];
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
|
||||||
amp += ym2413_calcch( opll, i );
|
|
||||||
ym2413_advance( opll );
|
|
||||||
int delta = amp - mono.last_amp;
|
int delta = amp - mono.last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
|
@ -182,13 +194,14 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
|
||||||
mono.last_amp = 0;
|
mono.last_amp = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
ym2413_advance_lfo( opll );
|
OPLL_advance( (OPLL *) opll );
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
{
|
{
|
||||||
Vrc7_Osc& osc = oscs [i];
|
Vrc7_Osc& osc = oscs [i];
|
||||||
if ( osc.output )
|
if ( osc.output )
|
||||||
{
|
{
|
||||||
int amp = ym2413_calcch( opll, i );
|
OPLL_calc_stereo( (OPLL *) opll, buffers, 1, i );
|
||||||
|
int amp = buffer [0] + buffer [1];
|
||||||
int delta = amp - osc.last_amp;
|
int delta = amp - osc.last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
|
@ -197,7 +210,6 @@ void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ym2413_advance( opll );
|
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
while ( time < end_time );
|
while ( time < end_time );
|
||||||
|
|
|
@ -1,80 +1,82 @@
|
||||||
// Konami VRC7 sound chip emulator
|
// Konami VRC7 sound chip emulator
|
||||||
|
|
||||||
#ifndef NES_VRC7_APU_H
|
#ifndef NES_VRC7_APU_H
|
||||||
#define NES_VRC7_APU_H
|
#define NES_VRC7_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
struct vrc7_snapshot_t;
|
struct vrc7_snapshot_t;
|
||||||
|
|
||||||
class Nes_Vrc7_Apu {
|
class Nes_Vrc7_Apu {
|
||||||
public:
|
public:
|
||||||
blargg_err_t init();
|
blargg_err_t init();
|
||||||
|
|
||||||
// See Nes_Apu.h for reference
|
// See Nes_Apu.h for reference
|
||||||
void reset();
|
void reset();
|
||||||
void volume( double );
|
void volume( double );
|
||||||
void treble_eq( blip_eq_t const& );
|
void treble_eq( blip_eq_t const& );
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
enum { osc_count = 6 };
|
enum { osc_count = 6 };
|
||||||
void set_output( int index, Blip_Buffer* );
|
void set_output( int index, Blip_Buffer* );
|
||||||
void end_frame( blip_time_t );
|
void end_frame( blip_time_t );
|
||||||
void save_snapshot( vrc7_snapshot_t* ) const;
|
void save_snapshot( vrc7_snapshot_t* ) const;
|
||||||
void load_snapshot( vrc7_snapshot_t const& );
|
void load_snapshot( vrc7_snapshot_t const& );
|
||||||
|
|
||||||
void write_reg( int reg );
|
void write_reg( int reg );
|
||||||
void write_data( blip_time_t, int data );
|
void write_data( blip_time_t, int data );
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Nes_Vrc7_Apu();
|
Nes_Vrc7_Apu();
|
||||||
~Nes_Vrc7_Apu();
|
~Nes_Vrc7_Apu();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
|
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
|
||||||
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
|
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
|
||||||
|
|
||||||
struct Vrc7_Osc
|
struct Vrc7_Osc
|
||||||
{
|
{
|
||||||
BOOST::uint8_t regs [3];
|
BOOST::uint8_t regs [3];
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
};
|
};
|
||||||
|
|
||||||
Vrc7_Osc oscs [osc_count];
|
Vrc7_Osc oscs [osc_count];
|
||||||
void* opll;
|
BOOST::uint8_t kon;
|
||||||
int addr;
|
BOOST::uint8_t inst [8];
|
||||||
blip_time_t next_time;
|
void* opll;
|
||||||
struct {
|
int addr;
|
||||||
Blip_Buffer* output;
|
blip_time_t next_time;
|
||||||
int last_amp;
|
struct {
|
||||||
} mono;
|
Blip_Buffer* output;
|
||||||
|
int last_amp;
|
||||||
Blip_Synth_Fast synth;
|
} mono;
|
||||||
|
|
||||||
void run_until( blip_time_t );
|
Blip_Synth_Fast synth;
|
||||||
void output_changed();
|
|
||||||
};
|
void run_until( blip_time_t );
|
||||||
|
void output_changed();
|
||||||
struct vrc7_snapshot_t
|
};
|
||||||
{
|
|
||||||
BOOST::uint8_t latch;
|
struct vrc7_snapshot_t
|
||||||
BOOST::uint8_t inst [8];
|
{
|
||||||
BOOST::uint8_t regs [6] [3];
|
BOOST::uint8_t latch;
|
||||||
BOOST::uint8_t delay;
|
BOOST::uint8_t inst [8];
|
||||||
};
|
BOOST::uint8_t regs [6] [3];
|
||||||
|
BOOST::uint8_t delay;
|
||||||
inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf )
|
};
|
||||||
{
|
|
||||||
assert( (unsigned) i < osc_count );
|
inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf )
|
||||||
oscs [i].output = buf;
|
{
|
||||||
output_changed();
|
assert( (unsigned) i < osc_count );
|
||||||
}
|
oscs [i].output = buf;
|
||||||
|
output_changed();
|
||||||
// DB2LIN_AMP_BITS == 11, * 2
|
}
|
||||||
inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
|
|
||||||
|
// DB2LIN_AMP_BITS == 11, * 2
|
||||||
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
|
||||||
|
|
||||||
#endif
|
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -1,302 +1,302 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nsf_Core.h"
|
#include "Nsf_Core.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
#include "Nes_Namco_Apu.h"
|
#include "Nes_Namco_Apu.h"
|
||||||
#include "Nes_Vrc6_Apu.h"
|
#include "Nes_Vrc6_Apu.h"
|
||||||
#include "Nes_Fme7_Apu.h"
|
#include "Nes_Fme7_Apu.h"
|
||||||
#include "Nes_Fds_Apu.h"
|
#include "Nes_Fds_Apu.h"
|
||||||
#include "Nes_Mmc5_Apu.h"
|
#include "Nes_Mmc5_Apu.h"
|
||||||
#include "Nes_Vrc7_Apu.h"
|
#include "Nes_Vrc7_Apu.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
Nsf_Core::Nsf_Core()
|
Nsf_Core::Nsf_Core()
|
||||||
{
|
{
|
||||||
fds = NULL;
|
fds = NULL;
|
||||||
fme7 = NULL;
|
fme7 = NULL;
|
||||||
mmc5 = NULL;
|
mmc5 = NULL;
|
||||||
namco = NULL;
|
namco = NULL;
|
||||||
vrc6 = NULL;
|
vrc6 = NULL;
|
||||||
vrc7 = NULL;
|
vrc7 = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nsf_Core::~Nsf_Core()
|
Nsf_Core::~Nsf_Core()
|
||||||
{
|
{
|
||||||
unload();
|
unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Core::unload()
|
void Nsf_Core::unload()
|
||||||
{
|
{
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
delete fds;
|
delete fds;
|
||||||
fds = NULL;
|
fds = NULL;
|
||||||
|
|
||||||
delete fme7;
|
delete fme7;
|
||||||
fme7 = NULL;
|
fme7 = NULL;
|
||||||
|
|
||||||
delete namco;
|
delete namco;
|
||||||
namco = NULL;
|
namco = NULL;
|
||||||
|
|
||||||
delete mmc5;
|
delete mmc5;
|
||||||
mmc5 = NULL;
|
mmc5 = NULL;
|
||||||
|
|
||||||
delete vrc6;
|
delete vrc6;
|
||||||
vrc6 = NULL;
|
vrc6 = NULL;
|
||||||
|
|
||||||
delete vrc7;
|
delete vrc7;
|
||||||
vrc7 = NULL;
|
vrc7 = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Nsf_Impl::unload();
|
Nsf_Impl::unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Core::set_tempo( double t )
|
void Nsf_Core::set_tempo( double t )
|
||||||
{
|
{
|
||||||
set_play_period( (int) (header().play_period() / t) );
|
set_play_period( (int) (header().play_period() / t) );
|
||||||
nes_apu()->set_tempo( t );
|
nes_apu()->set_tempo( t );
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
if ( fds )
|
if ( fds )
|
||||||
fds->set_tempo( t );
|
fds->set_tempo( t );
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Nsf_Core::post_load()
|
blargg_err_t Nsf_Core::post_load()
|
||||||
{
|
{
|
||||||
int chip_flags = header().chip_flags;
|
int chip_flags = header().chip_flags;
|
||||||
|
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
if ( chip_flags & header_t::fds_mask )
|
if ( chip_flags & header_t::fds_mask )
|
||||||
CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
|
CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
|
||||||
|
|
||||||
if ( chip_flags & header_t::fme7_mask )
|
if ( chip_flags & header_t::fme7_mask )
|
||||||
CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
|
CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
|
||||||
|
|
||||||
if ( chip_flags & header_t::mmc5_mask )
|
if ( chip_flags & header_t::mmc5_mask )
|
||||||
CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
|
CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
|
||||||
|
|
||||||
if ( chip_flags & header_t::namco_mask )
|
if ( chip_flags & header_t::namco_mask )
|
||||||
CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
|
CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
|
||||||
|
|
||||||
if ( chip_flags & header_t::vrc6_mask )
|
if ( chip_flags & header_t::vrc6_mask )
|
||||||
CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
|
CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
|
||||||
|
|
||||||
if ( chip_flags & header_t::vrc7_mask )
|
if ( chip_flags & header_t::vrc7_mask )
|
||||||
{
|
{
|
||||||
#if NSF_EMU_NO_VRC7
|
#if NSF_EMU_NO_VRC7
|
||||||
chip_flags = ~chips_mask; // give warning rather than error
|
chip_flags = ~chips_mask; // give warning rather than error
|
||||||
#else
|
#else
|
||||||
CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
|
CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
|
||||||
RETURN_ERR( vrc7->init() );
|
RETURN_ERR( vrc7->init() );
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
set_tempo( 1.0 );
|
set_tempo( 1.0 );
|
||||||
|
|
||||||
if ( chip_flags & ~chips_mask )
|
if ( chip_flags & ~chips_mask )
|
||||||
set_warning( "Uses unsupported audio expansion hardware" );
|
set_warning( "Uses unsupported audio expansion hardware" );
|
||||||
|
|
||||||
return Nsf_Impl::post_load();
|
return Nsf_Impl::post_load();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Nsf_Core::cpu_read( addr_t addr )
|
int Nsf_Core::cpu_read( addr_t addr )
|
||||||
{
|
{
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
{
|
{
|
||||||
if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
|
if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
|
||||||
return namco->read_data();
|
return namco->read_data();
|
||||||
|
|
||||||
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
|
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
|
||||||
return fds->read( time(), addr );
|
return fds->read( time(), addr );
|
||||||
|
|
||||||
int i = addr - 0x5C00;
|
int i = addr - 0x5C00;
|
||||||
if ( (unsigned) i < mmc5->exram_size && mmc5 )
|
if ( (unsigned) i < mmc5->exram_size && mmc5 )
|
||||||
return mmc5->exram [i];
|
return mmc5->exram [i];
|
||||||
|
|
||||||
int m = addr - 0x5205;
|
int m = addr - 0x5205;
|
||||||
if ( (unsigned) m < 2 && mmc5 )
|
if ( (unsigned) m < 2 && mmc5 )
|
||||||
return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
|
return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Nsf_Impl::cpu_read( addr );
|
return Nsf_Impl::cpu_read( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Nsf_Core::unmapped_read( addr_t addr )
|
int Nsf_Core::unmapped_read( addr_t addr )
|
||||||
{
|
{
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x2002:
|
case 0x2002:
|
||||||
case 0x4016:
|
case 0x4016:
|
||||||
case 0x4017:
|
case 0x4017:
|
||||||
return addr >> 8;
|
return addr >> 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Nsf_Impl::unmapped_read( addr );
|
return Nsf_Impl::unmapped_read( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Core::cpu_write( addr_t addr, int data )
|
void Nsf_Core::cpu_write( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
{
|
{
|
||||||
if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
|
if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
|
||||||
{
|
{
|
||||||
fds->write( time(), addr, data );
|
fds->write( time(), addr, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( namco )
|
if ( namco )
|
||||||
{
|
{
|
||||||
if ( addr == namco->addr_reg_addr )
|
if ( addr == namco->addr_reg_addr )
|
||||||
{
|
{
|
||||||
namco->write_addr( data );
|
namco->write_addr( data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( addr == namco->data_reg_addr )
|
if ( addr == namco->data_reg_addr )
|
||||||
{
|
{
|
||||||
namco->write_data( time(), data );
|
namco->write_data( time(), data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( vrc6 )
|
if ( vrc6 )
|
||||||
{
|
{
|
||||||
int reg = addr & (vrc6->addr_step - 1);
|
int reg = addr & (vrc6->addr_step - 1);
|
||||||
int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
|
int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
|
||||||
if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
|
if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
|
||||||
{
|
{
|
||||||
vrc6->write_osc( time(), osc, reg, data );
|
vrc6->write_osc( time(), osc, reg, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( addr >= fme7->latch_addr && fme7 )
|
if ( addr >= fme7->latch_addr && fme7 )
|
||||||
{
|
{
|
||||||
switch ( addr & fme7->addr_mask )
|
switch ( addr & fme7->addr_mask )
|
||||||
{
|
{
|
||||||
case Nes_Fme7_Apu::latch_addr:
|
case Nes_Fme7_Apu::latch_addr:
|
||||||
fme7->write_latch( data );
|
fme7->write_latch( data );
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case Nes_Fme7_Apu::data_addr:
|
case Nes_Fme7_Apu::data_addr:
|
||||||
fme7->write_data( time(), data );
|
fme7->write_data( time(), data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mmc5 )
|
if ( mmc5 )
|
||||||
{
|
{
|
||||||
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
|
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
|
||||||
{
|
{
|
||||||
mmc5->write_register( time(), addr, data );
|
mmc5->write_register( time(), addr, data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int m = addr - 0x5205;
|
int m = addr - 0x5205;
|
||||||
if ( (unsigned) m < 2 )
|
if ( (unsigned) m < 2 )
|
||||||
{
|
{
|
||||||
mmc5_mul [m] = data;
|
mmc5_mul [m] = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int i = addr - 0x5C00;
|
int i = addr - 0x5C00;
|
||||||
if ( (unsigned) i < mmc5->exram_size )
|
if ( (unsigned) i < mmc5->exram_size )
|
||||||
{
|
{
|
||||||
mmc5->exram [i] = data;
|
mmc5->exram [i] = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( vrc7 )
|
if ( vrc7 )
|
||||||
{
|
{
|
||||||
if ( addr == 0x9010 )
|
if ( addr == 0x9010 )
|
||||||
{
|
{
|
||||||
vrc7->write_reg( data );
|
vrc7->write_reg( data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( (unsigned) (addr - 0x9028) <= 0x08 )
|
if ( (unsigned) (addr - 0x9028) <= 0x08 )
|
||||||
{
|
{
|
||||||
vrc7->write_data( time(), data );
|
vrc7->write_data( time(), data );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Nsf_Impl::cpu_write( addr, data );
|
return Nsf_Impl::cpu_write( addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Core::unmapped_write( addr_t addr, int data )
|
void Nsf_Core::unmapped_write( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
switch ( addr )
|
switch ( addr )
|
||||||
{
|
{
|
||||||
case 0x8000: // some write to $8000 and $8001 repeatedly
|
case 0x8000: // some write to $8000 and $8001 repeatedly
|
||||||
case 0x8001:
|
case 0x8001:
|
||||||
case 0x4800: // probably namco sound mistakenly turned on in MCK
|
case 0x4800: // probably namco sound mistakenly turned on in MCK
|
||||||
case 0xF800:
|
case 0xF800:
|
||||||
case 0xFFF8: // memory mapper?
|
case 0xFFF8: // memory mapper?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( mmc5 && addr == 0x5115 ) return;
|
if ( mmc5 && addr == 0x5115 ) return;
|
||||||
|
|
||||||
// FDS memory
|
// FDS memory
|
||||||
if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;
|
if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;
|
||||||
|
|
||||||
Nsf_Impl::unmapped_write( addr, data );
|
Nsf_Impl::unmapped_write( addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Nsf_Core::start_track( int track )
|
blargg_err_t Nsf_Core::start_track( int track )
|
||||||
{
|
{
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
if ( mmc5 )
|
if ( mmc5 )
|
||||||
{
|
{
|
||||||
mmc5_mul [0] = 0;
|
mmc5_mul [0] = 0;
|
||||||
mmc5_mul [1] = 0;
|
mmc5_mul [1] = 0;
|
||||||
memset( mmc5->exram, 0, mmc5->exram_size );
|
memset( mmc5->exram, 0, mmc5->exram_size );
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
if ( fds ) fds ->reset();
|
if ( fds ) fds ->reset();
|
||||||
if ( fme7 ) fme7 ->reset();
|
if ( fme7 ) fme7 ->reset();
|
||||||
if ( mmc5 ) mmc5 ->reset();
|
if ( mmc5 ) mmc5 ->reset();
|
||||||
if ( namco ) namco->reset();
|
if ( namco ) namco->reset();
|
||||||
if ( vrc6 ) vrc6 ->reset();
|
if ( vrc6 ) vrc6 ->reset();
|
||||||
if ( vrc7 ) vrc7 ->reset();
|
if ( vrc7 ) vrc7 ->reset();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Nsf_Impl::start_track( track );
|
return Nsf_Impl::start_track( track );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Core::end_frame( time_t end )
|
void Nsf_Core::end_frame( time_t end )
|
||||||
{
|
{
|
||||||
Nsf_Impl::end_frame( end );
|
Nsf_Impl::end_frame( end );
|
||||||
|
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
if ( fds ) fds ->end_frame( end );
|
if ( fds ) fds ->end_frame( end );
|
||||||
if ( fme7 ) fme7 ->end_frame( end );
|
if ( fme7 ) fme7 ->end_frame( end );
|
||||||
if ( mmc5 ) mmc5 ->end_frame( end );
|
if ( mmc5 ) mmc5 ->end_frame( end );
|
||||||
if ( namco ) namco->end_frame( end );
|
if ( namco ) namco->end_frame( end );
|
||||||
if ( vrc6 ) vrc6 ->end_frame( end );
|
if ( vrc6 ) vrc6 ->end_frame( end );
|
||||||
if ( vrc7 ) vrc7 ->end_frame( end );
|
if ( vrc7 ) vrc7 ->end_frame( end );
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
// Loads NSF file and emulates CPU and sound chips
|
// Loads NSF file and emulates CPU and sound chips
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef NSF_CORE_H
|
#ifndef NSF_CORE_H
|
||||||
#define NSF_CORE_H
|
#define NSF_CORE_H
|
||||||
|
|
||||||
#include "Nsf_Impl.h"
|
#include "Nsf_Impl.h"
|
||||||
|
|
||||||
class Nes_Namco_Apu;
|
class Nes_Namco_Apu;
|
||||||
class Nes_Vrc6_Apu;
|
class Nes_Vrc6_Apu;
|
||||||
class Nes_Fme7_Apu;
|
class Nes_Fme7_Apu;
|
||||||
class Nes_Mmc5_Apu;
|
class Nes_Mmc5_Apu;
|
||||||
class Nes_Vrc7_Apu;
|
class Nes_Vrc7_Apu;
|
||||||
class Nes_Fds_Apu;
|
class Nes_Fds_Apu;
|
||||||
|
|
||||||
class Nsf_Core : public Nsf_Impl {
|
class Nsf_Core : public Nsf_Impl {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
||||||
// Loading a file resets tempo to 1.0.
|
// Loading a file resets tempo to 1.0.
|
||||||
void set_tempo( double );
|
void set_tempo( double );
|
||||||
|
|
||||||
// Pointer to sound chip, or NULL if not used by current file.
|
// Pointer to sound chip, or NULL if not used by current file.
|
||||||
// Must be assigned to a Blip_Buffer to get any sound.
|
// Must be assigned to a Blip_Buffer to get any sound.
|
||||||
Nes_Fds_Apu * fds_apu () { return fds; }
|
Nes_Fds_Apu * fds_apu () { return fds; }
|
||||||
Nes_Fme7_Apu * fme7_apu () { return fme7; }
|
Nes_Fme7_Apu * fme7_apu () { return fme7; }
|
||||||
Nes_Mmc5_Apu * mmc5_apu () { return mmc5; }
|
Nes_Mmc5_Apu * mmc5_apu () { return mmc5; }
|
||||||
Nes_Namco_Apu* namco_apu() { return namco; }
|
Nes_Namco_Apu* namco_apu() { return namco; }
|
||||||
Nes_Vrc6_Apu * vrc6_apu () { return vrc6; }
|
Nes_Vrc6_Apu * vrc6_apu () { return vrc6; }
|
||||||
Nes_Vrc7_Apu * vrc7_apu () { return vrc7; }
|
Nes_Vrc7_Apu * vrc7_apu () { return vrc7; }
|
||||||
|
|
||||||
// Mask for which chips are supported
|
// Mask for which chips are supported
|
||||||
#if NSF_EMU_APU_ONLY
|
#if NSF_EMU_APU_ONLY
|
||||||
enum { chips_mask = 0 };
|
enum { chips_mask = 0 };
|
||||||
#else
|
#else
|
||||||
enum { chips_mask = header_t::all_mask };
|
enum { chips_mask = header_t::all_mask };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual int unmapped_read( addr_t );
|
virtual int unmapped_read( addr_t );
|
||||||
virtual void unmapped_write( addr_t, int data );
|
virtual void unmapped_write( addr_t, int data );
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Nsf_Core();
|
Nsf_Core();
|
||||||
~Nsf_Core();
|
~Nsf_Core();
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
virtual blargg_err_t start_track( int );
|
virtual blargg_err_t start_track( int );
|
||||||
virtual void end_frame( time_t );
|
virtual void end_frame( time_t );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t post_load();
|
virtual blargg_err_t post_load();
|
||||||
virtual int cpu_read( addr_t );
|
virtual int cpu_read( addr_t );
|
||||||
virtual void cpu_write( addr_t, int );
|
virtual void cpu_write( addr_t, int );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
byte mmc5_mul [2];
|
byte mmc5_mul [2];
|
||||||
|
|
||||||
Nes_Fds_Apu* fds;
|
Nes_Fds_Apu* fds;
|
||||||
Nes_Fme7_Apu* fme7;
|
Nes_Fme7_Apu* fme7;
|
||||||
Nes_Mmc5_Apu* mmc5;
|
Nes_Mmc5_Apu* mmc5;
|
||||||
Nes_Namco_Apu* namco;
|
Nes_Namco_Apu* namco;
|
||||||
Nes_Vrc6_Apu* vrc6;
|
Nes_Vrc6_Apu* vrc6;
|
||||||
Nes_Vrc7_Apu* vrc7;
|
Nes_Vrc7_Apu* vrc7;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,116 +1,116 @@
|
||||||
// Normal CPU for NSF emulator
|
// Normal CPU for NSF emulator
|
||||||
|
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nsf_Impl.h"
|
#include "Nsf_Impl.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
#ifdef BLARGG_DEBUG_H
|
#ifdef BLARGG_DEBUG_H
|
||||||
//#define CPU_LOG_START 1000000
|
//#define CPU_LOG_START 1000000
|
||||||
//#include "nes_cpu_log.h"
|
//#include "nes_cpu_log.h"
|
||||||
#undef LOG_MEM
|
#undef LOG_MEM
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
#ifndef LOG_MEM
|
#ifndef LOG_MEM
|
||||||
#define LOG_MEM( addr, str, data ) data
|
#define LOG_MEM( addr, str, data ) data
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int Nsf_Impl::read_mem( addr_t addr )
|
int Nsf_Impl::read_mem( addr_t addr )
|
||||||
{
|
{
|
||||||
int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around
|
int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around
|
||||||
if ( addr & 0xE000 )
|
if ( addr & 0xE000 )
|
||||||
{
|
{
|
||||||
result = *cpu.get_code( addr );
|
result = *cpu.get_code( addr );
|
||||||
if ( addr < sram_addr )
|
if ( addr < sram_addr )
|
||||||
{
|
{
|
||||||
if ( addr == apu.status_addr )
|
if ( addr == apu.status_addr )
|
||||||
result = apu.read_status( time() );
|
result = apu.read_status( time() );
|
||||||
else
|
else
|
||||||
result = cpu_read( addr );
|
result = cpu_read( addr );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return LOG_MEM( addr, ">", result );
|
return LOG_MEM( addr, ">", result );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::write_mem( addr_t addr, int data )
|
void Nsf_Impl::write_mem( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
(void) LOG_MEM( addr, "<", data );
|
(void) LOG_MEM( addr, "<", data );
|
||||||
|
|
||||||
int offset = addr - sram_addr;
|
int offset = addr - sram_addr;
|
||||||
if ( (unsigned) offset < sram_size )
|
if ( (unsigned) offset < sram_size )
|
||||||
{
|
{
|
||||||
sram() [offset] = data;
|
sram() [offset] = data;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// after sram because CPU handles most low_ram accesses internally already
|
// after sram because CPU handles most low_ram accesses internally already
|
||||||
int temp = addr & (low_ram_size-1); // also handles wrap-around
|
int temp = addr & (low_ram_size-1); // also handles wrap-around
|
||||||
if ( !(addr & 0xE000) )
|
if ( !(addr & 0xE000) )
|
||||||
{
|
{
|
||||||
low_ram [temp] = data;
|
low_ram [temp] = data;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int bank = addr - banks_addr;
|
int bank = addr - banks_addr;
|
||||||
if ( (unsigned) bank < bank_count )
|
if ( (unsigned) bank < bank_count )
|
||||||
{
|
{
|
||||||
write_bank( bank, data );
|
write_bank( bank, data );
|
||||||
}
|
}
|
||||||
else if ( (unsigned) (addr - apu.io_addr) < apu.io_size )
|
else if ( (unsigned) (addr - apu.io_addr) < apu.io_size )
|
||||||
{
|
{
|
||||||
apu.write_register( time(), addr, data );
|
apu.write_register( time(), addr, data );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
// 0x8000-0xDFFF is writable
|
// 0x8000-0xDFFF is writable
|
||||||
int i = addr - 0x8000;
|
int i = addr - 0x8000;
|
||||||
if ( (unsigned) i < fdsram_size && fds_enabled() )
|
if ( (unsigned) i < fdsram_size && fds_enabled() )
|
||||||
fdsram() [i] = data;
|
fdsram() [i] = data;
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
cpu_write( addr, data );
|
cpu_write( addr, data );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] ))
|
#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] ))
|
||||||
#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data ))
|
#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data ))
|
||||||
|
|
||||||
#define CAN_WRITE_FAST( addr ) (addr < low_ram_size)
|
#define CAN_WRITE_FAST( addr ) (addr < low_ram_size)
|
||||||
#define WRITE_FAST WRITE_LOW
|
#define WRITE_FAST WRITE_LOW
|
||||||
|
|
||||||
// addr < 0x2000 || addr >= 0x8000
|
// addr < 0x2000 || addr >= 0x8000
|
||||||
#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000)
|
#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000)
|
||||||
#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) ))
|
#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) ))
|
||||||
|
|
||||||
#define READ_MEM( addr ) read_mem( addr )
|
#define READ_MEM( addr ) read_mem( addr )
|
||||||
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
||||||
|
|
||||||
#define CPU cpu
|
#define CPU cpu
|
||||||
|
|
||||||
#define CPU_BEGIN \
|
#define CPU_BEGIN \
|
||||||
bool Nsf_Impl::run_cpu_until( time_t end )\
|
bool Nsf_Impl::run_cpu_until( time_t end )\
|
||||||
{\
|
{\
|
||||||
cpu.set_end_time( end );\
|
cpu.set_end_time( end );\
|
||||||
if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\
|
if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\
|
||||||
{
|
{
|
||||||
#include "Nes_Cpu_run.h"
|
#include "Nes_Cpu_run.h"
|
||||||
}
|
}
|
||||||
return cpu.time_past_end() < 0;
|
return cpu.time_past_end() < 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,14 @@ FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80, 0,0,0,0,0,0,0,0 };
|
Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80, 0,0,0,0,0,0,0,0 };
|
||||||
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 };
|
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 };
|
||||||
|
|
||||||
Nsf_Emu::Nsf_Emu()
|
Nsf_Emu::Nsf_Emu()
|
||||||
{
|
{
|
||||||
set_type( gme_nsf_type );
|
set_type( gme_nsf_type );
|
||||||
set_silence_lookahead( 6 );
|
set_silence_lookahead( 6 );
|
||||||
set_gain( 1.4 );
|
set_gain( 1.4 );
|
||||||
|
|
|
@ -1,55 +1,55 @@
|
||||||
// Nintendo NES/Famicom NSF music file emulator
|
// Nintendo NES/Famicom NSF music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef NSF_EMU_H
|
#ifndef NSF_EMU_H
|
||||||
#define NSF_EMU_H
|
#define NSF_EMU_H
|
||||||
|
|
||||||
#include "Classic_Emu.h"
|
#include "Classic_Emu.h"
|
||||||
#include "Nsf_Core.h"
|
#include "Nsf_Core.h"
|
||||||
|
|
||||||
void hash_nsf_file( Nsf_Core::header_t const& h, unsigned char const* data, int data_size, Music_Emu::Hash_Function& out );
|
void hash_nsf_file( Nsf_Core::header_t const& h, unsigned char const* data, int data_size, Music_Emu::Hash_Function& out );
|
||||||
|
|
||||||
class Nsf_Emu : public Classic_Emu {
|
class Nsf_Emu : public Classic_Emu {
|
||||||
public:
|
public:
|
||||||
// Equalizer profiles for US NES and Japanese Famicom
|
// Equalizer profiles for US NES and Japanese Famicom
|
||||||
static equalizer_t const nes_eq;
|
static equalizer_t const nes_eq;
|
||||||
static equalizer_t const famicom_eq;
|
static equalizer_t const famicom_eq;
|
||||||
|
|
||||||
// NSF file header (see Nsf_Impl.h)
|
// NSF file header (see Nsf_Impl.h)
|
||||||
typedef Nsf_Core::header_t header_t;
|
typedef Nsf_Core::header_t header_t;
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
header_t const& header() const { return core_.header(); }
|
header_t const& header() const { return core_.header(); }
|
||||||
|
|
||||||
blargg_err_t hash_( Hash_Function& ) const;
|
blargg_err_t hash_( Hash_Function& ) const;
|
||||||
|
|
||||||
static gme_type_t static_type() { return gme_nsf_type; }
|
static gme_type_t static_type() { return gme_nsf_type; }
|
||||||
|
|
||||||
Nsf_Core& core() { return core_; }
|
Nsf_Core& core() { return core_; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Nsf_Emu();
|
Nsf_Emu();
|
||||||
~Nsf_Emu();
|
~Nsf_Emu();
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual blargg_err_t start_track_( int );
|
virtual blargg_err_t start_track_( int );
|
||||||
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
||||||
virtual void set_tempo_( double );
|
virtual void set_tempo_( double );
|
||||||
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
||||||
virtual void update_eq( blip_eq_t const& );
|
virtual void update_eq( blip_eq_t const& );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { max_voices = 32 };
|
enum { max_voices = 32 };
|
||||||
const char* voice_names_ [32];
|
const char* voice_names_ [32];
|
||||||
int voice_types_ [32];
|
int voice_types_ [32];
|
||||||
int voice_count_;
|
int voice_count_;
|
||||||
Nsf_Core core_;
|
Nsf_Core core_;
|
||||||
|
|
||||||
blargg_err_t init_sound();
|
blargg_err_t init_sound();
|
||||||
void append_voices( const char* const names [], int const types [], int count );
|
void append_voices( const char* const names [], int const types [], int count );
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,328 +1,328 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Nsf_Impl.h"
|
#include "Nsf_Impl.h"
|
||||||
|
|
||||||
#include "blargg_endian.h"
|
#include "blargg_endian.h"
|
||||||
|
|
||||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
// number of frames until play interrupts init
|
// number of frames until play interrupts init
|
||||||
int const initial_play_delay = 7; // KikiKaikai needed this to work
|
int const initial_play_delay = 7; // KikiKaikai needed this to work
|
||||||
int const bank_size = 0x1000;
|
int const bank_size = 0x1000;
|
||||||
int const rom_addr = 0x8000;
|
int const rom_addr = 0x8000;
|
||||||
|
|
||||||
int Nsf_Impl::read_code( addr_t addr ) const
|
int Nsf_Impl::read_code( addr_t addr ) const
|
||||||
{
|
{
|
||||||
return *cpu.get_code( addr );
|
return *cpu.get_code( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Nsf_Impl::pcm_read( void* self, int addr )
|
int Nsf_Impl::pcm_read( void* self, int addr )
|
||||||
{
|
{
|
||||||
return STATIC_CAST(Nsf_Impl*,self)->read_code( addr );
|
return STATIC_CAST(Nsf_Impl*,self)->read_code( addr );
|
||||||
}
|
}
|
||||||
|
|
||||||
Nsf_Impl::Nsf_Impl() : rom( bank_size ), enable_w4011( true )
|
Nsf_Impl::Nsf_Impl() : rom( bank_size ), enable_w4011( true )
|
||||||
{
|
{
|
||||||
apu.dmc_reader( pcm_read, this );
|
apu.dmc_reader( pcm_read, this );
|
||||||
assert( offsetof (header_t,unused [4]) == header_t::size );
|
assert( offsetof (header_t,unused [4]) == header_t::size );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::unload()
|
void Nsf_Impl::unload()
|
||||||
{
|
{
|
||||||
rom.clear();
|
rom.clear();
|
||||||
high_ram.clear();
|
high_ram.clear();
|
||||||
Gme_Loader::unload();
|
Gme_Loader::unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
Nsf_Impl::~Nsf_Impl() { unload(); }
|
Nsf_Impl::~Nsf_Impl() { unload(); }
|
||||||
|
|
||||||
bool nsf_header_t::valid_tag() const
|
bool nsf_header_t::valid_tag() const
|
||||||
{
|
{
|
||||||
return 0 == memcmp( tag, "NESM\x1A", 5 );
|
return 0 == memcmp( tag, "NESM\x1A", 5 );
|
||||||
}
|
}
|
||||||
|
|
||||||
double nsf_header_t::clock_rate() const
|
double nsf_header_t::clock_rate() const
|
||||||
{
|
{
|
||||||
return pal_only() ? 1662607.125 : 1789772.727272727;
|
return pal_only() ? 1662607.125 : 1789772.727272727;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nsf_header_t::play_period() const
|
int nsf_header_t::play_period() const
|
||||||
{
|
{
|
||||||
// NTSC
|
// NTSC
|
||||||
int clocks = 29780;
|
int clocks = 29780;
|
||||||
int value = 0x411A;
|
int value = 0x411A;
|
||||||
byte const* rate_ptr = ntsc_speed;
|
byte const* rate_ptr = ntsc_speed;
|
||||||
|
|
||||||
// PAL
|
// PAL
|
||||||
if ( pal_only() )
|
if ( pal_only() )
|
||||||
{
|
{
|
||||||
clocks = 33247;
|
clocks = 33247;
|
||||||
value = 0x4E20;
|
value = 0x4E20;
|
||||||
rate_ptr = pal_speed;
|
rate_ptr = pal_speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default rate
|
// Default rate
|
||||||
int rate = get_le16( rate_ptr );
|
int rate = get_le16( rate_ptr );
|
||||||
if ( rate == 0 )
|
if ( rate == 0 )
|
||||||
rate = value;
|
rate = value;
|
||||||
|
|
||||||
// Custom rate
|
// Custom rate
|
||||||
if ( rate != value )
|
if ( rate != value )
|
||||||
clocks = (int) (rate * clock_rate() * (1.0/1000000.0));
|
clocks = (int) (rate * clock_rate() * (1.0/1000000.0));
|
||||||
|
|
||||||
return clocks;
|
return clocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets address, given pointer to it in file header. If zero, returns rom_addr.
|
// Gets address, given pointer to it in file header. If zero, returns rom_addr.
|
||||||
Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] )
|
Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] )
|
||||||
{
|
{
|
||||||
addr_t addr = get_le16( in );
|
addr_t addr = get_le16( in );
|
||||||
if ( addr == 0 )
|
if ( addr == 0 )
|
||||||
addr = rom_addr;
|
addr = rom_addr;
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Nsf_Impl::load_( Data_Reader& in )
|
blargg_err_t Nsf_Impl::load_( Data_Reader& in )
|
||||||
{
|
{
|
||||||
// pad ROM data with 0
|
// pad ROM data with 0
|
||||||
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
|
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
|
||||||
|
|
||||||
if ( !header_.valid_tag() )
|
if ( !header_.valid_tag() )
|
||||||
return blargg_err_file_type;
|
return blargg_err_file_type;
|
||||||
|
|
||||||
RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) );
|
RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) );
|
||||||
|
|
||||||
addr_t load_addr = get_addr( header_.load_addr );
|
addr_t load_addr = get_addr( header_.load_addr );
|
||||||
if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) )
|
if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) )
|
||||||
set_warning( "Load address is too low" );
|
set_warning( "Load address is too low" );
|
||||||
|
|
||||||
rom.set_addr( load_addr % bank_size );
|
rom.set_addr( load_addr % bank_size );
|
||||||
|
|
||||||
if ( header_.vers != 1 )
|
if ( header_.vers != 1 )
|
||||||
set_warning( "Unknown file version" );
|
set_warning( "Unknown file version" );
|
||||||
|
|
||||||
set_play_period( header_.play_period() );
|
set_play_period( header_.play_period() );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::write_bank( int bank, int data )
|
void Nsf_Impl::write_bank( int bank, int data )
|
||||||
{
|
{
|
||||||
// Find bank in ROM
|
// Find bank in ROM
|
||||||
int offset = rom.mask_addr( data * bank_size );
|
int offset = rom.mask_addr( data * bank_size );
|
||||||
if ( offset >= rom.size() )
|
if ( offset >= rom.size() )
|
||||||
special_event( "invalid bank" );
|
special_event( "invalid bank" );
|
||||||
void const* rom_data = rom.at_addr( offset );
|
void const* rom_data = rom.at_addr( offset );
|
||||||
|
|
||||||
#if !NSF_EMU_APU_ONLY
|
#if !NSF_EMU_APU_ONLY
|
||||||
if ( bank < bank_count - fds_banks && fds_enabled() )
|
if ( bank < bank_count - fds_banks && fds_enabled() )
|
||||||
{
|
{
|
||||||
// TODO: FDS bank switching is kind of hacky, might need to
|
// TODO: FDS bank switching is kind of hacky, might need to
|
||||||
// treat ROM as RAM so changes won't get lost when switching.
|
// treat ROM as RAM so changes won't get lost when switching.
|
||||||
byte* out = sram();
|
byte* out = sram();
|
||||||
if ( bank >= fds_banks )
|
if ( bank >= fds_banks )
|
||||||
{
|
{
|
||||||
out = fdsram();
|
out = fdsram();
|
||||||
bank -= fds_banks;
|
bank -= fds_banks;
|
||||||
}
|
}
|
||||||
memcpy( &out [bank * bank_size], rom_data, bank_size );
|
memcpy( &out [bank * bank_size], rom_data, bank_size );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ( bank >= fds_banks )
|
if ( bank >= fds_banks )
|
||||||
cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data );
|
cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::map_memory()
|
void Nsf_Impl::map_memory()
|
||||||
{
|
{
|
||||||
// Map standard things
|
// Map standard things
|
||||||
cpu.reset( unmapped_code() );
|
cpu.reset( unmapped_code() );
|
||||||
cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times
|
cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times
|
||||||
cpu.map_code( sram_addr, sram_size, sram() );
|
cpu.map_code( sram_addr, sram_size, sram() );
|
||||||
|
|
||||||
// Determine initial banks
|
// Determine initial banks
|
||||||
byte banks [bank_count];
|
byte banks [bank_count];
|
||||||
static byte const zero_banks [sizeof header_.banks] = { 0 };
|
static byte const zero_banks [sizeof header_.banks] = { 0 };
|
||||||
if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) )
|
if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) )
|
||||||
{
|
{
|
||||||
banks [0] = header_.banks [6];
|
banks [0] = header_.banks [6];
|
||||||
banks [1] = header_.banks [7];
|
banks [1] = header_.banks [7];
|
||||||
memcpy( banks + fds_banks, header_.banks, sizeof header_.banks );
|
memcpy( banks + fds_banks, header_.banks, sizeof header_.banks );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No initial banks, so assign them based on load_addr
|
// No initial banks, so assign them based on load_addr
|
||||||
int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size;
|
int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size;
|
||||||
unsigned total_banks = rom.size() / bank_size;
|
unsigned total_banks = rom.size() / bank_size;
|
||||||
for ( int i = bank_count; --i >= 0; )
|
for ( int i = bank_count; --i >= 0; )
|
||||||
{
|
{
|
||||||
int bank = i - first_bank;
|
int bank = i - first_bank;
|
||||||
if ( (unsigned) bank >= total_banks )
|
if ( (unsigned) bank >= total_banks )
|
||||||
bank = 0;
|
bank = 0;
|
||||||
banks [i] = bank;
|
banks [i] = bank;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map banks
|
// Map banks
|
||||||
for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i )
|
for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i )
|
||||||
write_bank( i, banks [i] );
|
write_bank( i, banks [i] );
|
||||||
|
|
||||||
// Map FDS RAM
|
// Map FDS RAM
|
||||||
if ( fds_enabled() )
|
if ( fds_enabled() )
|
||||||
cpu.map_code( rom_addr, fdsram_size, fdsram() );
|
cpu.map_code( rom_addr, fdsram_size, fdsram() );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Nsf_Impl::push_byte( int b )
|
inline void Nsf_Impl::push_byte( int b )
|
||||||
{
|
{
|
||||||
low_ram [0x100 + cpu.r.sp--] = b;
|
low_ram [0x100 + cpu.r.sp--] = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jumps to routine, given pointer to address in file header. Pushes idle_addr
|
// Jumps to routine, given pointer to address in file header. Pushes idle_addr
|
||||||
// as return address, NOT old PC.
|
// as return address, NOT old PC.
|
||||||
void Nsf_Impl::jsr_then_stop( byte const addr [] )
|
void Nsf_Impl::jsr_then_stop( byte const addr [] )
|
||||||
{
|
{
|
||||||
cpu.r.pc = get_addr( addr );
|
cpu.r.pc = get_addr( addr );
|
||||||
push_byte( (idle_addr - 1) >> 8 );
|
push_byte( (idle_addr - 1) >> 8 );
|
||||||
push_byte( (idle_addr - 1) );
|
push_byte( (idle_addr - 1) );
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Nsf_Impl::start_track( int track )
|
blargg_err_t Nsf_Impl::start_track( int track )
|
||||||
{
|
{
|
||||||
int speed_flags = 0;
|
int speed_flags = 0;
|
||||||
#if NSF_EMU_EXTRA_FLAGS
|
#if NSF_EMU_EXTRA_FLAGS
|
||||||
speed_flags = header().speed_flags;
|
speed_flags = header().speed_flags;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 );
|
apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 );
|
||||||
apu.enable_w4011_( enable_w4011 );
|
apu.enable_w4011_( enable_w4011 );
|
||||||
apu.write_register( 0, 0x4015, 0x0F );
|
apu.write_register( 0, 0x4015, 0x0F );
|
||||||
apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 );
|
apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 );
|
||||||
|
|
||||||
// Clear memory
|
// Clear memory
|
||||||
memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size );
|
memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size );
|
||||||
memset( low_ram, 0, low_ram_size );
|
memset( low_ram, 0, low_ram_size );
|
||||||
memset( sram(), 0, sram_size );
|
memset( sram(), 0, sram_size );
|
||||||
|
|
||||||
map_memory();
|
map_memory();
|
||||||
|
|
||||||
// Arrange time of first call to play routine
|
// Arrange time of first call to play routine
|
||||||
play_extra = 0;
|
play_extra = 0;
|
||||||
next_play = play_period;
|
next_play = play_period;
|
||||||
|
|
||||||
play_delay = initial_play_delay;
|
play_delay = initial_play_delay;
|
||||||
saved_state.pc = idle_addr;
|
saved_state.pc = idle_addr;
|
||||||
|
|
||||||
// Setup for call to init routine
|
// Setup for call to init routine
|
||||||
cpu.r.a = track;
|
cpu.r.a = track;
|
||||||
cpu.r.x = header_.pal_only();
|
cpu.r.x = header_.pal_only();
|
||||||
cpu.r.sp = 0xFF;
|
cpu.r.sp = 0xFF;
|
||||||
jsr_then_stop( header_.init_addr );
|
jsr_then_stop( header_.init_addr );
|
||||||
if ( cpu.r.pc < get_addr( header_.load_addr ) )
|
if ( cpu.r.pc < get_addr( header_.load_addr ) )
|
||||||
set_warning( "Init address < load address" );
|
set_warning( "Init address < load address" );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::unmapped_write( addr_t addr, int data )
|
void Nsf_Impl::unmapped_write( addr_t addr, int data )
|
||||||
{
|
{
|
||||||
dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data );
|
dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
int Nsf_Impl::unmapped_read( addr_t addr )
|
int Nsf_Impl::unmapped_read( addr_t addr )
|
||||||
{
|
{
|
||||||
dprintf( "Unmapped read $%04X\n", (int) addr );
|
dprintf( "Unmapped read $%04X\n", (int) addr );
|
||||||
return addr >> 8;
|
return addr >> 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::special_event( const char str [] )
|
void Nsf_Impl::special_event( const char str [] )
|
||||||
{
|
{
|
||||||
dprintf( "%s\n", str );
|
dprintf( "%s\n", str );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::run_once( time_t end )
|
void Nsf_Impl::run_once( time_t end )
|
||||||
{
|
{
|
||||||
// Emulate until next play call if possible
|
// Emulate until next play call if possible
|
||||||
if ( run_cpu_until( min( next_play, end ) ) )
|
if ( run_cpu_until( min( next_play, end ) ) )
|
||||||
{
|
{
|
||||||
// Halt instruction encountered
|
// Halt instruction encountered
|
||||||
|
|
||||||
if ( cpu.r.pc != idle_addr )
|
if ( cpu.r.pc != idle_addr )
|
||||||
{
|
{
|
||||||
special_event( "illegal instruction" );
|
special_event( "illegal instruction" );
|
||||||
cpu.count_error();
|
cpu.count_error();
|
||||||
cpu.set_time( cpu.end_time() );
|
cpu.set_time( cpu.end_time() );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init/play routine returned
|
// Init/play routine returned
|
||||||
play_delay = 1; // play can now be called regularly
|
play_delay = 1; // play can now be called regularly
|
||||||
|
|
||||||
if ( saved_state.pc == idle_addr )
|
if ( saved_state.pc == idle_addr )
|
||||||
{
|
{
|
||||||
// nothing to run
|
// nothing to run
|
||||||
time_t t = cpu.end_time();
|
time_t t = cpu.end_time();
|
||||||
if ( cpu.time() < t )
|
if ( cpu.time() < t )
|
||||||
cpu.set_time( t );
|
cpu.set_time( t );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// continue init routine that was interrupted by play routine
|
// continue init routine that was interrupted by play routine
|
||||||
cpu.r = saved_state;
|
cpu.r = saved_state;
|
||||||
saved_state.pc = idle_addr;
|
saved_state.pc = idle_addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( time() >= next_play )
|
if ( time() >= next_play )
|
||||||
{
|
{
|
||||||
// Calculate time of next call to play routine
|
// Calculate time of next call to play routine
|
||||||
play_extra ^= 1; // extra clock every other call
|
play_extra ^= 1; // extra clock every other call
|
||||||
next_play += play_period + play_extra;
|
next_play += play_period + play_extra;
|
||||||
|
|
||||||
// Call routine if ready
|
// Call routine if ready
|
||||||
if ( play_delay && !--play_delay )
|
if ( play_delay && !--play_delay )
|
||||||
{
|
{
|
||||||
// Save state if init routine is still running
|
// Save state if init routine is still running
|
||||||
if ( cpu.r.pc != idle_addr )
|
if ( cpu.r.pc != idle_addr )
|
||||||
{
|
{
|
||||||
check( saved_state.pc == idle_addr );
|
check( saved_state.pc == idle_addr );
|
||||||
saved_state = cpu.r;
|
saved_state = cpu.r;
|
||||||
special_event( "play called during init" );
|
special_event( "play called during init" );
|
||||||
}
|
}
|
||||||
|
|
||||||
jsr_then_stop( header_.play_addr );
|
jsr_then_stop( header_.play_addr );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::run_until( time_t end )
|
void Nsf_Impl::run_until( time_t end )
|
||||||
{
|
{
|
||||||
while ( time() < end )
|
while ( time() < end )
|
||||||
run_once( end );
|
run_once( end );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nsf_Impl::end_frame( time_t end )
|
void Nsf_Impl::end_frame( time_t end )
|
||||||
{
|
{
|
||||||
if ( time() < end )
|
if ( time() < end )
|
||||||
run_until( end );
|
run_until( end );
|
||||||
cpu.adjust_time( -end );
|
cpu.adjust_time( -end );
|
||||||
|
|
||||||
// Localize to new time frame
|
// Localize to new time frame
|
||||||
next_play -= end;
|
next_play -= end;
|
||||||
check( next_play >= 0 );
|
check( next_play >= 0 );
|
||||||
if ( next_play < 0 )
|
if ( next_play < 0 )
|
||||||
next_play = 0;
|
next_play = 0;
|
||||||
|
|
||||||
apu.end_frame( end );
|
apu.end_frame( end );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,194 +1,194 @@
|
||||||
// Loads NSF file and emulates CPU and RAM, no sound chips
|
// Loads NSF file and emulates CPU and RAM, no sound chips
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef NSF_IMPL_H
|
#ifndef NSF_IMPL_H
|
||||||
#define NSF_IMPL_H
|
#define NSF_IMPL_H
|
||||||
|
|
||||||
#include "Gme_Loader.h"
|
#include "Gme_Loader.h"
|
||||||
#include "Nes_Cpu.h"
|
#include "Nes_Cpu.h"
|
||||||
#include "Rom_Data.h"
|
#include "Rom_Data.h"
|
||||||
#include "Nes_Apu.h"
|
#include "Nes_Apu.h"
|
||||||
|
|
||||||
// NSF file header
|
// NSF file header
|
||||||
struct nsf_header_t
|
struct nsf_header_t
|
||||||
{
|
{
|
||||||
typedef unsigned char byte;
|
typedef unsigned char byte;
|
||||||
enum { size = 0x80 };
|
enum { size = 0x80 };
|
||||||
|
|
||||||
char tag [ 5];
|
char tag [ 5];
|
||||||
byte vers;
|
byte vers;
|
||||||
byte track_count;
|
byte track_count;
|
||||||
byte first_track;
|
byte first_track;
|
||||||
byte load_addr [ 2];
|
byte load_addr [ 2];
|
||||||
byte init_addr [ 2];
|
byte init_addr [ 2];
|
||||||
byte play_addr [ 2];
|
byte play_addr [ 2];
|
||||||
char game [32]; // NOT null-terminated if 32 chars in length
|
char game [32]; // NOT null-terminated if 32 chars in length
|
||||||
char author [32];
|
char author [32];
|
||||||
char copyright [32];
|
char copyright [32];
|
||||||
byte ntsc_speed [ 2];
|
byte ntsc_speed [ 2];
|
||||||
byte banks [ 8];
|
byte banks [ 8];
|
||||||
byte pal_speed [ 2];
|
byte pal_speed [ 2];
|
||||||
byte speed_flags;
|
byte speed_flags;
|
||||||
byte chip_flags;
|
byte chip_flags;
|
||||||
byte unused [ 4];
|
byte unused [ 4];
|
||||||
|
|
||||||
// Sound chip masks
|
// Sound chip masks
|
||||||
enum {
|
enum {
|
||||||
vrc6_mask = 1 << 0,
|
vrc6_mask = 1 << 0,
|
||||||
vrc7_mask = 1 << 1,
|
vrc7_mask = 1 << 1,
|
||||||
fds_mask = 1 << 2,
|
fds_mask = 1 << 2,
|
||||||
mmc5_mask = 1 << 3,
|
mmc5_mask = 1 << 3,
|
||||||
namco_mask = 1 << 4,
|
namco_mask = 1 << 4,
|
||||||
fme7_mask = 1 << 5,
|
fme7_mask = 1 << 5,
|
||||||
all_mask = (1 << 6) - 1
|
all_mask = (1 << 6) - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// True if header has proper NSF file signature
|
// True if header has proper NSF file signature
|
||||||
bool valid_tag() const;
|
bool valid_tag() const;
|
||||||
|
|
||||||
// True if file supports only PAL speed
|
// True if file supports only PAL speed
|
||||||
bool pal_only() const { return (speed_flags & 3) == 1; }
|
bool pal_only() const { return (speed_flags & 3) == 1; }
|
||||||
|
|
||||||
// Clocks per second
|
// Clocks per second
|
||||||
double clock_rate() const;
|
double clock_rate() const;
|
||||||
|
|
||||||
// Clocks between calls to play routine
|
// Clocks between calls to play routine
|
||||||
int play_period() const;
|
int play_period() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Loads NSF file into memory, then emulates CPU, RAM, and ROM.
|
/* Loads NSF file into memory, then emulates CPU, RAM, and ROM.
|
||||||
Non-memory accesses are routed through cpu_read() and cpu_write(). */
|
Non-memory accesses are routed through cpu_read() and cpu_write(). */
|
||||||
class Nsf_Impl : public Gme_Loader {
|
class Nsf_Impl : public Gme_Loader {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Sound chip
|
// Sound chip
|
||||||
Nes_Apu* nes_apu() { return &apu; }
|
Nes_Apu* nes_apu() { return &apu; }
|
||||||
|
|
||||||
// Starts track, where 0 is the first
|
// Starts track, where 0 is the first
|
||||||
virtual blargg_err_t start_track( int );
|
virtual blargg_err_t start_track( int );
|
||||||
|
|
||||||
// Emulates to at least time t, then begins new time frame at
|
// Emulates to at least time t, then begins new time frame at
|
||||||
// time t. Might emulate a few clocks extra, so after returning,
|
// time t. Might emulate a few clocks extra, so after returning,
|
||||||
// time() may not be zero.
|
// time() may not be zero.
|
||||||
typedef int time_t; // clock count
|
typedef int time_t; // clock count
|
||||||
virtual void end_frame( time_t n );
|
virtual void end_frame( time_t n );
|
||||||
|
|
||||||
// Finer control
|
// Finer control
|
||||||
|
|
||||||
// Header for currently loaded file
|
// Header for currently loaded file
|
||||||
typedef nsf_header_t header_t;
|
typedef nsf_header_t header_t;
|
||||||
header_t const& header() const { return header_; }
|
header_t const& header() const { return header_; }
|
||||||
|
|
||||||
// Sets clocks between calls to play routine to p + 1/2 clock
|
// Sets clocks between calls to play routine to p + 1/2 clock
|
||||||
void set_play_period( int p ) { play_period = p; }
|
void set_play_period( int p ) { play_period = p; }
|
||||||
|
|
||||||
// Time play routine will next be called
|
// Time play routine will next be called
|
||||||
time_t play_time() const { return next_play; }
|
time_t play_time() const { return next_play; }
|
||||||
|
|
||||||
// Emulates to at least time t. Might emulate a few clocks extra.
|
// Emulates to at least time t. Might emulate a few clocks extra.
|
||||||
virtual void run_until( time_t t );
|
virtual void run_until( time_t t );
|
||||||
|
|
||||||
// Time emulated to
|
// Time emulated to
|
||||||
time_t time() const { return cpu.time(); }
|
time_t time() const { return cpu.time(); }
|
||||||
|
|
||||||
void enable_w4011_(bool enable = true) { enable_w4011 = enable; }
|
void enable_w4011_(bool enable = true) { enable_w4011 = enable; }
|
||||||
|
|
||||||
Rom_Data const& rom_() const { return rom; }
|
Rom_Data const& rom_() const { return rom; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Nsf_Core use
|
// Nsf_Core use
|
||||||
|
|
||||||
typedef int addr_t;
|
typedef int addr_t;
|
||||||
|
|
||||||
// Called for unmapped accesses. Default just prints info if debugging.
|
// Called for unmapped accesses. Default just prints info if debugging.
|
||||||
virtual void unmapped_write( addr_t, int data );
|
virtual void unmapped_write( addr_t, int data );
|
||||||
virtual int unmapped_read( addr_t );
|
virtual int unmapped_read( addr_t );
|
||||||
|
|
||||||
// Override in derived class
|
// Override in derived class
|
||||||
// Bank writes and RAM at 0-$7FF and $6000-$7FFF are handled internally
|
// Bank writes and RAM at 0-$7FF and $6000-$7FFF are handled internally
|
||||||
virtual int cpu_read( addr_t a ) { return unmapped_read( a ); }
|
virtual int cpu_read( addr_t a ) { return unmapped_read( a ); }
|
||||||
virtual void cpu_write( addr_t a, int data ){ unmapped_write( a, data ); }
|
virtual void cpu_write( addr_t a, int data ){ unmapped_write( a, data ); }
|
||||||
|
|
||||||
// Reads byte as CPU would when executing code. Only works for RAM/ROM,
|
// Reads byte as CPU would when executing code. Only works for RAM/ROM,
|
||||||
// NOT I/O like sound chips.
|
// NOT I/O like sound chips.
|
||||||
int read_code( addr_t addr ) const;
|
int read_code( addr_t addr ) const;
|
||||||
|
|
||||||
// Debugger services
|
// Debugger services
|
||||||
|
|
||||||
enum { mem_size = 0x10000 };
|
enum { mem_size = 0x10000 };
|
||||||
|
|
||||||
// CPU sits here when waiting for next call to play routine
|
// CPU sits here when waiting for next call to play routine
|
||||||
enum { idle_addr = 0x5FF6 };
|
enum { idle_addr = 0x5FF6 };
|
||||||
|
|
||||||
Nes_Cpu cpu;
|
Nes_Cpu cpu;
|
||||||
|
|
||||||
// Runs CPU to at least time t and returns false, or returns true
|
// Runs CPU to at least time t and returns false, or returns true
|
||||||
// if it encounters illegal instruction (halt).
|
// if it encounters illegal instruction (halt).
|
||||||
virtual bool run_cpu_until( time_t t );
|
virtual bool run_cpu_until( time_t t );
|
||||||
|
|
||||||
// CPU calls through to these to access memory (except instructions)
|
// CPU calls through to these to access memory (except instructions)
|
||||||
int read_mem( addr_t );
|
int read_mem( addr_t );
|
||||||
void write_mem( addr_t, int );
|
void write_mem( addr_t, int );
|
||||||
|
|
||||||
// Address of play routine
|
// Address of play routine
|
||||||
addr_t play_addr() const { return get_addr( header_.play_addr ); }
|
addr_t play_addr() const { return get_addr( header_.play_addr ); }
|
||||||
|
|
||||||
// Same as run_until, except emulation stops for any event (routine returned,
|
// Same as run_until, except emulation stops for any event (routine returned,
|
||||||
// play routine called, illegal instruction).
|
// play routine called, illegal instruction).
|
||||||
void run_once( time_t );
|
void run_once( time_t );
|
||||||
|
|
||||||
// Make a note of event
|
// Make a note of event
|
||||||
virtual void special_event( const char str [] );
|
virtual void special_event( const char str [] );
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Nsf_Impl();
|
Nsf_Impl();
|
||||||
~Nsf_Impl();
|
~Nsf_Impl();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum { low_ram_size = 0x800 };
|
enum { low_ram_size = 0x800 };
|
||||||
enum { fdsram_size = 0x6000 };
|
enum { fdsram_size = 0x6000 };
|
||||||
enum { sram_size = 0x2000 };
|
enum { sram_size = 0x2000 };
|
||||||
enum { unmapped_size= Nes_Cpu::page_size + 8 };
|
enum { unmapped_size= Nes_Cpu::page_size + 8 };
|
||||||
enum { fds_banks = 2 };
|
enum { fds_banks = 2 };
|
||||||
enum { bank_count = fds_banks + 8 };
|
enum { bank_count = fds_banks + 8 };
|
||||||
enum { banks_addr = idle_addr };
|
enum { banks_addr = idle_addr };
|
||||||
enum { sram_addr = 0x6000 };
|
enum { sram_addr = 0x6000 };
|
||||||
|
|
||||||
blargg_vector<byte> high_ram;
|
blargg_vector<byte> high_ram;
|
||||||
Rom_Data rom;
|
Rom_Data rom;
|
||||||
|
|
||||||
// Play routine timing
|
// Play routine timing
|
||||||
time_t next_play;
|
time_t next_play;
|
||||||
time_t play_period;
|
time_t play_period;
|
||||||
int play_extra;
|
int play_extra;
|
||||||
int play_delay;
|
int play_delay;
|
||||||
bool enable_w4011;
|
bool enable_w4011;
|
||||||
Nes_Cpu::registers_t saved_state; // of interrupted init routine
|
Nes_Cpu::registers_t saved_state; // of interrupted init routine
|
||||||
|
|
||||||
// Large objects after others
|
// Large objects after others
|
||||||
header_t header_;
|
header_t header_;
|
||||||
Nes_Apu apu;
|
Nes_Apu apu;
|
||||||
byte low_ram [low_ram_size];
|
byte low_ram [low_ram_size];
|
||||||
|
|
||||||
// Larger RAM areas allocated separately
|
// Larger RAM areas allocated separately
|
||||||
enum { fdsram_offset = sram_size + unmapped_size };
|
enum { fdsram_offset = sram_size + unmapped_size };
|
||||||
byte* sram() { return high_ram.begin(); }
|
byte* sram() { return high_ram.begin(); }
|
||||||
byte* unmapped_code() { return &high_ram [sram_size]; }
|
byte* unmapped_code() { return &high_ram [sram_size]; }
|
||||||
byte* fdsram() { return &high_ram [fdsram_offset]; }
|
byte* fdsram() { return &high_ram [fdsram_offset]; }
|
||||||
int fds_enabled() const { return header_.chip_flags & header_t::fds_mask; }
|
int fds_enabled() const { return header_.chip_flags & header_t::fds_mask; }
|
||||||
|
|
||||||
void map_memory();
|
void map_memory();
|
||||||
void write_bank( int index, int data );
|
void write_bank( int index, int data );
|
||||||
void jsr_then_stop( byte const addr [] );
|
void jsr_then_stop( byte const addr [] );
|
||||||
void push_byte( int );
|
void push_byte( int );
|
||||||
static addr_t get_addr( byte const [] );
|
static addr_t get_addr( byte const [] );
|
||||||
static int pcm_read( void*, int );
|
static int pcm_read( void*, int );
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,74 +1,74 @@
|
||||||
// Nintendo NES/Famicom NSFE music file emulator
|
// Nintendo NES/Famicom NSFE music file emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef NSFE_EMU_H
|
#ifndef NSFE_EMU_H
|
||||||
#define NSFE_EMU_H
|
#define NSFE_EMU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Nsf_Emu.h"
|
#include "Nsf_Emu.h"
|
||||||
class Nsfe_Emu;
|
class Nsfe_Emu;
|
||||||
|
|
||||||
// Allows reading info from NSFE file without creating emulator
|
// Allows reading info from NSFE file without creating emulator
|
||||||
class Nsfe_Info {
|
class Nsfe_Info {
|
||||||
public:
|
public:
|
||||||
blargg_err_t load( Data_Reader&, Nsfe_Emu* );
|
blargg_err_t load( Data_Reader&, Nsfe_Emu* );
|
||||||
|
|
||||||
struct info_t : Nsf_Emu::header_t
|
struct info_t : Nsf_Emu::header_t
|
||||||
{
|
{
|
||||||
char game [256];
|
char game [256];
|
||||||
char author [256];
|
char author [256];
|
||||||
char copyright [256];
|
char copyright [256];
|
||||||
char dumper [256];
|
char dumper [256];
|
||||||
} info;
|
} info;
|
||||||
|
|
||||||
blargg_vector<unsigned char> data;
|
blargg_vector<unsigned char> data;
|
||||||
|
|
||||||
void disable_playlist( bool = true );
|
void disable_playlist( bool = true );
|
||||||
|
|
||||||
blargg_err_t track_info_( track_info_t* out, int track ) const;
|
blargg_err_t track_info_( track_info_t* out, int track ) const;
|
||||||
|
|
||||||
int remap_track( int i ) const;
|
int remap_track( int i ) const;
|
||||||
|
|
||||||
void unload();
|
void unload();
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Nsfe_Info();
|
Nsfe_Info();
|
||||||
~Nsfe_Info();
|
~Nsfe_Info();
|
||||||
BLARGG_DISABLE_NOTHROW
|
BLARGG_DISABLE_NOTHROW
|
||||||
private:
|
private:
|
||||||
blargg_vector<char> track_name_data;
|
blargg_vector<char> track_name_data;
|
||||||
blargg_vector<const char*> track_names;
|
blargg_vector<const char*> track_names;
|
||||||
blargg_vector<unsigned char> playlist;
|
blargg_vector<unsigned char> playlist;
|
||||||
blargg_vector<char [4]> track_times;
|
blargg_vector<char [4]> track_times;
|
||||||
int actual_track_count_;
|
int actual_track_count_;
|
||||||
bool playlist_disabled;
|
bool playlist_disabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Nsfe_Emu : public Nsf_Emu {
|
class Nsfe_Emu : public Nsf_Emu {
|
||||||
public:
|
public:
|
||||||
static gme_type_t static_type() { return gme_nsfe_type; }
|
static gme_type_t static_type() { return gme_nsfe_type; }
|
||||||
|
|
||||||
struct header_t { char tag [4]; };
|
struct header_t { char tag [4]; };
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Nsfe_Emu();
|
Nsfe_Emu();
|
||||||
~Nsfe_Emu();
|
~Nsfe_Emu();
|
||||||
virtual void unload();
|
virtual void unload();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t load_( Data_Reader& );
|
virtual blargg_err_t load_( Data_Reader& );
|
||||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||||
virtual blargg_err_t start_track_( int );
|
virtual blargg_err_t start_track_( int );
|
||||||
virtual void clear_playlist_();
|
virtual void clear_playlist_();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Nsfe_Info info;
|
Nsfe_Info info;
|
||||||
|
|
||||||
void disable_playlist_( bool b );
|
void disable_playlist_( bool b );
|
||||||
friend class Nsfe_Info;
|
friend class Nsfe_Info;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -2,8 +2,16 @@
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
#include "ym2413.h"
|
extern "C" {
|
||||||
#include "fmopl.h"
|
#include "../vgmplay/chips/mamedef.h"
|
||||||
|
#include "../vgmplay/chips/emu2413.h"
|
||||||
|
#include "../vgmplay/chips/fmopl.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char vrc7_inst[(16 + 3) * 8] =
|
||||||
|
{
|
||||||
|
#include "../vgmplay/chips/vrc7tone.h"
|
||||||
|
};
|
||||||
|
|
||||||
Opl_Apu::Opl_Apu() { opl = 0; opl_memory = 0; }
|
Opl_Apu::Opl_Apu() { opl = 0; opl_memory = 0; }
|
||||||
|
|
||||||
|
@ -20,26 +28,29 @@ blargg_err_t Opl_Apu::init( long clock, long rate, blip_time_t period, type_t ty
|
||||||
case type_opll:
|
case type_opll:
|
||||||
case type_msxmusic:
|
case type_msxmusic:
|
||||||
case type_smsfmunit:
|
case type_smsfmunit:
|
||||||
opl = ym2413_init( clock, rate, 0 );
|
opl = OPLL_new( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
|
||||||
|
OPLL_SetChipMode( (OPLL *) opl, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_vrc7:
|
case type_vrc7:
|
||||||
opl = ym2413_init( clock, rate, 1 );
|
opl = OPLL_new( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
|
||||||
|
OPLL_SetChipMode((OPLL *) opl, 1 );
|
||||||
|
OPLL_setPatch((OPLL *) opl, vrc7_inst);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_opl:
|
case type_opl:
|
||||||
opl = ym3526_init( clock, rate );
|
opl = ym3526_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_msxaudio:
|
case type_msxaudio:
|
||||||
//logfile = fopen("c:\\temp\\msxaudio.log", "wb");
|
//logfile = fopen("c:\\temp\\msxaudio.log", "wb");
|
||||||
opl = y8950_init( clock, rate );
|
opl = y8950_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
|
||||||
opl_memory = malloc( 32768 );
|
opl_memory = malloc( 32768 );
|
||||||
y8950_set_delta_t_memory( opl, opl_memory, 32768 );
|
y8950_set_delta_t_memory( opl, opl_memory, 32768 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_opl2:
|
case type_opl2:
|
||||||
opl = ym3812_init( clock, rate );
|
opl = ym3812_init( (BOOST::uint32_t) clock, (BOOST::uint32_t) rate );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
reset();
|
reset();
|
||||||
|
@ -56,7 +67,7 @@ Opl_Apu::~Opl_Apu()
|
||||||
case type_msxmusic:
|
case type_msxmusic:
|
||||||
case type_smsfmunit:
|
case type_smsfmunit:
|
||||||
case type_vrc7:
|
case type_vrc7:
|
||||||
ym2413_shutdown( opl );
|
OPLL_delete( (OPLL *) opl );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_opl:
|
case type_opl:
|
||||||
|
@ -88,7 +99,7 @@ void Opl_Apu::reset()
|
||||||
case type_msxmusic:
|
case type_msxmusic:
|
||||||
case type_smsfmunit:
|
case type_smsfmunit:
|
||||||
case type_vrc7:
|
case type_vrc7:
|
||||||
ym2413_reset_chip( opl );
|
OPLL_reset( (OPLL *) opl );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_opl:
|
case type_opl:
|
||||||
|
@ -114,8 +125,8 @@ void Opl_Apu::write_data( blip_time_t time, int data )
|
||||||
case type_msxmusic:
|
case type_msxmusic:
|
||||||
case type_smsfmunit:
|
case type_smsfmunit:
|
||||||
case type_vrc7:
|
case type_vrc7:
|
||||||
ym2413_write( opl, 0, addr );
|
OPLL_writeIO( (OPLL *) opl, 0, addr );
|
||||||
ym2413_write( opl, 1, data );
|
OPLL_writeIO( (OPLL *) opl, 1, data );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case type_opl:
|
case type_opl:
|
||||||
|
@ -149,7 +160,7 @@ int Opl_Apu::read( blip_time_t time, int port )
|
||||||
case type_msxmusic:
|
case type_msxmusic:
|
||||||
case type_smsfmunit:
|
case type_smsfmunit:
|
||||||
case type_vrc7:
|
case type_vrc7:
|
||||||
return ym2413_read( opl, port );
|
return port ? 0xFF : 0;
|
||||||
|
|
||||||
case type_opl:
|
case type_opl:
|
||||||
return ym3526_read( opl, port );
|
return ym3526_read( opl, port );
|
||||||
|
@ -192,15 +203,15 @@ void Opl_Apu::run_until( blip_time_t end_time )
|
||||||
case type_smsfmunit:
|
case type_smsfmunit:
|
||||||
case type_vrc7:
|
case type_vrc7:
|
||||||
{
|
{
|
||||||
SAMP bufMO[ 1024 ];
|
e_int32 bufMO[ 1024 ];
|
||||||
SAMP bufRO[ 1024 ];
|
e_int32 bufRO[ 1024 ];
|
||||||
SAMP * buffers[2] = { bufMO, bufRO };
|
e_int32 * buffers[2] = { bufMO, bufRO };
|
||||||
|
|
||||||
while ( count > 0 )
|
while ( count > 0 )
|
||||||
{
|
{
|
||||||
unsigned todo = count;
|
unsigned todo = count;
|
||||||
if ( todo > 1024 ) todo = 1024;
|
if ( todo > 1024 ) todo = 1024;
|
||||||
ym2413_update_one( opl, buffers, todo );
|
OPLL_calc_stereo( (OPLL *) opl, buffers, todo, -1 );
|
||||||
|
|
||||||
if ( output_ )
|
if ( output_ )
|
||||||
{
|
{
|
||||||
|
@ -229,7 +240,9 @@ void Opl_Apu::run_until( blip_time_t end_time )
|
||||||
case type_msxaudio:
|
case type_msxaudio:
|
||||||
case type_opl2:
|
case type_opl2:
|
||||||
{
|
{
|
||||||
OPLSAMPLE buffer[ 1024 ];
|
OPLSAMPLE bufL[ 1024 ];
|
||||||
|
OPLSAMPLE bufR[ 1024 ];
|
||||||
|
OPLSAMPLE* buffers[2] = {bufL, bufR};
|
||||||
|
|
||||||
while ( count > 0 )
|
while ( count > 0 )
|
||||||
{
|
{
|
||||||
|
@ -237,18 +250,18 @@ void Opl_Apu::run_until( blip_time_t end_time )
|
||||||
if ( todo > 1024 ) todo = 1024;
|
if ( todo > 1024 ) todo = 1024;
|
||||||
switch (type_)
|
switch (type_)
|
||||||
{
|
{
|
||||||
case type_opl: ym3526_update_one( opl, buffer, todo ); break;
|
case type_opl: ym3526_update_one( opl, buffers, todo ); break;
|
||||||
case type_msxaudio: y8950_update_one( opl, buffer, todo ); break;
|
case type_msxaudio: y8950_update_one( opl, buffers, todo ); break;
|
||||||
case type_opl2: ym3812_update_one( opl, buffer, todo ); break;
|
case type_opl2: ym3812_update_one( opl, buffers, todo ); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( output_ )
|
if ( output_ )
|
||||||
{
|
{
|
||||||
int last_amp = this->last_amp;
|
int last_amp = this->last_amp;
|
||||||
for ( unsigned i = 0; i < todo; i++ )
|
for ( unsigned i = 0; i < todo; i++ )
|
||||||
{
|
{
|
||||||
int amp = buffer [i];
|
int amp = bufL [i] + bufR [i];
|
||||||
int delta = amp - last_amp;
|
int delta = amp - last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -1,79 +1,79 @@
|
||||||
// $package. http://www.slack.net/~ant/
|
// $package. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Resampler.h"
|
#include "Resampler.h"
|
||||||
|
|
||||||
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
Resampler::Resampler()
|
Resampler::Resampler()
|
||||||
{
|
{
|
||||||
write_pos = 0;
|
write_pos = 0;
|
||||||
rate_ = 0;
|
rate_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Resampler::~Resampler() { }
|
Resampler::~Resampler() { }
|
||||||
|
|
||||||
void Resampler::clear()
|
void Resampler::clear()
|
||||||
{
|
{
|
||||||
write_pos = 0;
|
write_pos = 0;
|
||||||
clear_();
|
clear_();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Resampler::resample_wrapper( sample_t out [], int* out_size,
|
inline int Resampler::resample_wrapper( sample_t out [], int* out_size,
|
||||||
sample_t const in [], int in_size )
|
sample_t const in [], int in_size )
|
||||||
{
|
{
|
||||||
assert( rate() );
|
assert( rate() );
|
||||||
|
|
||||||
sample_t* out_ = out;
|
sample_t* out_ = out;
|
||||||
int result = resample_( &out_, out + *out_size, in, in_size ) - in;
|
int result = resample_( &out_, out + *out_size, in, in_size ) - in;
|
||||||
assert( out_ <= out + *out_size );
|
assert( out_ <= out + *out_size );
|
||||||
assert( result <= in_size );
|
assert( result <= in_size );
|
||||||
|
|
||||||
*out_size = out_ - out;
|
*out_size = out_ - out;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Resampler::resample( sample_t out [], int out_size, sample_t const in [], int* in_size )
|
int Resampler::resample( sample_t out [], int out_size, sample_t const in [], int* in_size )
|
||||||
{
|
{
|
||||||
*in_size = resample_wrapper( out, &out_size, in, *in_size );
|
*in_size = resample_wrapper( out, &out_size, in, *in_size );
|
||||||
return out_size;
|
return out_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//// Buffering
|
//// Buffering
|
||||||
|
|
||||||
blargg_err_t Resampler::resize_buffer( int new_size )
|
blargg_err_t Resampler::resize_buffer( int new_size )
|
||||||
{
|
{
|
||||||
RETURN_ERR( buf.resize( new_size ) );
|
RETURN_ERR( buf.resize( new_size ) );
|
||||||
clear();
|
clear();
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Resampler::skip_input( int count )
|
int Resampler::skip_input( int count )
|
||||||
{
|
{
|
||||||
write_pos -= count;
|
write_pos -= count;
|
||||||
if ( write_pos < 0 ) // occurs when downsampling
|
if ( write_pos < 0 ) // occurs when downsampling
|
||||||
{
|
{
|
||||||
count += write_pos;
|
count += write_pos;
|
||||||
write_pos = 0;
|
write_pos = 0;
|
||||||
}
|
}
|
||||||
memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] );
|
memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] );
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Resampler::read( sample_t out [], int out_size )
|
int Resampler::read( sample_t out [], int out_size )
|
||||||
{
|
{
|
||||||
if ( out_size )
|
if ( out_size )
|
||||||
skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) );
|
skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) );
|
||||||
return out_size;
|
return out_size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,110 +1,110 @@
|
||||||
// Common interface for resamplers
|
// Common interface for resamplers
|
||||||
|
|
||||||
// $package
|
// $package
|
||||||
#ifndef RESAMPLER_H
|
#ifndef RESAMPLER_H
|
||||||
#define RESAMPLER_H
|
#define RESAMPLER_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
|
|
||||||
class Resampler {
|
class Resampler {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual ~Resampler();
|
virtual ~Resampler();
|
||||||
|
|
||||||
// Sets input/output resampling ratio
|
// Sets input/output resampling ratio
|
||||||
blargg_err_t set_rate( double );
|
blargg_err_t set_rate( double );
|
||||||
|
|
||||||
// Current input/output ratio
|
// Current input/output ratio
|
||||||
double rate() const { return rate_; }
|
double rate() const { return rate_; }
|
||||||
|
|
||||||
// Samples are 16-bit signed
|
// Samples are 16-bit signed
|
||||||
typedef short sample_t;
|
typedef short sample_t;
|
||||||
|
|
||||||
// One of two different buffering schemes can be used, as decided by the caller:
|
// One of two different buffering schemes can be used, as decided by the caller:
|
||||||
|
|
||||||
// External buffering (caller provides input buffer)
|
// External buffering (caller provides input buffer)
|
||||||
|
|
||||||
// Resamples in to at most n out samples and returns number of samples actually
|
// Resamples in to at most n out samples and returns number of samples actually
|
||||||
// written. Sets *in_size to number of input samples that aren't needed anymore
|
// written. Sets *in_size to number of input samples that aren't needed anymore
|
||||||
// and should be removed from input.
|
// and should be removed from input.
|
||||||
int resample( sample_t out [], int n, sample_t const in [], int* in_size );
|
int resample( sample_t out [], int n, sample_t const in [], int* in_size );
|
||||||
|
|
||||||
// Internal buffering (resampler manages buffer)
|
// Internal buffering (resampler manages buffer)
|
||||||
|
|
||||||
// Resizes input buffer to n samples, then clears it
|
// Resizes input buffer to n samples, then clears it
|
||||||
blargg_err_t resize_buffer( int n );
|
blargg_err_t resize_buffer( int n );
|
||||||
|
|
||||||
// Clears input buffer
|
// Clears input buffer
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
// Writes at most n samples to input buffer and returns number actually written.
|
// Writes at most n samples to input buffer and returns number actually written.
|
||||||
// Result will be less than n if there isn't enough free space in buffer.
|
// Result will be less than n if there isn't enough free space in buffer.
|
||||||
int write( sample_t const in [], int n );
|
int write( sample_t const in [], int n );
|
||||||
|
|
||||||
// Number of input samples in buffer
|
// Number of input samples in buffer
|
||||||
int written() const { return write_pos; }
|
int written() const { return write_pos; }
|
||||||
|
|
||||||
// Removes first n input samples from buffer, fewer if there aren't that many.
|
// Removes first n input samples from buffer, fewer if there aren't that many.
|
||||||
// Returns number of samples actually removed.
|
// Returns number of samples actually removed.
|
||||||
int skip_input( int n );
|
int skip_input( int n );
|
||||||
|
|
||||||
// Resamples input to at most n output samples. Returns number of samples
|
// Resamples input to at most n output samples. Returns number of samples
|
||||||
// actually written to out. Result will be less than n if there aren't
|
// actually written to out. Result will be less than n if there aren't
|
||||||
// enough input samples in buffer.
|
// enough input samples in buffer.
|
||||||
int read( sample_t out [], int n );
|
int read( sample_t out [], int n );
|
||||||
|
|
||||||
// Direct writing to input buffer, instead of using write( in, n ) above
|
// Direct writing to input buffer, instead of using write( in, n ) above
|
||||||
|
|
||||||
// Pointer to place to write input samples
|
// Pointer to place to write input samples
|
||||||
sample_t* buffer() { return &buf [write_pos]; }
|
sample_t* buffer() { return &buf [write_pos]; }
|
||||||
|
|
||||||
// Number of samples that can be written to buffer()
|
// Number of samples that can be written to buffer()
|
||||||
int buffer_free() const { return buf.size() - write_pos; }
|
int buffer_free() const { return buf.size() - write_pos; }
|
||||||
|
|
||||||
// Notifies resampler that n input samples have been written to buffer().
|
// Notifies resampler that n input samples have been written to buffer().
|
||||||
// N must not be greater than buffer_free().
|
// N must not be greater than buffer_free().
|
||||||
void write( int n );
|
void write( int n );
|
||||||
|
|
||||||
// Derived interface
|
// Derived interface
|
||||||
protected:
|
protected:
|
||||||
virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; )
|
virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; )
|
||||||
|
|
||||||
virtual void clear_() { }
|
virtual void clear_() { }
|
||||||
|
|
||||||
// Resample as many available in samples as will fit within out_size and
|
// Resample as many available in samples as will fit within out_size and
|
||||||
// return pointer past last input sample read and set *out just past
|
// return pointer past last input sample read and set *out just past
|
||||||
// the last output sample.
|
// the last output sample.
|
||||||
virtual sample_t const* resample_( sample_t** out, sample_t const* out_end,
|
virtual sample_t const* resample_( sample_t** out, sample_t const* out_end,
|
||||||
sample_t const in [], int in_size ) BLARGG_PURE( { return in; } )
|
sample_t const in [], int in_size ) BLARGG_PURE( { return in; } )
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Resampler();
|
Resampler();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
blargg_vector<sample_t> buf;
|
blargg_vector<sample_t> buf;
|
||||||
int write_pos;
|
int write_pos;
|
||||||
double rate_;
|
double rate_;
|
||||||
|
|
||||||
int resample_wrapper( sample_t out [], int* out_size,
|
int resample_wrapper( sample_t out [], int* out_size,
|
||||||
sample_t const in [], int in_size );
|
sample_t const in [], int in_size );
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Resampler::write( int count )
|
inline void Resampler::write( int count )
|
||||||
{
|
{
|
||||||
write_pos += count;
|
write_pos += count;
|
||||||
assert( (unsigned) write_pos <= buf.size() );
|
assert( (unsigned) write_pos <= buf.size() );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline blargg_err_t Resampler::set_rate_( double r )
|
inline blargg_err_t Resampler::set_rate_( double r )
|
||||||
{
|
{
|
||||||
rate_ = r;
|
rate_ = r;
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline blargg_err_t Resampler::set_rate( double r )
|
inline blargg_err_t Resampler::set_rate( double r )
|
||||||
{
|
{
|
||||||
return set_rate_( r );
|
return set_rate_( r );
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -1,339 +1,339 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Sap_Apu.h"
|
#include "Sap_Apu.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const max_frequency = 12000; // pure waves above this frequency are silenced
|
int const max_frequency = 12000; // pure waves above this frequency are silenced
|
||||||
|
|
||||||
static void gen_poly( unsigned mask, int count, byte out [] )
|
static void gen_poly( unsigned mask, int count, byte out [] )
|
||||||
{
|
{
|
||||||
unsigned n = 1;
|
unsigned n = 1;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int bits = 0;
|
int bits = 0;
|
||||||
int b = 0;
|
int b = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// implemented using "Galios configuration"
|
// implemented using "Galios configuration"
|
||||||
bits |= (n & 1) << b;
|
bits |= (n & 1) << b;
|
||||||
n = (n >> 1) ^ (mask * (n & 1));
|
n = (n >> 1) ^ (mask * (n & 1));
|
||||||
}
|
}
|
||||||
while ( b++ < 7 );
|
while ( b++ < 7 );
|
||||||
*out++ = bits;
|
*out++ = bits;
|
||||||
}
|
}
|
||||||
while ( --count );
|
while ( --count );
|
||||||
}
|
}
|
||||||
|
|
||||||
// poly5
|
// poly5
|
||||||
int const poly5_len = (1 << 5) - 1;
|
int const poly5_len = (1 << 5) - 1;
|
||||||
unsigned const poly5_mask = (1U << poly5_len) - 1;
|
unsigned const poly5_mask = (1U << poly5_len) - 1;
|
||||||
unsigned const poly5 = 0x167C6EA1;
|
unsigned const poly5 = 0x167C6EA1;
|
||||||
|
|
||||||
inline unsigned run_poly5( unsigned in, int shift )
|
inline unsigned run_poly5( unsigned in, int shift )
|
||||||
{
|
{
|
||||||
return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
|
return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define POLY_MASK( width, tap1, tap2 ) \
|
#define POLY_MASK( width, tap1, tap2 ) \
|
||||||
((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2)))
|
((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2)))
|
||||||
|
|
||||||
Sap_Apu_Impl::Sap_Apu_Impl()
|
Sap_Apu_Impl::Sap_Apu_Impl()
|
||||||
{
|
{
|
||||||
gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
|
gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
|
||||||
gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
|
gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
|
||||||
gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
|
gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
|
||||||
|
|
||||||
if ( 0 ) // comment out to recauculate poly5 constant
|
if ( 0 ) // comment out to recauculate poly5 constant
|
||||||
{
|
{
|
||||||
byte poly5 [4];
|
byte poly5 [4];
|
||||||
gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
|
gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
|
||||||
unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 +
|
unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 +
|
||||||
poly5 [1] * 0x100 + poly5 [0];
|
poly5 [1] * 0x100 + poly5 [0];
|
||||||
unsigned rev = n & 1;
|
unsigned rev = n & 1;
|
||||||
for ( int i = 1; i < poly5_len; i++ )
|
for ( int i = 1; i < poly5_len; i++ )
|
||||||
rev |= (n >> i & 1) << (poly5_len - i);
|
rev |= (n >> i & 1) << (poly5_len - i);
|
||||||
dprintf( "poly5: 0x%08lX\n", rev );
|
dprintf( "poly5: 0x%08lX\n", rev );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Apu::set_output( Blip_Buffer* b )
|
void Sap_Apu::set_output( Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
for ( int i = 0; i < osc_count; ++i )
|
for ( int i = 0; i < osc_count; ++i )
|
||||||
set_output( i, b );
|
set_output( i, b );
|
||||||
}
|
}
|
||||||
|
|
||||||
Sap_Apu::Sap_Apu()
|
Sap_Apu::Sap_Apu()
|
||||||
{
|
{
|
||||||
impl = NULL;
|
impl = NULL;
|
||||||
set_output( NULL );
|
set_output( NULL );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
|
void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
|
||||||
{
|
{
|
||||||
impl = new_impl;
|
impl = new_impl;
|
||||||
last_time = 0;
|
last_time = 0;
|
||||||
poly5_pos = 0;
|
poly5_pos = 0;
|
||||||
poly4_pos = 0;
|
poly4_pos = 0;
|
||||||
polym_pos = 0;
|
polym_pos = 0;
|
||||||
control = 0;
|
control = 0;
|
||||||
|
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
memset( &oscs [i], 0, offsetof (osc_t,output) );
|
memset( &oscs [i], 0, offsetof (osc_t,output) );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Sap_Apu::calc_periods()
|
inline void Sap_Apu::calc_periods()
|
||||||
{
|
{
|
||||||
// 15/64 kHz clock
|
// 15/64 kHz clock
|
||||||
int divider = 28;
|
int divider = 28;
|
||||||
if ( this->control & 1 )
|
if ( this->control & 1 )
|
||||||
divider = 114;
|
divider = 114;
|
||||||
|
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
{
|
{
|
||||||
osc_t* const osc = &oscs [i];
|
osc_t* const osc = &oscs [i];
|
||||||
|
|
||||||
int const osc_reload = osc->regs [0]; // cache
|
int const osc_reload = osc->regs [0]; // cache
|
||||||
int period = (osc_reload + 1) * divider;
|
int period = (osc_reload + 1) * divider;
|
||||||
static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
|
static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
|
||||||
if ( this->control & fast_bits [i] )
|
if ( this->control & fast_bits [i] )
|
||||||
{
|
{
|
||||||
period = osc_reload + 4;
|
period = osc_reload + 4;
|
||||||
if ( i & 1 )
|
if ( i & 1 )
|
||||||
{
|
{
|
||||||
period = osc_reload * 0x100 + osc [-1].regs [0] + 7;
|
period = osc_reload * 0x100 + osc [-1].regs [0] + 7;
|
||||||
if ( !(this->control & fast_bits [i - 1]) )
|
if ( !(this->control & fast_bits [i - 1]) )
|
||||||
period = (period - 6) * divider;
|
period = (period - 6) * divider;
|
||||||
|
|
||||||
if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
|
if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
|
||||||
dprintf( "Use of slave channel in 16-bit mode not supported\n" );
|
dprintf( "Use of slave channel in 16-bit mode not supported\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
osc->period = period;
|
osc->period = period;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Apu::run_until( blip_time_t end_time )
|
void Sap_Apu::run_until( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
calc_periods();
|
calc_periods();
|
||||||
Sap_Apu_Impl* const impl = this->impl; // cache
|
Sap_Apu_Impl* const impl = this->impl; // cache
|
||||||
|
|
||||||
// 17/9-bit poly selection
|
// 17/9-bit poly selection
|
||||||
byte const* polym = impl->poly17;
|
byte const* polym = impl->poly17;
|
||||||
int polym_len = poly17_len;
|
int polym_len = poly17_len;
|
||||||
if ( this->control & 0x80 )
|
if ( this->control & 0x80 )
|
||||||
{
|
{
|
||||||
polym_len = poly9_len;
|
polym_len = poly9_len;
|
||||||
polym = impl->poly9;
|
polym = impl->poly9;
|
||||||
}
|
}
|
||||||
polym_pos %= polym_len;
|
polym_pos %= polym_len;
|
||||||
|
|
||||||
for ( int i = 0; i < osc_count; i++ )
|
for ( int i = 0; i < osc_count; i++ )
|
||||||
{
|
{
|
||||||
osc_t* const osc = &oscs [i];
|
osc_t* const osc = &oscs [i];
|
||||||
blip_time_t time = last_time + osc->delay;
|
blip_time_t time = last_time + osc->delay;
|
||||||
blip_time_t const period = osc->period;
|
blip_time_t const period = osc->period;
|
||||||
|
|
||||||
// output
|
// output
|
||||||
Blip_Buffer* output = osc->output;
|
Blip_Buffer* output = osc->output;
|
||||||
if ( output )
|
if ( output )
|
||||||
{
|
{
|
||||||
int const osc_control = osc->regs [1]; // cache
|
int const osc_control = osc->regs [1]; // cache
|
||||||
int volume = (osc_control & 0x0F) * 2;
|
int volume = (osc_control & 0x0F) * 2;
|
||||||
if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
|
if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
|
||||||
((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
|
((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
|
||||||
{
|
{
|
||||||
if ( !(osc_control & 0x10) )
|
if ( !(osc_control & 0x10) )
|
||||||
volume >>= 1; // inaudible frequency = half volume
|
volume >>= 1; // inaudible frequency = half volume
|
||||||
|
|
||||||
int delta = volume - osc->last_amp;
|
int delta = volume - osc->last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
osc->last_amp = volume;
|
osc->last_amp = volume;
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
impl->synth.offset( last_time, delta, output );
|
impl->synth.offset( last_time, delta, output );
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: doesn't maintain high pass flip-flop (very minor issue)
|
// TODO: doesn't maintain high pass flip-flop (very minor issue)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// high pass
|
// high pass
|
||||||
static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
|
static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
|
||||||
blip_time_t period2 = 0; // unused if no high pass
|
blip_time_t period2 = 0; // unused if no high pass
|
||||||
blip_time_t time2 = end_time;
|
blip_time_t time2 = end_time;
|
||||||
if ( this->control & hipass_bits [i] )
|
if ( this->control & hipass_bits [i] )
|
||||||
{
|
{
|
||||||
period2 = osc [2].period;
|
period2 = osc [2].period;
|
||||||
time2 = last_time + osc [2].delay;
|
time2 = last_time + osc [2].delay;
|
||||||
if ( osc->invert )
|
if ( osc->invert )
|
||||||
{
|
{
|
||||||
// trick inner wave loop into inverting output
|
// trick inner wave loop into inverting output
|
||||||
osc->last_amp -= volume;
|
osc->last_amp -= volume;
|
||||||
volume = -volume;
|
volume = -volume;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( time < end_time || time2 < end_time )
|
if ( time < end_time || time2 < end_time )
|
||||||
{
|
{
|
||||||
// poly source
|
// poly source
|
||||||
static byte const poly1 [] = { 0x55, 0x55 }; // square wave
|
static byte const poly1 [] = { 0x55, 0x55 }; // square wave
|
||||||
byte const* poly = poly1;
|
byte const* poly = poly1;
|
||||||
int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
|
int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
|
||||||
int poly_pos = osc->phase & 1;
|
int poly_pos = osc->phase & 1;
|
||||||
int poly_inc = 1;
|
int poly_inc = 1;
|
||||||
if ( !(osc_control & 0x20) )
|
if ( !(osc_control & 0x20) )
|
||||||
{
|
{
|
||||||
poly = polym;
|
poly = polym;
|
||||||
poly_len = polym_len;
|
poly_len = polym_len;
|
||||||
poly_pos = polym_pos;
|
poly_pos = polym_pos;
|
||||||
if ( osc_control & 0x40 )
|
if ( osc_control & 0x40 )
|
||||||
{
|
{
|
||||||
poly = impl->poly4;
|
poly = impl->poly4;
|
||||||
poly_len = poly4_len;
|
poly_len = poly4_len;
|
||||||
poly_pos = poly4_pos;
|
poly_pos = poly4_pos;
|
||||||
}
|
}
|
||||||
poly_inc = period % poly_len;
|
poly_inc = period % poly_len;
|
||||||
poly_pos = (poly_pos + osc->delay) % poly_len;
|
poly_pos = (poly_pos + osc->delay) % poly_len;
|
||||||
}
|
}
|
||||||
poly_inc -= poly_len; // allows more optimized inner loop below
|
poly_inc -= poly_len; // allows more optimized inner loop below
|
||||||
|
|
||||||
// square/poly5 wave
|
// square/poly5 wave
|
||||||
unsigned wave = poly5;
|
unsigned wave = poly5;
|
||||||
check( poly5 & 1 ); // low bit is set for pure wave
|
check( poly5 & 1 ); // low bit is set for pure wave
|
||||||
int poly5_inc = 0;
|
int poly5_inc = 0;
|
||||||
if ( !(osc_control & 0x80) )
|
if ( !(osc_control & 0x80) )
|
||||||
{
|
{
|
||||||
wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
|
wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
|
||||||
poly5_inc = period % poly5_len;
|
poly5_inc = period % poly5_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
output->set_modified();
|
output->set_modified();
|
||||||
|
|
||||||
// Run wave and high pass interleved with each catching up to the other.
|
// Run wave and high pass interleved with each catching up to the other.
|
||||||
// Disabled high pass has no performance effect since inner wave loop
|
// Disabled high pass has no performance effect since inner wave loop
|
||||||
// makes no compromise for high pass, and only runs once in that case.
|
// makes no compromise for high pass, and only runs once in that case.
|
||||||
int osc_last_amp = osc->last_amp;
|
int osc_last_amp = osc->last_amp;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
// run high pass
|
// run high pass
|
||||||
if ( time2 < time )
|
if ( time2 < time )
|
||||||
{
|
{
|
||||||
int delta = -osc_last_amp;
|
int delta = -osc_last_amp;
|
||||||
if ( volume < 0 )
|
if ( volume < 0 )
|
||||||
delta += volume;
|
delta += volume;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
osc_last_amp += delta - volume;
|
osc_last_amp += delta - volume;
|
||||||
volume = -volume;
|
volume = -volume;
|
||||||
impl->synth.offset( time2, delta, output );
|
impl->synth.offset( time2, delta, output );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ( time2 <= time ) // must advance *past* time to avoid hang
|
while ( time2 <= time ) // must advance *past* time to avoid hang
|
||||||
time2 += period2;
|
time2 += period2;
|
||||||
|
|
||||||
// run wave
|
// run wave
|
||||||
blip_time_t end = end_time;
|
blip_time_t end = end_time;
|
||||||
if ( end > time2 )
|
if ( end > time2 )
|
||||||
end = time2;
|
end = time2;
|
||||||
while ( time < end )
|
while ( time < end )
|
||||||
{
|
{
|
||||||
if ( wave & 1 )
|
if ( wave & 1 )
|
||||||
{
|
{
|
||||||
int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
|
int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
|
||||||
if ( (poly_pos += poly_inc) < 0 )
|
if ( (poly_pos += poly_inc) < 0 )
|
||||||
poly_pos += poly_len;
|
poly_pos += poly_len;
|
||||||
int delta = amp - osc_last_amp;
|
int delta = amp - osc_last_amp;
|
||||||
if ( delta )
|
if ( delta )
|
||||||
{
|
{
|
||||||
osc_last_amp = amp;
|
osc_last_amp = amp;
|
||||||
impl->synth.offset( time, delta, output );
|
impl->synth.offset( time, delta, output );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wave = run_poly5( wave, poly5_inc );
|
wave = run_poly5( wave, poly5_inc );
|
||||||
time += period;
|
time += period;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while ( time < end_time || time2 < end_time );
|
while ( time < end_time || time2 < end_time );
|
||||||
|
|
||||||
osc->phase = poly_pos;
|
osc->phase = poly_pos;
|
||||||
osc->last_amp = osc_last_amp;
|
osc->last_amp = osc_last_amp;
|
||||||
}
|
}
|
||||||
|
|
||||||
osc->invert = 0;
|
osc->invert = 0;
|
||||||
if ( volume < 0 )
|
if ( volume < 0 )
|
||||||
{
|
{
|
||||||
// undo inversion trickery
|
// undo inversion trickery
|
||||||
osc->last_amp -= volume;
|
osc->last_amp -= volume;
|
||||||
osc->invert = 1;
|
osc->invert = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// maintain divider
|
// maintain divider
|
||||||
blip_time_t remain = end_time - time;
|
blip_time_t remain = end_time - time;
|
||||||
if ( remain > 0 )
|
if ( remain > 0 )
|
||||||
{
|
{
|
||||||
int count = (remain + period - 1) / period;
|
int count = (remain + period - 1) / period;
|
||||||
osc->phase ^= count;
|
osc->phase ^= count;
|
||||||
time += count * period;
|
time += count * period;
|
||||||
}
|
}
|
||||||
osc->delay = time - end_time;
|
osc->delay = time - end_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance polies
|
// advance polies
|
||||||
blip_time_t duration = end_time - last_time;
|
blip_time_t duration = end_time - last_time;
|
||||||
last_time = end_time;
|
last_time = end_time;
|
||||||
poly4_pos = (poly4_pos + duration) % poly4_len;
|
poly4_pos = (poly4_pos + duration) % poly4_len;
|
||||||
poly5_pos = (poly5_pos + duration) % poly5_len;
|
poly5_pos = (poly5_pos + duration) % poly5_len;
|
||||||
polym_pos += duration; // will get %'d on next call
|
polym_pos += duration; // will get %'d on next call
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Apu::write_data( blip_time_t time, int addr, int data )
|
void Sap_Apu::write_data( blip_time_t time, int addr, int data )
|
||||||
{
|
{
|
||||||
run_until( time );
|
run_until( time );
|
||||||
int i = (addr - 0xD200) >> 1;
|
int i = (addr - 0xD200) >> 1;
|
||||||
if ( (unsigned) i < osc_count )
|
if ( (unsigned) i < osc_count )
|
||||||
{
|
{
|
||||||
oscs [i].regs [addr & 1] = data;
|
oscs [i].regs [addr & 1] = data;
|
||||||
}
|
}
|
||||||
else if ( addr == 0xD208 )
|
else if ( addr == 0xD208 )
|
||||||
{
|
{
|
||||||
control = data;
|
control = data;
|
||||||
}
|
}
|
||||||
else if ( addr == 0xD209 )
|
else if ( addr == 0xD209 )
|
||||||
{
|
{
|
||||||
oscs [0].delay = 0;
|
oscs [0].delay = 0;
|
||||||
oscs [1].delay = 0;
|
oscs [1].delay = 0;
|
||||||
oscs [2].delay = 0;
|
oscs [2].delay = 0;
|
||||||
oscs [3].delay = 0;
|
oscs [3].delay = 0;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
// TODO: are polynomials reset in this case?
|
// TODO: are polynomials reset in this case?
|
||||||
else if ( addr == 0xD20F )
|
else if ( addr == 0xD20F )
|
||||||
{
|
{
|
||||||
if ( (data & 3) == 0 )
|
if ( (data & 3) == 0 )
|
||||||
polym_pos = 0;
|
polym_pos = 0;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Apu::end_frame( blip_time_t end_time )
|
void Sap_Apu::end_frame( blip_time_t end_time )
|
||||||
{
|
{
|
||||||
if ( end_time > last_time )
|
if ( end_time > last_time )
|
||||||
run_until( end_time );
|
run_until( end_time );
|
||||||
|
|
||||||
last_time -= end_time;
|
last_time -= end_time;
|
||||||
assert( last_time >= 0 );
|
assert( last_time >= 0 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,103 +1,103 @@
|
||||||
// Atari POKEY sound chip emulator
|
// Atari POKEY sound chip emulator
|
||||||
|
|
||||||
// Game_Music_Emu $vers
|
// Game_Music_Emu $vers
|
||||||
#ifndef SAP_APU_H
|
#ifndef SAP_APU_H
|
||||||
#define SAP_APU_H
|
#define SAP_APU_H
|
||||||
|
|
||||||
#include "blargg_common.h"
|
#include "blargg_common.h"
|
||||||
#include "Blip_Buffer.h"
|
#include "Blip_Buffer.h"
|
||||||
|
|
||||||
class Sap_Apu_Impl;
|
class Sap_Apu_Impl;
|
||||||
|
|
||||||
class Sap_Apu {
|
class Sap_Apu {
|
||||||
public:
|
public:
|
||||||
// Basics
|
// Basics
|
||||||
|
|
||||||
// Sets buffer to generate sound into, or 0 to mute
|
// Sets buffer to generate sound into, or 0 to mute
|
||||||
void set_output( Blip_Buffer* );
|
void set_output( Blip_Buffer* );
|
||||||
|
|
||||||
// Emulates to time t, then writes data to addr
|
// Emulates to time t, then writes data to addr
|
||||||
void write_data( blip_time_t t, int addr, int data );
|
void write_data( blip_time_t t, int addr, int data );
|
||||||
|
|
||||||
// Emulates to time t, then subtracts t from the current time.
|
// Emulates to time t, then subtracts t from the current time.
|
||||||
// OK if previous write call had time slightly after t.
|
// OK if previous write call had time slightly after t.
|
||||||
void end_frame( blip_time_t t );
|
void end_frame( blip_time_t t );
|
||||||
|
|
||||||
// More features
|
// More features
|
||||||
|
|
||||||
// Same as set_output(), but for a particular channel
|
// Same as set_output(), but for a particular channel
|
||||||
enum { osc_count = 4 };
|
enum { osc_count = 4 };
|
||||||
void set_output( int index, Blip_Buffer* );
|
void set_output( int index, Blip_Buffer* );
|
||||||
|
|
||||||
// Resets sound chip and sets Sap_Apu_Impl
|
// Resets sound chip and sets Sap_Apu_Impl
|
||||||
void reset( Sap_Apu_Impl* impl );
|
void reset( Sap_Apu_Impl* impl );
|
||||||
|
|
||||||
// Registers are at io_addr to io_addr+io_size-1
|
// Registers are at io_addr to io_addr+io_size-1
|
||||||
enum { io_addr = 0xD200 };
|
enum { io_addr = 0xD200 };
|
||||||
enum { io_size = 0x0A };
|
enum { io_size = 0x0A };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// noncopyable
|
// noncopyable
|
||||||
Sap_Apu( const Sap_Apu& );
|
Sap_Apu( const Sap_Apu& );
|
||||||
Sap_Apu& operator = ( const Sap_Apu& );
|
Sap_Apu& operator = ( const Sap_Apu& );
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Sap_Apu();
|
Sap_Apu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct osc_t
|
struct osc_t
|
||||||
{
|
{
|
||||||
unsigned char regs [2];
|
unsigned char regs [2];
|
||||||
unsigned char phase;
|
unsigned char phase;
|
||||||
unsigned char invert;
|
unsigned char invert;
|
||||||
int last_amp;
|
int last_amp;
|
||||||
blip_time_t delay;
|
blip_time_t delay;
|
||||||
blip_time_t period; // always recalculated before use; here for convenience
|
blip_time_t period; // always recalculated before use; here for convenience
|
||||||
Blip_Buffer* output;
|
Blip_Buffer* output;
|
||||||
};
|
};
|
||||||
osc_t oscs [osc_count];
|
osc_t oscs [osc_count];
|
||||||
Sap_Apu_Impl* impl;
|
Sap_Apu_Impl* impl;
|
||||||
blip_time_t last_time;
|
blip_time_t last_time;
|
||||||
int poly5_pos;
|
int poly5_pos;
|
||||||
int poly4_pos;
|
int poly4_pos;
|
||||||
int polym_pos;
|
int polym_pos;
|
||||||
int control;
|
int control;
|
||||||
|
|
||||||
void calc_periods();
|
void calc_periods();
|
||||||
void run_until( blip_time_t );
|
void run_until( blip_time_t );
|
||||||
|
|
||||||
enum { poly4_len = (1 << 4) - 1 };
|
enum { poly4_len = (1 << 4) - 1 };
|
||||||
enum { poly9_len = (1 << 9) - 1 };
|
enum { poly9_len = (1 << 9) - 1 };
|
||||||
enum { poly17_len = (1 << 17) - 1 };
|
enum { poly17_len = (1 << 17) - 1 };
|
||||||
friend class Sap_Apu_Impl;
|
friend class Sap_Apu_Impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects
|
// Common tables and Blip_Synth that can be shared among multiple Sap_Apu objects
|
||||||
class Sap_Apu_Impl {
|
class Sap_Apu_Impl {
|
||||||
public:
|
public:
|
||||||
// Set treble with synth.treble_eq()
|
// Set treble with synth.treble_eq()
|
||||||
Blip_Synth_Norm synth;
|
Blip_Synth_Norm synth;
|
||||||
|
|
||||||
// Sets overall volume, where 1.0is normal
|
// Sets overall volume, where 1.0is normal
|
||||||
void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); }
|
void volume( double d ) { synth.volume( 1.0 / Sap_Apu::osc_count / 30 * d ); }
|
||||||
|
|
||||||
|
|
||||||
// Implementation
|
// Implementation
|
||||||
public:
|
public:
|
||||||
Sap_Apu_Impl();
|
Sap_Apu_Impl();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BOOST::uint8_t poly4 [Sap_Apu::poly4_len /8 + 1];
|
BOOST::uint8_t poly4 [Sap_Apu::poly4_len /8 + 1];
|
||||||
BOOST::uint8_t poly9 [Sap_Apu::poly9_len /8 + 1];
|
BOOST::uint8_t poly9 [Sap_Apu::poly9_len /8 + 1];
|
||||||
BOOST::uint8_t poly17 [Sap_Apu::poly17_len/8 + 1];
|
BOOST::uint8_t poly17 [Sap_Apu::poly17_len/8 + 1];
|
||||||
friend class Sap_Apu;
|
friend class Sap_Apu;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void Sap_Apu::set_output( int i, Blip_Buffer* b )
|
inline void Sap_Apu::set_output( int i, Blip_Buffer* b )
|
||||||
{
|
{
|
||||||
assert( (unsigned) i < osc_count );
|
assert( (unsigned) i < osc_count );
|
||||||
oscs [i].output = b;
|
oscs [i].output = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,192 +1,192 @@
|
||||||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||||
|
|
||||||
#include "Sap_Core.h"
|
#include "Sap_Core.h"
|
||||||
|
|
||||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||||
General Public License as published by the Free Software Foundation; either
|
General Public License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version. This
|
version 2.1 of the License, or (at your option) any later version. This
|
||||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
details. You should have received a copy of the GNU Lesser General Public
|
details. You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this module; if not, write to the Free Software Foundation,
|
License along with this module; if not, write to the Free Software Foundation,
|
||||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||||
|
|
||||||
#include "blargg_source.h"
|
#include "blargg_source.h"
|
||||||
|
|
||||||
int const idle_addr = 0xD2D2;
|
int const idle_addr = 0xD2D2;
|
||||||
|
|
||||||
Sap_Core::Sap_Core()
|
Sap_Core::Sap_Core()
|
||||||
{
|
{
|
||||||
set_tempo( 1 );
|
set_tempo( 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Core::push( int b )
|
void Sap_Core::push( int b )
|
||||||
{
|
{
|
||||||
mem.ram [0x100 + cpu.r.sp--] = (byte) b;
|
mem.ram [0x100 + cpu.r.sp--] = (byte) b;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Core::jsr_then_stop( addr_t addr )
|
void Sap_Core::jsr_then_stop( addr_t addr )
|
||||||
{
|
{
|
||||||
cpu.r.pc = addr;
|
cpu.r.pc = addr;
|
||||||
|
|
||||||
// Some rips pop three bytes off stack before RTS.
|
// Some rips pop three bytes off stack before RTS.
|
||||||
push( (idle_addr - 1) >> 8 );
|
push( (idle_addr - 1) >> 8 );
|
||||||
push( idle_addr - 1 );
|
push( idle_addr - 1 );
|
||||||
|
|
||||||
// 3 bytes so that RTI or RTS will jump to idle_addr.
|
// 3 bytes so that RTI or RTS will jump to idle_addr.
|
||||||
// RTI will use the first two bytes as the address, 0xD2D2.
|
// RTI will use the first two bytes as the address, 0xD2D2.
|
||||||
// RTS will use the last two bytes, 0xD2D1, which it internally increments.
|
// RTS will use the last two bytes, 0xD2D1, which it internally increments.
|
||||||
push( (idle_addr - 1) >> 8 );
|
push( (idle_addr - 1) >> 8 );
|
||||||
push( (idle_addr - 1) >> 8 );
|
push( (idle_addr - 1) >> 8 );
|
||||||
push( idle_addr - 1 );
|
push( idle_addr - 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runs routine and allows it up to one second to return
|
// Runs routine and allows it up to one second to return
|
||||||
void Sap_Core::run_routine( addr_t addr )
|
void Sap_Core::run_routine( addr_t addr )
|
||||||
{
|
{
|
||||||
jsr_then_stop( addr );
|
jsr_then_stop( addr );
|
||||||
run_cpu( lines_per_frame * base_scanline_period * 60 );
|
run_cpu( lines_per_frame * base_scanline_period * 60 );
|
||||||
check( cpu.r.pc == idle_addr );
|
check( cpu.r.pc == idle_addr );
|
||||||
check( cpu.r.sp >= 0xFF - 6 );
|
check( cpu.r.sp >= 0xFF - 6 );
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Sap_Core::call_init( int track )
|
inline void Sap_Core::call_init( int track )
|
||||||
{
|
{
|
||||||
cpu.r.a = track;
|
cpu.r.a = track;
|
||||||
|
|
||||||
switch ( info.type )
|
switch ( info.type )
|
||||||
{
|
{
|
||||||
case 'B':
|
case 'B':
|
||||||
run_routine( info.init_addr );
|
run_routine( info.init_addr );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'C':
|
case 'C':
|
||||||
cpu.r.a = 0x70;
|
cpu.r.a = 0x70;
|
||||||
cpu.r.x = info.music_addr&0xFF;
|
cpu.r.x = info.music_addr&0xFF;
|
||||||
cpu.r.y = info.music_addr >> 8;
|
cpu.r.y = info.music_addr >> 8;
|
||||||
run_routine( info.play_addr + 3 );
|
run_routine( info.play_addr + 3 );
|
||||||
cpu.r.a = 0;
|
cpu.r.a = 0;
|
||||||
cpu.r.x = track;
|
cpu.r.x = track;
|
||||||
run_routine( info.play_addr + 3 );
|
run_routine( info.play_addr + 3 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D':
|
case 'D':
|
||||||
check( info.fastplay == lines_per_frame );
|
check( info.fastplay == lines_per_frame );
|
||||||
jsr_then_stop( info.init_addr );
|
jsr_then_stop( info.init_addr );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sap_Core::setup_ram()
|
void Sap_Core::setup_ram()
|
||||||
{
|
{
|
||||||
memset( &mem, 0, sizeof mem );
|
memset( &mem, 0, sizeof mem );
|
||||||
|
|
||||||
ram() [idle_addr] = cpu.halt_opcode;
|
ram() [idle_addr] = cpu.halt_opcode;
|
||||||
|
|
||||||
addr_t const irq_addr = idle_addr - 1;
|
addr_t const irq_addr = idle_addr - 1;
|
||||||
ram() [irq_addr] = cpu.halt_opcode;
|
ram() [irq_addr] = cpu.halt_opcode;
|
||||||
ram() [0xFFFE] = (byte) irq_addr;
|
ram() [0xFFFE] = (byte) irq_addr;
|
||||||
ram() [0xFFFF] = irq_addr >> 8;
|
ram() [0xFFFF] = irq_addr >> 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Sap_Core::start_track( int track, info_t const& new_info )
|
blargg_err_t Sap_Core::start_track( int track, info_t const& new_info )
|
||||||
{
|
{
|
||||||
info = new_info;
|
info = new_info;
|
||||||
|
|
||||||
check( ram() [idle_addr] == cpu.halt_opcode );
|
check( ram() [idle_addr] == cpu.halt_opcode );
|
||||||
|
|
||||||
apu_ .reset( &apu_impl_ );
|
apu_ .reset( &apu_impl_ );
|
||||||
apu2_.reset( &apu_impl_ );
|
apu2_.reset( &apu_impl_ );
|
||||||
|
|
||||||
cpu.reset( ram() );
|
cpu.reset( ram() );
|
||||||
|
|
||||||
frame_start = 0;
|
frame_start = 0;
|
||||||
next_play = play_period() * 4;
|
next_play = play_period() * 4;
|
||||||
saved_state.pc = idle_addr;
|
saved_state.pc = idle_addr;
|
||||||
|
|
||||||
time_mask = 0; // disables sound during init
|
time_mask = 0; // disables sound during init
|
||||||
call_init( track );
|
call_init( track );
|
||||||
time_mask = ~0;
|
time_mask = ~0;
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Sap_Core::run_until( time_t end )
|
blargg_err_t Sap_Core::run_until( time_t end )
|
||||||
{
|
{
|
||||||
while ( cpu.time() < end )
|
while ( cpu.time() < end )
|
||||||
{
|
{
|
||||||
time_t next = min( next_play, end );
|
time_t next = min( next_play, end );
|
||||||
if ( (run_cpu( next ) && cpu.r.pc != idle_addr) || cpu.error_count() )
|
if ( (run_cpu( next ) && cpu.r.pc != idle_addr) || cpu.error_count() )
|
||||||
// TODO: better error
|
// TODO: better error
|
||||||
return BLARGG_ERR( BLARGG_ERR_GENERIC, "Emulation error (illegal instruction)" );
|
return BLARGG_ERR( BLARGG_ERR_GENERIC, "Emulation error (illegal instruction)" );
|
||||||
|
|
||||||
if ( cpu.r.pc == idle_addr )
|
if ( cpu.r.pc == idle_addr )
|
||||||
{
|
{
|
||||||
if ( saved_state.pc == idle_addr )
|
if ( saved_state.pc == idle_addr )
|
||||||
{
|
{
|
||||||
// no code to run until next play call
|
// no code to run until next play call
|
||||||
cpu.set_time( next );
|
cpu.set_time( next );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// play had interrupted non-returning init, so restore registers
|
// play had interrupted non-returning init, so restore registers
|
||||||
// init routine was running
|
// init routine was running
|
||||||
check( cpu.r.sp == saved_state.sp - 3 );
|
check( cpu.r.sp == saved_state.sp - 3 );
|
||||||
cpu.r = saved_state;
|
cpu.r = saved_state;
|
||||||
saved_state.pc = idle_addr;
|
saved_state.pc = idle_addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cpu.time() >= next_play )
|
if ( cpu.time() >= next_play )
|
||||||
{
|
{
|
||||||
next_play += play_period();
|
next_play += play_period();
|
||||||
|
|
||||||
if ( cpu.r.pc == idle_addr || info.type == 'D' )
|
if ( cpu.r.pc == idle_addr || info.type == 'D' )
|
||||||
{
|
{
|
||||||
// Save state if init routine is still running
|
// Save state if init routine is still running
|
||||||
if ( cpu.r.pc != idle_addr )
|
if ( cpu.r.pc != idle_addr )
|
||||||
{
|
{
|
||||||
check( info.type == 'D' );
|
check( info.type == 'D' );
|
||||||
check( saved_state.pc == idle_addr );
|
check( saved_state.pc == idle_addr );
|
||||||
saved_state = cpu.r;
|
saved_state = cpu.r;
|
||||||
}
|
}
|
||||||
|
|
||||||
addr_t addr = info.play_addr;
|
addr_t addr = info.play_addr;
|
||||||
if ( info.type == 'C' )
|
if ( info.type == 'C' )
|
||||||
addr += 6;
|
addr += 6;
|
||||||
jsr_then_stop( addr );
|
jsr_then_stop( addr );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dprintf( "init/play hadn't returned before next play call\n" );
|
dprintf( "init/play hadn't returned before next play call\n" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
blargg_err_t Sap_Core::end_frame( time_t end )
|
blargg_err_t Sap_Core::end_frame( time_t end )
|
||||||
{
|
{
|
||||||
RETURN_ERR( run_until( end ) );
|
RETURN_ERR( run_until( end ) );
|
||||||
|
|
||||||
cpu.adjust_time( -end );
|
cpu.adjust_time( -end );
|
||||||
|
|
||||||
time_t frame_time = lines_per_frame * scanline_period;
|
time_t frame_time = lines_per_frame * scanline_period;
|
||||||
while ( frame_start < end )
|
while ( frame_start < end )
|
||||||
frame_start += frame_time;
|
frame_start += frame_time;
|
||||||
frame_start -= end + frame_time;
|
frame_start -= end + frame_time;
|
||||||
|
|
||||||
if ( (next_play -= end) < 0 )
|
if ( (next_play -= end) < 0 )
|
||||||
{
|
{
|
||||||
next_play = 0;
|
next_play = 0;
|
||||||
check( false );
|
check( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
apu_.end_frame( end );
|
apu_.end_frame( end );
|
||||||
if ( info.stereo )
|
if ( info.stereo )
|
||||||
apu2_.end_frame( end );
|
apu2_.end_frame( end );
|
||||||
|
|
||||||
return blargg_ok;
|
return blargg_ok;
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue