Replaced Game_Music_Emu with mpyne version, for the most part. Re-added the missing NSF chips, replaced the SPC player with the Higan one, re-added SFM, and disabled GYM and VGM.

This commit is contained in:
Christopher Snowhill 2022-01-03 17:50:07 -08:00
parent 888ee2fb38
commit fc38295d02
350 changed files with 29995 additions and 242970 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,412 +1,395 @@
// $package. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Ay_Apu.h" #include "Ay_Apu.h"
/* Copyright (C) 2006-2008 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"
// 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; unsigned 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 ) Ay_Apu::Ay_Apu()
{ {
for ( int i = 0; i < osc_count; ++i ) // build full table of the upper 8 envelope waveforms
set_output( i, b ); for ( int m = 8; m--; )
} {
byte* out = env.modes [m];
Ay_Apu::Ay_Apu() int flags = modes [m];
{ for ( int x = 3; --x >= 0; )
// build full table of the upper 8 envelope waveforms {
for ( int m = 8; m--; ) int amp = flags & 1;
{ int end = flags >> 1 & 1;
byte* out = env_modes [m]; int step = end - amp;
int flags = modes [m]; amp *= 15;
for ( int x = 3; --x >= 0; ) for ( int y = 16; --y >= 0; )
{ {
int amp = flags & 1; *out++ = amp_table [amp];
int end = flags >> 1 & 1; amp += step;
int step = end - amp; }
amp *= 15; flags >>= 2;
for ( int y = 16; --y >= 0; ) }
{ }
*out++ = amp_table [amp];
amp += step; output( 0 );
} volume( 1.0 );
flags >>= 2; reset();
} }
}
void Ay_Apu::reset()
type_ = Ay8910; {
set_output( NULL ); last_time = 0;
volume( 1.0 ); noise.delay = 0;
reset(); noise.lfsr = 1;
}
osc_t* osc = &oscs [osc_count];
void Ay_Apu::reset() do
{ {
addr_ = 0; osc--;
last_time = 0; osc->period = period_factor;
noise_delay = 0; osc->delay = 0;
noise_lfsr = 1; osc->last_amp = 0;
osc->phase = 0;
for ( osc_t* osc = &oscs [osc_count]; osc != oscs; ) }
{ while ( osc != oscs );
osc--;
osc->period = period_factor; for ( int i = sizeof regs; --i >= 0; )
osc->delay = 0; regs [i] = 0;
osc->last_amp = 0; regs [7] = 0xFF;
osc->phase = 0; write_data_( 13, 0 );
} }
for ( int i = sizeof regs; --i >= 0; ) void Ay_Apu::write_data_( int addr, int data )
regs [i] = 0; {
regs [7] = 0xFF; assert( (unsigned) addr < reg_count );
write_data_( 13, 0 );
} if ( (unsigned) addr >= 14 )
{
int Ay_Apu::read() #ifdef debug_printf
{ debug_printf( "Wrote to I/O port %02X\n", (int) addr );
static byte const masks [reg_count] = { #endif
0xFF, 0x0F, 0xFF, 0x0F, 0xFF, 0x0F, 0x1F, 0x3F, }
0x1F, 0x1F, 0x1F, 0xFF, 0xFF, 0x0F, 0x00, 0x00
}; // envelope mode
if (!(type_ & 0x10)) return regs [addr_] & masks [addr_]; if ( addr == 13 )
else return regs [addr_]; {
} if ( !(data & 8) ) // convert modes 0-7 to proper equivalents
data = (data & 4) ? 15 : 9;
void Ay_Apu::write_data_( int addr, int data ) env.wave = env.modes [data - 7];
{ env.pos = -48;
assert( (unsigned) addr < reg_count ); env.delay = 0; // will get set to envelope period in run_until()
}
if ( (unsigned) addr >= 14 ) regs [addr] = data;
dprintf( "Wrote to I/O port %02X\n", (int) addr );
// handle period changes accurately
// envelope mode int i = addr >> 1;
if ( addr == 13 ) if ( i < osc_count )
{ {
if ( !(data & 8) ) // convert modes 0-7 to proper equivalents blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100L * period_factor) +
data = (data & 4) ? 15 : 9; regs [i * 2] * period_factor;
env_wave = env_modes [data - 7]; if ( !period )
env_pos = -48; period = period_factor;
env_delay = 0; // will get set to envelope period in run_until()
} // adjust time of next timer expiration based on change in period
regs [addr] = data; osc_t& osc = oscs [i];
if ( (osc.delay += period - osc.period) < 0 )
// handle period changes accurately osc.delay = 0;
int i = addr >> 1; osc.period = period;
if ( i < osc_count ) }
{
blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) + // TODO: same as above for envelope timer, and it also has a divide by two after it
regs [i * 2] * period_factor; }
if ( !period )
period = period_factor; int const noise_off = 0x08;
int const tone_off = 0x01;
// adjust time of next timer expiration based on change in period
osc_t& osc = oscs [i]; void Ay_Apu::run_until( blip_time_t final_end_time )
if ( (osc.delay += period - osc.period) < 0 ) {
osc.delay = 0; require( final_end_time >= last_time );
osc.period = period;
} // noise period and initial values
blip_time_t const noise_period_factor = period_factor * 2; // verified
// TODO: same as above for envelope timer, and it also has a divide by two after it blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor;
} if ( !noise_period )
noise_period = noise_period_factor;
int const noise_off = 0x08; blip_time_t const old_noise_delay = noise.delay;
int const tone_off = 0x01; blargg_ulong const old_noise_lfsr = noise.lfsr;
void Ay_Apu::run_until( blip_time_t final_end_time ) // envelope period
{ blip_time_t const env_period_factor = period_factor * 2; // verified
require( final_end_time >= last_time ); blip_time_t env_period = (regs [12] * 0x100L + regs [11]) * env_period_factor;
if ( !env_period )
// noise period and initial values env_period = env_period_factor; // same as period 1 on my AY chip
blip_time_t const noise_period_factor = period_factor * 2; // verified if ( !env.delay )
blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor; env.delay = env_period;
if ( !noise_period )
noise_period = noise_period_factor; // run each osc separately
blip_time_t const old_noise_delay = noise_delay; for ( int index = 0; index < osc_count; index++ )
unsigned const old_noise_lfsr = noise_lfsr; {
osc_t* const osc = &oscs [index];
// envelope period int osc_mode = regs [7] >> index;
int env_step_scale = ((type_ & 0xF0) == 0x00) ? 1 : 0;
blip_time_t const env_period_factor = period_factor << env_step_scale; // verified // output
blip_time_t env_period = (regs [12] * 0x100 + regs [11]) * env_period_factor; Blip_Buffer* const osc_output = osc->output;
if ( !env_period ) if ( !osc_output )
env_period = env_period_factor; // same as period 1 on my AY chip continue;
if ( !env_delay ) osc_output->set_modified();
env_delay = env_period;
// period
// run each osc separately int half_vol = 0;
for ( int index = 0; index < osc_count; index++ ) blip_time_t inaudible_period = (blargg_ulong) (osc_output->clock_rate() +
{ inaudible_freq) / (inaudible_freq * 2);
osc_t* const osc = &oscs [index]; if ( osc->period <= inaudible_period && !(osc_mode & tone_off) )
int osc_mode = regs [7] >> index; {
half_vol = 1; // Actually around 60%, but 50% is close enough
// output osc_mode |= tone_off;
Blip_Buffer* const osc_output = osc->output; }
if ( !osc_output )
continue; // envelope
osc_output->set_modified(); blip_time_t start_time = last_time;
blip_time_t end_time = final_end_time;
// period int const vol_mode = regs [0x08 + index];
int half_vol = 0; int volume = amp_table [vol_mode & 0x0F] >> half_vol;
blip_time_t inaudible_period = (unsigned) (osc_output->clock_rate() + int osc_env_pos = env.pos;
inaudible_freq) / (unsigned) (inaudible_freq * 2); if ( vol_mode & 0x10 )
if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) {
{ volume = env.wave [osc_env_pos] >> half_vol;
half_vol = 1; // Actually around 60%, but 50% is close enough // use envelope only if it's a repeating wave or a ramp that hasn't finished
osc_mode |= tone_off; if ( !(regs [13] & 1) || osc_env_pos < -32 )
} {
end_time = start_time + env.delay;
// envelope if ( end_time >= final_end_time )
blip_time_t start_time = last_time; end_time = final_end_time;
blip_time_t end_time = final_end_time;
int const vol_mode = regs [0x08 + index]; //if ( !(regs [12] | regs [11]) )
int const vol_mode_mask = type_ == Ay8914 ? 0x30 : 0x10; // debug_printf( "Used envelope period 0\n" );
int volume = amp_table [vol_mode & 0x0F] >> (half_vol + env_step_scale); }
int osc_env_pos = env_pos; else if ( !volume )
if ( vol_mode & vol_mode_mask ) {
{ osc_mode = noise_off | tone_off;
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale); }
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 ); }
// use envelope only if it's a repeating wave or a ramp that hasn't finished else if ( !volume )
if ( !(regs [13] & 1) || osc_env_pos < -32 ) {
{ osc_mode = noise_off | tone_off;
end_time = start_time + env_delay; }
if ( end_time >= final_end_time )
end_time = final_end_time; // tone time
blip_time_t const period = osc->period;
//if ( !(regs [12] | regs [11]) ) blip_time_t time = start_time + osc->delay;
// dprintf( "Used envelope period 0\n" ); if ( osc_mode & tone_off ) // maintain tone's phase when off
} {
else if ( !volume ) blargg_long count = (final_end_time - time + period - 1) / period;
{ time += count * period;
osc_mode = noise_off | tone_off; osc->phase ^= count & 1;
} }
}
else if ( !volume ) // noise time
{ blip_time_t ntime = final_end_time;
osc_mode = noise_off | tone_off; blargg_ulong noise_lfsr = 1;
} if ( !(osc_mode & noise_off) )
{
// tone time ntime = start_time + old_noise_delay;
blip_time_t const period = osc->period; noise_lfsr = old_noise_lfsr;
blip_time_t time = start_time + osc->delay; //if ( (regs [6] & 0x1F) == 0 )
if ( osc_mode & tone_off ) // maintain tone's phase when off // debug_printf( "Used noise period 0\n" );
{ }
int count = (final_end_time - time + period - 1) / period;
time += count * period; // The following efficiently handles several cases (least demanding first):
osc->phase ^= count & 1; // * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC
} // * Just tone or just noise, envelope disabled
// * Envelope controlling tone and/or noise
// noise time // * Tone and noise disabled, envelope enabled with high frequency
blip_time_t ntime = final_end_time; // * Tone and noise together
unsigned noise_lfsr = 1; // * Tone and noise together with envelope
if ( !(osc_mode & noise_off) )
{ // This loop only runs one iteration if envelope is disabled. If envelope
ntime = start_time + old_noise_delay; // is being used as a waveform (tone and noise disabled), this loop will
noise_lfsr = old_noise_lfsr; // still be reasonably efficient since the bulk of it will be skipped.
//if ( (regs [6] & 0x1F) == 0 ) while ( 1 )
// dprintf( "Used noise period 0\n" ); {
} // current amplitude
int amp = 0;
// The following efficiently handles several cases (least demanding first): if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) )
// * Tone, noise, and envelope disabled, where channel acts as 4-bit DAC amp = volume;
// * Just tone or just noise, envelope disabled {
// * Envelope controlling tone and/or noise int delta = amp - osc->last_amp;
// * Tone and noise disabled, envelope enabled with high frequency if ( delta )
// * Tone and noise together {
// * Tone and noise together with envelope osc->last_amp = amp;
synth_.offset( start_time, delta, osc_output );
// This loop only runs one iteration if envelope is disabled. If envelope }
// is being used as a waveform (tone and noise disabled), this loop will }
// still be reasonably efficient since the bulk of it will be skipped.
while ( 1 ) // 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,
// current amplitude // so there will be no significant performance hit.
int amp = 0; if ( ntime < end_time || time < end_time )
if ( (osc_mode | osc->phase) & 1 & (osc_mode >> 3 | noise_lfsr) ) {
amp = volume; // Since amplitude was updated above, delta will always be +/- volume,
{ // so we can avoid using last_amp every time to calculate the delta.
int delta = amp - osc->last_amp; int delta = amp * 2 - volume;
if ( delta ) int delta_non_zero = delta != 0;
{ int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 );
osc->last_amp = amp; do
synth_.offset( start_time, delta, osc_output ); {
} // run noise
} blip_time_t end = end_time;
if ( end_time > time ) end = time;
// Run wave and noise interleved with each catching up to the other. if ( phase & delta_non_zero )
// If one or both are disabled, their "current time" will be past end time, {
// so there will be no significant performance hit. while ( ntime <= end ) // must advance *past* time to avoid hang
if ( ntime < end_time || time < end_time ) {
{ int changed = noise_lfsr + 1;
// Since amplitude was updated above, delta will always be +/- volume, noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1);
// so we can avoid using last_amp every time to calculate the delta. if ( changed & 2 )
int delta = amp * 2 - volume; {
int delta_non_zero = delta != 0; delta = -delta;
int phase = osc->phase | (osc_mode & tone_off); assert( tone_off == 0x01 ); synth_.offset( ntime, delta, osc_output );
do }
{ ntime += noise_period;
// run noise }
blip_time_t end = end_time; }
if ( end_time > time ) end = time; else
if ( phase & delta_non_zero ) {
{ // 20 or more noise periods on average for some music
while ( ntime <= end ) // must advance *past* time to avoid hang blargg_long remain = end - ntime;
{ blargg_long count = remain / noise_period;
int changed = noise_lfsr + 1; if ( remain >= 0 )
noise_lfsr = (-(noise_lfsr & 1) & 0x12000) ^ (noise_lfsr >> 1); ntime += noise_period + count * noise_period;
if ( changed & 2 ) }
{
delta = -delta; // run tone
synth_.offset( ntime, delta, osc_output ); end = end_time;
} if ( end_time > ntime ) end = ntime;
ntime += noise_period; if ( noise_lfsr & delta_non_zero )
} {
} while ( time < end )
else {
{ delta = -delta;
// 20 or more noise periods on average for some music synth_.offset( time, delta, osc_output );
int remain = end - ntime; time += period;
int count = remain / noise_period; //phase ^= 1;
if ( remain >= 0 ) }
ntime += noise_period + count * noise_period; //assert( phase == (delta > 0) );
} phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
// (delta > 0)
// run tone }
end = end_time; else
if ( end_time > ntime ) end = ntime; {
if ( noise_lfsr & delta_non_zero ) // loop usually runs less than once
{ //SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period );
while ( time < end )
{ while ( time < end )
delta = -delta; {
synth_.offset( time, delta, osc_output ); time += period;
time += period; phase ^= 1;
}
// alternate (less-efficient) implementation }
//phase ^= 1; }
} while ( time < end_time || ntime < end_time );
phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
check( phase == (delta > 0) ); osc->last_amp = (delta + volume) >> 1;
} if ( !(osc_mode & tone_off) )
else osc->phase = phase;
{ }
// loop usually runs less than once
//SUB_CASE_COUNTER( (time < end) * (end - time + period - 1) / period ); if ( end_time >= final_end_time )
break; // breaks first time when envelope is disabled
while ( time < end )
{ // next envelope step
time += period; if ( ++osc_env_pos >= 0 )
phase ^= 1; osc_env_pos -= 32;
} volume = env.wave [osc_env_pos] >> half_vol;
}
} start_time = end_time;
while ( time < end_time || ntime < end_time ); end_time += env_period;
if ( end_time > final_end_time )
osc->last_amp = (delta + volume) >> 1; end_time = final_end_time;
if ( !(osc_mode & tone_off) ) }
osc->phase = phase; osc->delay = time - final_end_time;
}
if ( !(osc_mode & noise_off) )
if ( end_time >= final_end_time ) {
break; // breaks first time when envelope is disabled noise.delay = ntime - final_end_time;
noise.lfsr = noise_lfsr;
// next envelope step }
if ( ++osc_env_pos >= 0 ) }
osc_env_pos -= 32;
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale); // TODO: optimized saw wave envelope?
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
// maintain envelope phase
start_time = end_time; blip_time_t remain = final_end_time - last_time - env.delay;
end_time += env_period; if ( remain >= 0 )
if ( end_time > final_end_time ) {
end_time = final_end_time; blargg_long count = (remain + env_period) / env_period;
} env.pos += count;
osc->delay = time - final_end_time; if ( env.pos >= 0 )
env.pos = (env.pos & 31) - 32;
if ( !(osc_mode & noise_off) ) remain -= count * env_period;
{ assert( -remain <= env_period );
noise_delay = ntime - final_end_time; }
this->noise_lfsr = noise_lfsr; env.delay = -remain;
} assert( env.delay > 0 );
} assert( env.pos < 0 );
// TODO: optimized saw wave envelope? last_time = final_end_time;
}
// maintain envelope phase
blip_time_t remain = final_end_time - last_time - env_delay;
if ( remain >= 0 )
{
int count = (remain + env_period) / env_period;
env_pos += count;
if ( env_pos >= 0 )
env_pos = (env_pos & 31) - 32;
remain -= count * env_period;
assert( -remain <= env_period );
}
env_delay = -remain;
assert( env_delay > 0 );
assert( env_pos < 0 );
last_time = final_end_time;
}

View file

@ -1,123 +1,106 @@
// AY-3-8910 sound chip emulator // AY-3-8910 sound chip emulator
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 // Set buffer to generate all sound into, or disable sound if NULL
enum Ay_Apu_Type void output( Blip_Buffer* );
{
Ay8910 = 0, // Reset sound chip
Ay8912, void reset();
Ay8913,
Ay8914, // Write to register at specified time
Ym2149 = 0x10, enum { reg_count = 16 };
Ym3439, void write( blip_time_t time, int addr, int data );
Ymz284,
Ymz294, // Run sound to specified time, end current time frame, then start a new
Ym2203 = 0x20, // time frame at time 0. Time frames have no effect on emulation and each
Ym2608, // can be whatever length is convenient.
Ym2610, void end_frame( blip_time_t length );
Ym2610b
}; // Additional features
void set_type( Ay_Apu_Type type ) { type_ = type; } // Set sound output of specific oscillator to buffer, where index is
// 0, 1, or 2. If buffer is NULL, the specified oscillator is muted.
// Sets buffer to generate sound into, or 0 to mute. enum { osc_count = 3 };
void set_output( Blip_Buffer* ); void osc_output( int index, Blip_Buffer* );
// Writes to address register // Set overall volume (default is 1.0)
void write_addr( int data ) { addr_ = data & 0x0F; } void volume( double );
// Emulates to time t, then writes to current data register // Set treble equalization (see documentation)
void write_data( blip_time_t t, int data ) { run_until( t ); write_data_( addr_, data ); } void treble_eq( blip_eq_t const& );
// Emulates to time t, then subtracts t from the current time. public:
// OK if previous write call had time slightly after t. Ay_Apu();
void end_frame( blip_time_t t ); typedef unsigned char byte;
private:
// More features struct osc_t
{
// Reads from current data register blip_time_t period;
int read(); blip_time_t delay;
short last_amp;
// Resets sound chip short phase;
void reset(); Blip_Buffer* output;
} oscs [osc_count];
// Number of registers blip_time_t last_time;
enum { reg_count = 16 }; byte regs [reg_count];
// Same as set_output(), but for a particular channel struct {
enum { osc_count = 3 }; blip_time_t delay;
void set_output( int chan, Blip_Buffer* ); blargg_ulong lfsr;
} noise;
// Sets overall volume, where 1.0 is normal
void volume( double v ) { synth_.volume( 0.7/osc_count/amp_range * v ); } struct {
blip_time_t delay;
// Sets treble equalization byte const* wave;
void treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); } int pos;
byte modes [8] [48]; // values already passed through volume table
private: } env;
// noncopyable
Ay_Apu( const Ay_Apu& ); void run_until( blip_time_t );
Ay_Apu& operator = ( const Ay_Apu& ); void write_data_( int addr, int data );
public:
// Implementation enum { amp_range = 255 };
public: Blip_Synth<blip_good_quality,1> synth_;
Ay_Apu(); };
BLARGG_DISABLE_NOTHROW
typedef BOOST::uint8_t byte; inline void Ay_Apu::volume( double v ) { synth_.volume( 0.7 / osc_count / amp_range * v ); }
private: inline void Ay_Apu::treble_eq( blip_eq_t const& eq ) { synth_.treble_eq( eq ); }
struct osc_t
{ inline void Ay_Apu::write( blip_time_t time, int addr, int data )
blip_time_t period; {
blip_time_t delay; run_until( time );
short last_amp; write_data_( addr, data );
short phase; }
Blip_Buffer* output;
} oscs [osc_count]; inline void Ay_Apu::osc_output( int i, Blip_Buffer* buf )
{
Ay_Apu_Type type_; assert( (unsigned) i < osc_count );
oscs [i].output = buf;
blip_time_t last_time; }
byte addr_;
byte regs [reg_count]; inline void Ay_Apu::output( Blip_Buffer* buf )
{
blip_time_t noise_delay; osc_output( 0, buf );
unsigned noise_lfsr; osc_output( 1, buf );
osc_output( 2, buf );
blip_time_t env_delay; }
byte const* env_wave;
int env_pos; inline void Ay_Apu::end_frame( blip_time_t time )
byte env_modes [8] [48]; // values already passed through volume table {
if ( time > last_time )
void write_data_( int addr, int data ); run_until( time );
void run_until( blip_time_t );
assert( last_time >= time );
public: last_time -= time;
enum { amp_range = 255 }; }
Blip_Synth_Norm synth_; // used by Ay_Core for beeper sound
}; #endif
inline void Ay_Apu::set_output( int i, Blip_Buffer* out )
{
assert( (unsigned) i < osc_count );
oscs [i].output = out;
}
inline void Ay_Apu::end_frame( blip_time_t time )
{
if ( time > last_time )
run_until( time );
last_time -= time;
assert( last_time >= 0 );
}
#endif

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,89 @@
// Z80 CPU emulator
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef AY_CPU_H
#define AY_CPU_H
#include "blargg_endian.h"
typedef blargg_long cpu_time_t;
// must be defined by caller
void ay_cpu_out( class Ay_Cpu*, cpu_time_t, unsigned addr, int data );
int ay_cpu_in( class Ay_Cpu*, unsigned addr );
class Ay_Cpu {
public:
// Clear all registers and keep pointer to 64K memory passed in
void reset( void* mem_64k );
// Run until specified time is reached. Returns true if suspicious/unsupported
// instruction was encountered at any point during run.
bool run( cpu_time_t end_time );
// Time of beginning of next instruction
cpu_time_t time() const { return state->time + state->base; }
// Alter current time. Not supported during run() call.
void set_time( cpu_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
#if BLARGG_BIG_ENDIAN
struct regs_t { uint8_t b, c, d, e, h, l, flags, a; };
#else
struct regs_t { uint8_t c, b, e, d, l, h, a, flags; };
#endif
static_assert( sizeof (regs_t) == 8, "Invalid register size, padding issue?" );
struct pairs_t { uint16_t bc, de, hl, fa; };
// Registers are not updated until run() returns
struct registers_t {
uint16_t pc;
uint16_t sp;
uint16_t ix;
uint16_t iy;
union {
regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a
pairs_t w; // w.bc, w.de, w.hl. w.fa
};
union {
regs_t b;
pairs_t w;
} alt;
uint8_t iff1;
uint8_t iff2;
uint8_t r;
uint8_t i;
uint8_t im;
};
//registers_t r; (below for efficiency)
// can read this far past end of memory
enum { cpu_padding = 0x100 };
public:
Ay_Cpu();
private:
uint8_t szpc [0x200];
uint8_t* mem;
cpu_time_t end_time_;
struct state_t {
cpu_time_t base;
cpu_time_t time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
void set_end_time( cpu_time_t t );
public:
registers_t r;
};
inline void Ay_Cpu::set_end_time( cpu_time_t t )
{
cpu_time_t delta = state->base - t;
state->base = t;
state->time += delta;
}
#endif

View file

@ -1,357 +1,410 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Ay_Emu.h" #include "Ay_Emu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser #include <algorithm> // min, max
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 /* Copyright (C) 2006 Shay Green. This module is free software; you
module is distributed in the hope that it will be useful, but WITHOUT ANY can redistribute it and/or modify it under the terms of the GNU Lesser
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS General Public License as published by the Free Software Foundation; either
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more version 2.1 of the License, or (at your option) any later version. This
details. You should have received a copy of the GNU Lesser General Public module is distributed in the hope that it will be useful, but WITHOUT ANY
License along with this module; if not, write to the Free Software Foundation, WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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
#include "blargg_source.h" License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
// TODO: probably don't need detailed errors as to why file is corrupt
#include "blargg_source.h"
int const spectrum_clock = 3546900; // 128K Spectrum
int const spectrum_period = 70908; long const spectrum_clock = 3546900;
long const cpc_clock = 2000000;
//int const spectrum_clock = 3500000; // 48K Spectrum
//int const spectrum_period = 69888; unsigned const ram_start = 0x4000;
int const osc_count = Ay_Apu::osc_count + 1;
int const cpc_clock = 2000000;
using std::min;
Ay_Emu::Ay_Emu() using std::max;
{
core.set_cpc_callback( enable_cpc_, this ); Ay_Emu::Ay_Emu()
set_type( gme_ay_type ); {
set_silence_lookahead( 6 ); beeper_output = 0;
} set_type( gme_ay_type );
Ay_Emu::~Ay_Emu() { } static const char* const names [osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Beeper"
// Track info };
set_voice_names( names );
// Given pointer to 2-byte offset of data, returns pointer to data, or NULL if
// offset is 0 or there is less than min_size bytes of data available. static int const types [osc_count] = {
static byte const* get_data( Ay_Emu::file_t const& file, byte const ptr [], int min_size ) wave_type | 0, wave_type | 1, wave_type | 2, mixed_type | 0
{ };
int offset = (BOOST::int16_t) get_be16( ptr ); set_voice_types( types );
int pos = ptr - (byte const*) file.header; set_silence_lookahead( 6 );
int size = file.end - (byte const*) file.header; }
assert( (unsigned) pos <= (unsigned) size - 2 );
int limit = size - min_size; Ay_Emu::~Ay_Emu() { }
if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit )
return NULL; // Track info
return ptr + offset;
} static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size )
{
static blargg_err_t parse_header( byte const in [], int size, Ay_Emu::file_t* out ) long pos = ptr - (byte const*) file.header;
{ long file_size = file.end - (byte const*) file.header;
typedef Ay_Emu::header_t header_t; assert( (unsigned long) pos <= (unsigned long) file_size - 2 );
if ( size < header_t::size ) int offset = (int16_t) get_be16( ptr );
return blargg_err_file_type; if ( !offset || blargg_ulong (pos + offset) > blargg_ulong (file_size - min_size) )
return 0;
out->header = (header_t const*) in; return ptr + offset;
out->end = in + size; }
header_t const& h = *(header_t const*) in;
if ( memcmp( h.tag, "ZXAYEMUL", 8 ) ) static blargg_err_t parse_header( byte const* in, long size, Ay_Emu::file_t* out )
return blargg_err_file_type; {
typedef Ay_Emu::header_t header_t;
out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 ); out->header = (header_t const*) in;
if ( !out->tracks ) out->end = in + size;
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "missing track data" );
if ( size < Ay_Emu::header_size )
return blargg_ok; return gme_wrong_file_type;
}
header_t const& h = *(header_t const*) in;
static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track ) if ( memcmp( h.tag, "ZXAYEMUL", 8 ) )
{ return gme_wrong_file_type;
Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) );
byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 ); out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 );
if ( track_info ) if ( !out->tracks )
out->length = get_be16( track_info + 4 ) * (1000 / 50); // frames to msec return "Missing track data";
Gme_File::copy_field_( out->author, (char const*) get_data( file, file.header->author, 1 ) ); return 0;
Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) ); }
}
static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track )
static void hash_ay_file( Ay_Emu::file_t const& file, Gme_Info_::Hash_Function& out ) {
{ Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) );
out.hash_( &file.header->vers, sizeof(file.header->vers) ); byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 );
out.hash_( &file.header->player, sizeof(file.header->player) ); if ( track_info )
out.hash_( &file.header->unused[0], sizeof(file.header->unused) ); out->length = get_be16( track_info + 4 ) * (1000L / 50); // frames to msec
out.hash_( &file.header->max_track, sizeof(file.header->max_track) );
out.hash_( &file.header->first_track, sizeof(file.header->first_track) ); Gme_File::copy_field_( out->author, (char const*) get_data( file, file.header->author, 1 ) );
Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) );
for ( unsigned i = 0; i <= file.header->max_track; i++ ) }
{
byte const* track_info = get_data( file, file.tracks + i * 4 + 2, 14 ); blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const
if ( track_info ) {
{ copy_ay_fields( file, out, track );
out.hash_( track_info + 8, 2 ); return 0;
byte const* points = get_data( file, track_info + 10, 6 ); }
if ( points ) out.hash_( points, 6 );
struct Ay_File : Gme_Info_
byte const* blocks = get_data( file, track_info + 12, 8 ); {
if ( blocks ) Ay_Emu::file_t file;
{
int addr = get_be16( blocks ); Ay_File() { set_type( gme_ay_type ); }
while ( addr ) blargg_err_t load_mem_( byte const* begin, long size )
{ {
out.hash_( blocks, 4 ); RETURN_ERR( parse_header( begin, size, &file ) );
set_track_count( file.header->max_track + 1 );
int len = get_be16( blocks + 2 ); return 0;
}
byte const* block = get_data( file, blocks + 4, len );
if ( block ) out.hash_( block, len ); blargg_err_t track_info_( track_info_t* out, int track ) const
{
blocks += 6; copy_ay_fields( file, out, track );
addr = get_be16( blocks ); return 0;
} }
} };
}
} static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; }
} static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; }
blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const static gme_type_t_ const gme_ay_type_ = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 };
{ extern gme_type_t const gme_ay_type = &gme_ay_type_;
copy_ay_fields( file, out, track );
return blargg_ok; // Setup
}
blargg_err_t Ay_Emu::load_mem_( byte const* in, long size )
struct Ay_File : Gme_Info_ {
{ assert( offsetof (header_t,track_info [2]) == header_size );
Ay_Emu::file_t file;
RETURN_ERR( parse_header( in, size, &file ) );
Ay_File() { set_type( gme_ay_type ); } set_track_count( file.header->max_track + 1 );
blargg_err_t load_mem_( byte const begin [], int size ) if ( file.header->vers > 2 )
{ set_warning( "Unknown file version" );
RETURN_ERR( parse_header( begin, size, &file ) );
set_track_count( file.header->max_track + 1 ); set_voice_count( osc_count );
return blargg_ok; apu.volume( gain() );
}
return setup_buffer( spectrum_clock );
blargg_err_t track_info_( track_info_t* out, int track ) const }
{
copy_ay_fields( file, out, track ); void Ay_Emu::update_eq( blip_eq_t const& eq )
return blargg_ok; {
} apu.treble_eq( eq );
}
blargg_err_t hash_( Hash_Function& out ) const
{ void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* )
hash_ay_file( file, out ); {
return blargg_ok; if ( i >= Ay_Apu::osc_count )
} beeper_output = center;
}; else
apu.osc_output( i, center );
static Music_Emu* new_ay_emu () }
{
return BLARGG_NEW Ay_Emu; // Emulation
}
void Ay_Emu::set_tempo_( double t )
static Music_Emu* new_ay_file() {
{ play_period = blip_time_t (clock_rate() / 50 / t);
return BLARGG_NEW Ay_File; }
}
blargg_err_t Ay_Emu::start_track_( int track )
gme_type_t_ const gme_ay_type [1] = {{ {
"ZX Spectrum", RETURN_ERR( Classic_Emu::start_track_( track ) );
0,
&new_ay_emu, memset( mem.ram + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
&new_ay_file, memset( mem.ram + 0x0100, 0xFF, 0x4000 - 0x100 );
"AY", memset( mem.ram + ram_start, 0x00, sizeof mem.ram - ram_start );
1 memset( mem.padding1, 0xFF, sizeof mem.padding1 );
}}; memset( mem.ram + 0x10000, 0xFF, sizeof mem.ram - 0x10000 );
// Setup // locate data blocks
byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
blargg_err_t Ay_Emu::load_mem_( byte const in [], int size ) if ( !data ) return "File data missing";
{
assert( offsetof (header_t,track_info [2]) == header_t::size ); byte const* const more_data = get_data( file, data + 10, 6 );
if ( !more_data ) return "File data missing";
RETURN_ERR( parse_header( in, size, &file ) );
set_track_count( file.header->max_track + 1 ); byte const* blocks = get_data( file, data + 12, 8 );
if ( !blocks ) return "File data missing";
if ( file.header->vers > 2 )
set_warning( "Unknown file version" ); // initial addresses
cpu::reset( mem.ram );
int const osc_count = Ay_Apu::osc_count + 1; // +1 for beeper r.sp = get_be16( more_data );
r.b.a = r.b.b = r.b.d = r.b.h = data [8];
set_voice_count( osc_count ); r.b.flags = r.b.c = r.b.e = r.b.l = data [9];
core.apu().volume( gain() ); r.alt.w = r.w;
r.ix = r.iy = r.w.hl;
static const char* const names [osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Beeper" unsigned addr = get_be16( blocks );
}; if ( !addr ) return "File data missing";
set_voice_names( names );
unsigned init = get_be16( more_data + 2 );
static int const types [osc_count] = { if ( !init )
wave_type+0, wave_type+1, wave_type+2, mixed_type+1 init = addr;
};
set_voice_types( types ); // copy blocks into memory
do
return setup_buffer( spectrum_clock ); {
} blocks += 2;
unsigned len = get_be16( blocks ); blocks += 2;
void Ay_Emu::update_eq( blip_eq_t const& eq ) if ( addr + len > 0x10000 )
{ {
core.apu().treble_eq( eq ); set_warning( "Bad data block size" );
} len = 0x10000 - addr;
}
void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* ) check( len );
{ byte const* in = get_data( file, blocks, 0 ); blocks += 2;
if ( i >= Ay_Apu::osc_count ) if ( len > blargg_ulong (file.end - in) )
core.set_beeper_output( center ); {
else set_warning( "Missing file data" );
core.apu().set_output( i, center ); len = file.end - in;
} }
//debug_printf( "addr: $%04X, len: $%04X\n", addr, len );
void Ay_Emu::set_tempo_( double t ) if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data
{ debug_printf( "Block addr in ROM\n" );
int p = spectrum_period; memcpy( mem.ram + addr, in, len );
if ( clock_rate() != spectrum_clock )
p = clock_rate() / 50; if ( file.end - blocks < 8 )
{
core.set_play_period( blip_time_t (p / t) ); set_warning( "Missing file data" );
} break;
}
blargg_err_t Ay_Emu::start_track_( int track ) }
{ while ( (addr = get_be16( blocks )) != 0 );
RETURN_ERR( Classic_Emu::start_track_( track ) );
// copy and configure driver
byte* const mem = core.mem(); static byte const passive [] = {
0xF3, // DI
memset( mem + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET 0xCD, 0, 0, // CALL init
memset( mem + 0x0100, 0xFF, 0x4000 - 0x100 ); 0xED, 0x5E, // LOOP: IM 2
memset( mem + core.ram_addr, 0x00, core.mem_size - core.ram_addr ); 0xFB, // EI
0x76, // HALT
// locate data blocks 0x18, 0xFA // JR LOOP
byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 ); };
if ( !data ) static byte const active [] = {
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" ); 0xF3, // DI
0xCD, 0, 0, // CALL init
byte const* const more_data = get_data( file, data + 10, 6 ); 0xED, 0x56, // LOOP: IM 1
if ( !more_data ) 0xFB, // EI
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" ); 0x76, // HALT
0xCD, 0, 0, // CALL play
byte const* blocks = get_data( file, data + 12, 8 ); 0x18, 0xF7 // JR LOOP
if ( !blocks ) };
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" ); memcpy( mem.ram, passive, sizeof passive );
unsigned play_addr = get_be16( more_data + 4 );
// initial addresses //debug_printf( "Play: $%04X\n", play_addr );
unsigned addr = get_be16( blocks ); if ( play_addr )
if ( !addr ) {
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" ); memcpy( mem.ram, active, sizeof active );
mem.ram [ 9] = play_addr;
unsigned init = get_be16( more_data + 2 ); mem.ram [10] = play_addr >> 8;
if ( !init ) }
init = addr; mem.ram [2] = init;
mem.ram [3] = init >> 8;
// copy blocks into memory
do mem.ram [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
{
blocks += 2; memcpy( mem.ram + 0x10000, mem.ram, 0x80 ); // some code wraps around (ugh)
unsigned len = get_be16( blocks ); blocks += 2;
if ( addr + len > core.mem_size ) beeper_delta = int (apu.amp_range * 0.65);
{ last_beeper = 0;
set_warning( "Bad data block size" ); apu.reset();
len = core.mem_size - addr; next_play = play_period;
}
check( len ); // start at spectrum speed
byte const* in = get_data( file, blocks, 0 ); blocks += 2; change_clock_rate( spectrum_clock );
if ( len > (unsigned) (file.end - in) ) set_tempo( tempo() );
{
set_warning( "File data missing" ); spectrum_mode = false;
len = file.end - in; cpc_mode = false;
} cpc_latch = 0;
//dprintf( "addr: $%04X, len: $%04X\n", addr, len );
if ( addr < core.ram_addr && addr >= 0x400 ) // several tracks use low data return 0;
dprintf( "Block addr in ROM\n" ); }
memcpy( mem + addr, in, len );
// Emulation
if ( file.end - blocks < 8 )
{ void Ay_Emu::cpu_out_misc( cpu_time_t time, unsigned addr, int data )
set_warning( "File data missing" ); {
break; if ( !cpc_mode )
} {
} switch ( addr & 0xFEFF )
while ( (addr = get_be16( blocks )) != 0 ); {
case 0xFEFD:
// copy and configure driver spectrum_mode = true;
static byte const passive [] = { apu_addr = data & 0x0F;
0xF3, // DI return;
0xCD, 0, 0, // CALL init
0xED, 0x5E, // LOOP: IM 2 case 0xBEFD:
0xFB, // EI spectrum_mode = true;
0x76, // HALT apu.write( time, apu_addr, data );
0x18, 0xFA // JR LOOP return;
}; }
static byte const active [] = { }
0xF3, // DI
0xCD, 0, 0, // CALL init if ( !spectrum_mode )
0xED, 0x56, // LOOP: IM 1 {
0xFB, // EI switch ( addr >> 8 )
0x76, // HALT {
0xCD, 0, 0, // CALL play case 0xF6:
0x18, 0xF7 // JR LOOP switch ( data & 0xC0 )
}; {
memcpy( mem, passive, sizeof passive ); case 0xC0:
int const play_addr = get_be16( more_data + 4 ); apu_addr = cpc_latch & 0x0F;
if ( play_addr ) goto enable_cpc;
{
memcpy( mem, active, sizeof active ); case 0x80:
mem [ 9] = play_addr; apu.write( time, apu_addr, cpc_latch );
mem [10] = play_addr >> 8; goto enable_cpc;
} }
mem [2] = init; break;
mem [3] = init >> 8;
case 0xF4:
mem [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET) cpc_latch = data;
goto enable_cpc;
// start at spectrum speed }
change_clock_rate( spectrum_clock ); }
set_tempo( tempo() );
debug_printf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
Ay_Core::registers_t r = { }; return;
r.sp = get_be16( more_data );
r.b.a = r.b.b = r.b.d = r.b.h = data [8]; enable_cpc:
r.b.flags = r.b.c = r.b.e = r.b.l = data [9]; if ( !cpc_mode )
r.alt.w = r.w; {
r.ix = r.iy = r.w.hl; cpc_mode = true;
change_clock_rate( cpc_clock );
core.start_track( r, play_addr ); set_tempo( tempo() );
}
return blargg_ok; }
}
void ay_cpu_out( Ay_Cpu* cpu, cpu_time_t time, unsigned addr, int data )
blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int ) {
{ Ay_Emu& emu = STATIC_CAST(Ay_Emu&,*cpu);
core.end_frame( &duration );
return blargg_ok; if ( (addr & 0xFF) == 0xFE && !emu.cpc_mode )
} {
int delta = emu.beeper_delta;
inline void Ay_Emu::enable_cpc() data &= 0x10;
{ if ( emu.last_beeper != data )
change_clock_rate( cpc_clock ); {
set_tempo( tempo() ); emu.last_beeper = data;
} emu.beeper_delta = -delta;
emu.spectrum_mode = true;
void Ay_Emu::enable_cpc_( void* data ) if ( emu.beeper_output )
{ emu.apu.synth_.offset( time, delta, emu.beeper_output );
STATIC_CAST(Ay_Emu*,data)->enable_cpc(); }
} }
else
blargg_err_t Ay_Emu::hash_( Hash_Function& out ) const {
{ emu.cpu_out_misc( time, addr, data );
hash_ay_file( file, out ); }
return blargg_ok; }
}
int ay_cpu_in( Ay_Cpu*, unsigned addr )
{
// keyboard read and other things
if ( (addr & 0xFF) == 0xFE )
return 0xFF; // other values break some beeper tunes
debug_printf( "Unmapped IN : $%04X\n", addr );
return 0xFF;
}
blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
{
set_time( 0 );
if ( !(spectrum_mode | cpc_mode) )
duration /= 2; // until mode is set, leave room for halved clock rate
while ( time() < duration )
{
cpu::run( min( duration, (blip_time_t) next_play ) );
if ( time() >= next_play )
{
next_play += play_period;
if ( r.iff1 )
{
if ( mem.ram [r.pc] == 0x76 )
r.pc++;
r.iff1 = r.iff2 = 0;
mem.ram [--r.sp] = uint8_t (r.pc >> 8);
mem.ram [--r.sp] = uint8_t (r.pc);
r.pc = 0x38;
cpu::adjust_time( 12 );
if ( r.im == 2 )
{
cpu::adjust_time( 6 );
unsigned addr = r.i * 0x100u + 0xFF;
r.pc = mem.ram [(addr + 1) & 0xFFFF] * 0x100u + mem.ram [addr];
}
}
}
}
duration = time();
next_play -= duration;
check( next_play >= 0 );
adjust_time( -duration );
apu.end_frame( duration );
return 0;
}

View file

@ -1,60 +1,69 @@
// Sinclair Spectrum AY music file emulator // Sinclair Spectrum AY music file emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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_Apu.h"
#include "Ay_Cpu.h"
class Ay_Emu : public Classic_Emu {
public: class Ay_Emu : private Ay_Cpu, public Classic_Emu {
// AY file header typedef Ay_Cpu cpu;
struct header_t public:
{ // AY file header
enum { size = 0x14 }; enum { header_size = 0x14 };
struct header_t
byte tag [8]; {
byte vers; byte tag [8];
byte player; byte vers;
byte unused [2]; byte player;
byte author [2]; byte unused [2];
byte comment [2]; byte author [2];
byte max_track; byte comment [2];
byte first_track; byte max_track;
byte track_info [2]; byte first_track;
}; 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 public:
public: Ay_Emu();
Ay_Emu(); ~Ay_Emu();
~Ay_Emu(); struct file_t {
header_t const* header;
struct file_t { byte const* end;
header_t const* header; byte const* tracks;
byte const* tracks; };
byte const* end; // end of file data protected:
}; blargg_err_t track_info_( track_info_t*, int track ) const;
blargg_err_t load_mem_( byte const*, long );
blargg_err_t hash_( Hash_Function& out ) const; blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
protected: void set_tempo_( double );
virtual blargg_err_t track_info_( track_info_t*, int track ) const; void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual blargg_err_t load_mem_( byte const [], int ); void update_eq( blip_eq_t const& );
virtual blargg_err_t start_track_( int ); private:
virtual blargg_err_t run_clocks( blip_time_t&, int ); file_t file;
virtual void set_tempo_( double );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); cpu_time_t play_period;
virtual void update_eq( blip_eq_t const& ); cpu_time_t next_play;
Blip_Buffer* beeper_output;
private: int beeper_delta;
file_t file; int last_beeper;
Ay_Core core; int apu_addr;
int cpc_latch;
void enable_cpc(); bool spectrum_mode;
static void enable_cpc_( void* data ); bool cpc_mode;
};
// large items
#endif struct {
byte padding1 [0x100];
byte ram [0x10000 + 0x100];
} mem;
Ay_Apu apu;
friend void ay_cpu_out( Ay_Cpu*, cpu_time_t, unsigned addr, int data );
void cpu_out_misc( cpu_time_t, unsigned addr, int data );
};
#endif

View file

@ -1,509 +1,460 @@
// Blip_Buffer $vers. http://www.slack.net/~ant/ // Blip_Buffer 0.4.1. http://www.slack.net/~ant/
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
#include <math.h> #include <assert.h>
#include <limits.h>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include <string.h>
can redistribute it and/or modify it under the terms of the GNU Lesser #include <stdlib.h>
General Public License as published by the Free Software Foundation; either #include <math.h>
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 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS can redistribute it and/or modify it under the terms of the GNU Lesser
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more General Public License as published by the Free Software Foundation; either
details. You should have received a copy of the GNU Lesser General Public version 2.1 of the License, or (at your option) any later version. This
License along with this module; if not, write to the Free Software Foundation, module is distributed in the hope that it will be useful, but WITHOUT ANY
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
#include "blargg_source.h" 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,
//// Blip_Buffer Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
Blip_Buffer::Blip_Buffer() #ifdef BLARGG_ENABLE_OPTIMIZER
{ #include BLARGG_ENABLE_OPTIMIZER
factor_ = UINT_MAX/2 + 1; #endif
buffer_ = NULL;
buffer_center_ = NULL; int const silent_buf_size = 1; // size used for Silent_Blip_Buffer
buffer_size_ = 0;
sample_rate_ = 0; Blip_Buffer::Blip_Buffer()
bass_shift_ = 0; {
clock_rate_ = 0; factor_ = (blip_ulong)-1 / 2;
bass_freq_ = 16; offset_ = 0;
length_ = 0; buffer_ = 0;
buffer_size_ = 0;
// assumptions code makes about implementation-defined features sample_rate_ = 0;
#ifndef NDEBUG reader_accum_ = 0;
// right shift of negative value preserves sign bass_shift_ = 0;
int i = -0x7FFFFFFE; clock_rate_ = 0;
assert( (i >> 1) == -0x3FFFFFFF ); bass_freq_ = 16;
length_ = 0;
// casting truncates and sign-extends
i = 0x18000; // assumptions code makes about implementation-defined features
assert( (BOOST::int16_t) i == -0x8000 ); #ifndef NDEBUG
#endif // right shift of negative value preserves sign
buf_t_ i = -0x7FFFFFFE;
clear(); assert( (i >> 1) == -0x3FFFFFFF );
}
// casting to short truncates to 16 bits and sign-extends
Blip_Buffer::~Blip_Buffer() i = 0x18000;
{ assert( (short) i == -0x8000 );
free( buffer_ ); #endif
} }
void Blip_Buffer::clear() Blip_Buffer::~Blip_Buffer()
{ {
bool const entire_buffer = true; if ( buffer_size_ != silent_buf_size )
free( buffer_ );
offset_ = 0; }
reader_accum_ = 0;
modified_ = false; Silent_Blip_Buffer::Silent_Blip_Buffer()
{
if ( buffer_ ) factor_ = 0;
{ buffer_ = buf;
int count = (entire_buffer ? buffer_size_ : samples_avail()); buffer_size_ = silent_buf_size;
memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (delta_t) ); memset( buf, 0, sizeof buf ); // in case machine takes exception for signed overflow
} }
}
void Blip_Buffer::clear( int entire_buffer )
blargg_err_t Blip_Buffer::set_sample_rate( int new_rate, int msec ) {
{ offset_ = 0;
// Limit to maximum size that resampled time can represent reader_accum_ = 0;
int max_size = (((blip_resampled_time_t) -1) >> BLIP_BUFFER_ACCURACY) - modified_ = 0;
blip_buffer_extra_ - 64; // TODO: -64 isn't needed if ( buffer_ )
int new_size = (new_rate * (msec + 1) + 999) / 1000; {
if ( new_size > max_size ) long count = (entire_buffer ? buffer_size_ : samples_avail());
new_size = max_size; memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (buf_t_) );
}
// Resize buffer }
if ( buffer_size_ != new_size )
{ Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec )
//dprintf( "%d \n", (new_size + blip_buffer_extra_) * sizeof *buffer_ ); {
void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ ); if ( buffer_size_ == silent_buf_size )
CHECK_ALLOC( p ); {
buffer_ = (delta_t*) p; assert( 0 );
buffer_center_ = buffer_ + BLIP_MAX_QUALITY/2; return "Internal (tried to resize Silent_Blip_Buffer)";
buffer_size_ = new_size; }
}
// start with maximum length that resampled time can represent
// Update sample_rate and things that depend on it long new_size = (UINT_MAX >> BLIP_BUFFER_ACCURACY) - blip_buffer_extra_ - 64;
sample_rate_ = new_rate; if ( msec != blip_max_length )
length_ = new_size * 1000 / new_rate - 1; {
if ( clock_rate_ ) long s = (new_rate * (msec + 1) + 999) / 1000;
clock_rate( clock_rate_ ); if ( s < new_size )
bass_freq( bass_freq_ ); new_size = s;
else
clear(); assert( 0 ); // fails if requested buffer length exceeds limit
}
return blargg_ok;
} if ( buffer_size_ != new_size )
{
blip_resampled_time_t Blip_Buffer::clock_rate_factor( int rate ) const void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ );
{ if ( !p )
double ratio = (double) sample_rate_ / rate; return "Out of memory";
int factor = (int) floor( ratio * (1 << BLIP_BUFFER_ACCURACY) + 0.5 ); buffer_ = (buf_t_*) p;
assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large }
return (blip_resampled_time_t) factor;
} buffer_size_ = new_size;
assert( buffer_size_ != silent_buf_size );
void Blip_Buffer::bass_freq( int freq )
{ // update things based on the sample rate
bass_freq_ = freq; sample_rate_ = new_rate;
int shift = 31; length_ = new_size * 1000 / new_rate - 1;
if ( freq > 0 && sample_rate_ ) if ( msec )
{ assert( length_ == msec ); // ensure length is same as that passed in
shift = 13; if ( clock_rate_ )
int f = (freq << 16) / sample_rate_; clock_rate( clock_rate_ );
while ( (f >>= 1) != 0 && --shift ) { } bass_freq( bass_freq_ );
}
bass_shift_ = shift; clear();
}
return 0; // success
void Blip_Buffer::end_frame( blip_time_t t ) }
{
offset_ += t * factor_; blip_resampled_time_t Blip_Buffer::clock_rate_factor( long rate ) const
assert( samples_avail() <= (int) buffer_size_ ); // fails if time is past end of buffer {
} double ratio = (double) sample_rate_ / rate;
blip_long factor = (blip_long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 );
int Blip_Buffer::count_samples( blip_time_t t ) const assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large
{ return (blip_resampled_time_t) factor;
blip_resampled_time_t last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; }
blip_resampled_time_t first_sample = offset_ >> BLIP_BUFFER_ACCURACY;
return (int) (last_sample - first_sample); void Blip_Buffer::bass_freq( int freq )
} {
bass_freq_ = freq;
blip_time_t Blip_Buffer::count_clocks( int count ) const int shift = 31;
{ if ( freq > 0 )
if ( count > buffer_size_ ) {
count = buffer_size_; shift = 13;
blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; long f = (freq << 16) / sample_rate_;
return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); while ( (f >>= 1) && --shift ) { }
} }
bass_shift_ = shift;
void Blip_Buffer::remove_samples( int count ) }
{
if ( count ) void Blip_Buffer::end_frame( blip_time_t t )
{ {
remove_silence( count ); offset_ += t * factor_;
assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length
// copy remaining samples to beginning and clear old samples }
int remain = samples_avail() + blip_buffer_extra_;
memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); void Blip_Buffer::remove_silence( long count )
memset( buffer_ + remain, 0, count * sizeof *buffer_ ); {
} assert( count <= samples_avail() ); // tried to remove more samples than available
} offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
}
int Blip_Buffer::read_samples( blip_sample_t out_ [], int max_samples, bool stereo )
{ long Blip_Buffer::count_samples( blip_time_t t ) const
int count = samples_avail(); {
if ( count > max_samples ) unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY;
count = max_samples; unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY;
return (long) (last_sample - first_sample);
if ( count ) }
{
int const bass = highpass_shift(); blip_time_t Blip_Buffer::count_clocks( long count ) const
delta_t const* reader = read_pos() + count; {
int reader_sum = integrator(); if ( !factor_ )
{
blip_sample_t* BLARGG_RESTRICT out = out_ + count; assert( 0 ); // sample rate and clock rates must be set first
if ( stereo ) return 0;
out += count; }
int offset = -count;
if ( count > buffer_size_ )
if ( !stereo ) count = buffer_size_;
{ blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
do return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_);
{ }
int s = reader_sum >> delta_bits;
void Blip_Buffer::remove_samples( long count )
reader_sum -= reader_sum >> bass; {
reader_sum += reader [offset]; if ( count )
{
BLIP_CLAMP( s, s ); remove_silence( count );
out [offset] = (blip_sample_t) s;
} // copy remaining samples to beginning and clear old samples
while ( ++offset ); long remain = samples_avail() + blip_buffer_extra_;
} memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ );
else memset( buffer_ + remain, 0, count * sizeof *buffer_ );
{ }
do }
{
int s = reader_sum >> delta_bits; // Blip_Synth_
reader_sum -= reader_sum >> bass; Blip_Synth_Fast_::Blip_Synth_Fast_()
reader_sum += reader [offset]; {
buf = 0;
BLIP_CLAMP( s, s ); last_amp = 0;
out [offset * 2] = (blip_sample_t) s; delta_factor = 0;
} }
while ( ++offset );
} void Blip_Synth_Fast_::volume_unit( double new_unit )
{
set_integrator( reader_sum ); delta_factor = int (new_unit * (1L << blip_sample_bits) + 0.5);
}
remove_samples( count );
} #if !BLIP_BUFFER_FAST
return count;
} Blip_Synth_::Blip_Synth_( short* p, int w ) :
impulses( p ),
void Blip_Buffer::mix_samples( blip_sample_t const in [], int count ) width( w )
{ {
delta_t* out = buffer_center_ + (offset_ >> BLIP_BUFFER_ACCURACY); volume_unit_ = 0.0;
kernel_unit = 0;
int const sample_shift = blip_sample_bits - 16; buf = 0;
int prev = 0; last_amp = 0;
while ( --count >= 0 ) delta_factor = 0;
{ }
int s = *in++ << sample_shift;
*out += s - prev; #undef PI
prev = s; #define PI 3.1415926535897932384626433832795029
++out;
} static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff )
*out -= prev; {
} if ( cutoff >= 0.999 )
cutoff = 0.999;
void Blip_Buffer::save_state( blip_buffer_state_t* out )
{ if ( treble < -300.0 )
assert( samples_avail() == 0 ); treble = -300.0;
out->offset_ = offset_; if ( treble > 5.0 )
out->reader_accum_ = reader_accum_; treble = 5.0;
memcpy( out->buf, &buffer_ [offset_ >> BLIP_BUFFER_ACCURACY], sizeof out->buf );
} double const maxh = 4096.0;
double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) );
void Blip_Buffer::load_state( blip_buffer_state_t const& in ) double const pow_a_n = pow( rolloff, maxh - maxh * cutoff );
{ double const to_angle = PI / 2 / maxh / oversample;
clear(); for ( int i = 0; i < count; i++ )
{
offset_ = in.offset_; double angle = ((i - count) * 2 + 1) * to_angle;
reader_accum_ = in.reader_accum_; double angle_maxh = angle * maxh;
memcpy( buffer_, in.buf, sizeof in.buf ); double angle_maxh_mid = angle_maxh * cutoff;
}
double y = maxh;
//// Blip_Synth_ // 0 to Fs/2*cutoff, flat
if ( angle_maxh_mid ) // unstable at t=0
Blip_Synth_Fast_::Blip_Synth_Fast_() y *= sin( angle_maxh_mid ) / angle_maxh_mid;
{
buf = NULL; // Fs/2*cutoff to Fs/2, logarithmic rolloff
last_amp = 0; double cosa = cos( angle );
delta_factor = 0; double den = 1 + rolloff * (rolloff - cosa - cosa);
}
// Becomes unstable when rolloff is near 1.0 and t is near 0,
void Blip_Synth_Fast_::volume_unit( double new_unit ) // which is the only time den becomes small
{ if ( den > 1e-13 )
delta_factor = int (new_unit * (1 << blip_sample_bits) + 0.5); {
} double num =
(cos( angle_maxh - angle ) * rolloff - cos( angle_maxh )) * pow_a_n -
#if BLIP_BUFFER_FAST cos( angle_maxh_mid - angle ) * rolloff + cos( angle_maxh_mid );
void blip_eq_t::generate( float* out, int count ) const { } y = y * cutoff + num / den;
}
#else
out [i] = (float) y;
Blip_Synth_::Blip_Synth_( short p [], int w ) : }
phases( p ), }
width( w )
{ void blip_eq_t::generate( float* out, int count ) const
volume_unit_ = 0.0; {
kernel_unit = 0; // lower cutoff freq for narrow kernels with their wider transition band
buf = NULL; // (8 points->1.49, 16 points->1.15)
last_amp = 0; double oversample = blip_res * 2.25 / count + 0.85;
delta_factor = 0; double half_rate = sample_rate * 0.5;
} if ( cutoff_freq )
oversample = half_rate / cutoff_freq;
#undef PI double cutoff = rolloff_freq * oversample / half_rate;
#define PI 3.1415926535897932384626433832795029
gen_sinc( out, count, blip_res * oversample, treble, cutoff );
// Generates right half of sinc kernel (including center point) with cutoff at
// sample rate / 2 / oversample. Frequency response at cutoff frequency is // apply (half of) hamming window
// treble dB (-6=0.5,-12=0.25). Mid controls frequency that rolloff begins at, double to_fraction = PI / (count - 1);
// cut * sample rate / 2. for ( int i = count; i--; )
static void gen_sinc( float out [], int out_size, double oversample, out [i] *= 0.54f - 0.46f * (float) cos( i * to_fraction );
double treble, double mid ) }
{
if ( mid > 0.9999 ) mid = 0.9999; void Blip_Synth_::adjust_impulse()
if ( treble < -300.0 ) treble = -300.0; {
if ( treble > 5.0 ) treble = 5.0; // sum pairs for each phase and add error correction to end of first half
int const size = impulses_size();
double const maxh = 4096.0; for ( int p = blip_res; p-- >= blip_res / 2; )
double rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - mid) ); {
double const pow_a_n = pow( rolloff, maxh - maxh * mid ); int p2 = blip_res - 2 - p;
double const to_angle = PI / maxh / oversample; long error = kernel_unit;
for ( int i = 1; i < out_size; i++ ) for ( int i = 1; i < size; i += blip_res )
{ {
double angle = i * to_angle; error -= impulses [i + p ];
double c = rolloff * cos( angle * maxh - angle ) - error -= impulses [i + p2];
cos( angle * maxh ); }
double cos_nc_angle = cos( angle * maxh * mid ); if ( p == p2 )
double cos_nc1_angle = cos( angle * maxh * mid - angle ); error /= 2; // phase = 0.5 impulse uses same half for both sides
double cos_angle = cos( angle ); impulses [size - blip_res + p] += (short) error;
//printf( "error: %ld\n", error );
c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; }
double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle);
double b = 2.0 - cos_angle - cos_angle; //for ( int i = blip_res; i--; printf( "\n" ) )
double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; // for ( int j = 0; j < width / 2; j++ )
// printf( "%5ld,", impulses [j * blip_res + i + 1] );
out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d }
}
void Blip_Synth_::treble_eq( blip_eq_t const& eq )
// Approximate center by looking at two points to right. Much simpler {
// and more reliable than trying to calculate it properly. float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2];
out [0] = out [1] + 0.5 * (out [1] - out [2]);
} int const half_size = blip_res / 2 * (width - 1);
eq.generate( &fimpulse [blip_res], half_size );
// Gain is 1-2800 for beta of 0-10, instead of 1.0 as it should be, but
// this is corrected by normalization in treble_eq(). int i;
static void kaiser_window( float io [], int count, float beta )
{ // need mirror slightly past center for calculation
int const accuracy = 10; for ( i = blip_res; i--; )
fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i];
float const beta2 = beta * beta;
float const step = (float) 0.5 / count; // starts at 0
float pos = (float) 0.5; for ( i = 0; i < blip_res; i++ )
for ( float* const end = io + count; io < end; ++io ) fimpulse [i] = 0.0f;
{
float x = (pos - pos*pos) * beta2; // find rescale factor
float u = x; double total = 0.0;
float k = 1; for ( i = 0; i < half_size; i++ )
float n = 2; total += fimpulse [blip_res + i];
// Keep refining until adjustment becomes small //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB
do //double const base_unit = 37888.0; // allows treble to +5 dB
{ double const base_unit = 32768.0; // necessary for blip_unscaled to work
u *= x / (n * n); double rescale = base_unit / 2 / total;
n += 1; kernel_unit = (long) base_unit;
k += u;
} // integrate, first difference, rescale, convert to int
while ( k <= u * (1 << accuracy) ); double sum = 0.0;
double next = 0.0;
pos += step; int const impulses_size = this->impulses_size();
*io *= k; for ( i = 0; i < impulses_size; i++ )
} {
} impulses [i] = (short) floor( (next - sum) * rescale + 0.5 );
sum += fimpulse [i];
void blip_eq_t::generate( float out [], int count ) const next += fimpulse [i + blip_res];
{ }
// lower cutoff freq for narrow kernels with their wider transition band adjust_impulse();
// (8 points->1.49, 16 points->1.15)
double cutoff_adj = blip_res * 2.25 / count + 0.85; // volume might require rescaling
if ( cutoff_adj < 1.02 ) double vol = volume_unit_;
cutoff_adj = 1.02; if ( vol )
double half_rate = sample_rate * 0.5; {
if ( cutoff_freq ) volume_unit_ = 0.0;
cutoff_adj = half_rate / cutoff_freq; volume_unit( vol );
double cutoff = rolloff_freq * cutoff_adj / half_rate; }
}
gen_sinc( out, count, oversample * cutoff_adj, treble, cutoff );
void Blip_Synth_::volume_unit( double new_unit )
kaiser_window( out, count, kaiser ); {
} if ( new_unit != volume_unit_ )
{
void Blip_Synth_::treble_eq( blip_eq_t const& eq ) // use default eq if it hasn't been set yet
{ if ( !kernel_unit )
// Generate right half of kernel treble_eq( -8.0 );
int const half_size = blip_eq_t::calc_count( width );
float fimpulse [blip_res / 2 * (BLIP_MAX_QUALITY - 1) + 1]; volume_unit_ = new_unit;
eq.generate( fimpulse, half_size ); double factor = new_unit * (1L << blip_sample_bits) / kernel_unit;
int i; if ( factor > 0.0 )
{
// Find rescale factor. Summing from small to large (right to left) int shift = 0;
// reduces error.
double total = 0.0; // if unit is really small, might need to attenuate kernel
for ( i = half_size; --i > 0; ) while ( factor < 2.0 )
total += fimpulse [i]; {
total = total * 2.0 + fimpulse [0]; shift++;
factor *= 2.0;
//double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB }
//double const base_unit = 37888.0; // allows treble to +5 dB
double const base_unit = 32768.0; // necessary for blip_unscaled to work if ( shift )
double rescale = base_unit / total; {
kernel_unit = (int) base_unit; kernel_unit >>= shift;
assert( kernel_unit > 0 ); // fails if volume unit is too low
// Integrate, first difference, rescale, convert to int
double sum = 0; // keep values positive to avoid round-towards-zero of sign-preserving
double next = 0; // right shift for negative values
int const size = impulses_size(); long offset = 0x8000 + (1 << (shift - 1));
for ( i = 0; i < size; i++ ) long offset2 = 0x8000 >> shift;
{ for ( int i = impulses_size(); i--; )
int j = (half_size - 1) - i; impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2);
adjust_impulse();
if ( i >= blip_res ) }
sum += fimpulse [j + blip_res]; }
delta_factor = (int) floor( factor + 0.5 );
// goes slightly past center, so it needs a little mirroring //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit );
next += fimpulse [j < 0 ? -j : j]; }
}
// calculate unintereleved index #endif
int x = (~i & (blip_res - 1)) * (width >> 1) + (i >> BLIP_PHASE_BITS);
assert( (unsigned) x < (unsigned) size ); long Blip_Buffer::read_samples( blip_sample_t* BLIP_RESTRICT out, long max_samples, int stereo )
{
// flooring separately virtually eliminates error long count = samples_avail();
phases [x] = (short) (int) if ( count > max_samples )
(floor( sum * rescale + 0.5 ) - floor( next * rescale + 0.5 )); count = max_samples;
//phases [x] = (short) (int)
// floor( sum * rescale - next * rescale + 0.5 ); if ( count )
} {
int const bass = BLIP_READER_BASS( *this );
adjust_impulse(); BLIP_READER_BEGIN( reader, *this );
// volume might require rescaling if ( !stereo )
double vol = volume_unit_; {
if ( vol ) for ( blip_long n = count; n; --n )
{ {
volume_unit_ = 0.0; blip_long s = BLIP_READER_READ( reader );
volume_unit( vol ); if ( (blip_sample_t) s != s )
} s = 0x7FFF - (s >> 24);
} *out++ = (blip_sample_t) s;
BLIP_READER_NEXT( reader, bass );
void Blip_Synth_::adjust_impulse() }
{ }
int const size = impulses_size(); else
int const half_width = width / 2; {
for ( blip_long n = count; n; --n )
// Sum each phase as would be done when synthesizing, and correct {
// any that don't add up to exactly kernel_half. blip_long s = BLIP_READER_READ( reader );
for ( int phase = blip_res / 2; --phase >= 0; ) if ( (blip_sample_t) s != s )
{ s = 0x7FFF - (s >> 24);
int const fwd = phase * half_width; *out = (blip_sample_t) s;
int const rev = size - half_width - fwd; out += 2;
BLIP_READER_NEXT( reader, bass );
int error = kernel_unit; }
for ( int i = half_width; --i >= 0; ) }
{ BLIP_READER_END( reader, *this );
error += phases [fwd + i];
error += phases [rev + i]; remove_samples( count );
} }
phases [fwd + half_width - 1] -= (short) error; return count;
}
// Error shouldn't occur now with improved calculation
//if ( error ) printf( "error: %ld\n", error ); void Blip_Buffer::mix_samples( blip_sample_t const* in, long count )
} {
if ( buffer_size_ == silent_buf_size )
#if 0 {
for ( int i = 0; i < blip_res; i++, printf( "\n" ) ) assert( 0 );
for ( int j = 0; j < width / 2; j++ ) return;
printf( "%5d,", (int) -phases [j + width/2 * i] ); }
#endif
} buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2;
void Blip_Synth_::rescale_kernel( int shift ) int const sample_shift = blip_sample_bits - 16;
{ int prev = 0;
// Keep values positive to avoid round-towards-zero of sign-preserving while ( count-- )
// right shift for negative values. {
int const keep_positive = 0x8000 + (1 << (shift - 1)); blip_long s = (blip_long) *in++ << sample_shift;
*out += s - prev;
int const half_width = width / 2; prev = s;
for ( int phase = blip_res; --phase >= 0; ) ++out;
{ }
int const fwd = phase * half_width; *out -= prev;
}
// Integrate, rescale, then differentiate again.
// If differences are rescaled directly, more error results.
int sum = keep_positive;
for ( int i = 0; i < half_width; i++ )
{
int prev = sum;
sum += phases [fwd + i];
phases [fwd + i] = (sum >> shift) - (prev >> shift);
}
}
adjust_impulse();
}
void Blip_Synth_::volume_unit( double new_unit )
{
if ( volume_unit_ != new_unit )
{
// use default eq if it hasn't been set yet
if ( !kernel_unit )
treble_eq( -8.0 );
// Factor that kernel must be multiplied by
volume_unit_ = new_unit;
double factor = new_unit * (1 << blip_sample_bits) / kernel_unit;
if ( factor > 0.0 )
{
// If factor is low, reduce amplitude of kernel itself
int shift = 0;
while ( factor < 2.0 )
{
shift++;
factor *= 2.0;
}
if ( shift )
{
kernel_unit >>= shift;
assert( kernel_unit > 0 ); // fails if volume unit is too low
rescale_kernel( shift );
}
}
delta_factor = -(int) floor( factor + 0.5 );
//printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit );
}
}
#endif

View file

@ -1,198 +1,493 @@
// Band-limited sound synthesis buffer // Band-limited sound synthesis buffer
// Blip_Buffer $vers // Blip_Buffer 0.4.1
#ifndef BLIP_BUFFER_H #ifndef BLIP_BUFFER_H
#define BLIP_BUFFER_H #define BLIP_BUFFER_H
#include "blargg_common.h" // internal
#include "Blip_Buffer_impl.h" #include <limits.h>
#if INT_MAX < 0x7FFFFFFF
typedef int blip_time_t; // Source clocks in current time frame #error "int must be at least 32 bits"
typedef BOOST::int16_t blip_sample_t; // 16-bit signed output sample #endif
int const blip_default_length = 1000 / 4; // Default Blip_Buffer length (1/4 second)
typedef int blip_long;
typedef unsigned blip_ulong;
//// Sample buffer for band-limited synthesis
// Time unit at source clock rate
class Blip_Buffer : public Blip_Buffer_ { typedef blip_long blip_time_t;
public:
// Output samples are 16-bit signed, with a range of -32768 to 32767
// Sets output sample rate and resizes and clears sample buffer typedef short blip_sample_t;
blargg_err_t set_sample_rate( int samples_per_sec, int msec_length = blip_default_length ); enum { blip_sample_max = 32767 };
// Sets number of source time units per second class Blip_Buffer {
void clock_rate( int clocks_per_sec ); public:
typedef const char* blargg_err_t;
// Clears buffer and removes all samples
void clear(); // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults
// to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there
// Use Blip_Synth to add waveform to buffer // isn't enough memory, returns error without affecting current buffer setup.
blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 );
// Resamples to time t, then subtracts t from current time. Appends result of resampling
// to buffer for reading. // Set number of source time units per second
void end_frame( blip_time_t t ); void clock_rate( long );
// Number of samples available for reading with read_samples() // End current time frame of specified duration and make its samples available
int samples_avail() const; // (along with any still-unread samples) for reading with read_samples(). Begins
// a new time frame at the end of the current frame.
// Reads at most n samples to out [0 to n-1] and returns number actually read. If stereo void end_frame( blip_time_t time );
// is true, writes to out [0], out [2], out [4] etc. instead.
int read_samples( blip_sample_t out [], int n, bool stereo = false ); // Read at most 'max_samples' out of buffer into 'dest', removing them from from
// the buffer. Returns number of samples actually read and removed. If stereo is
// More features // true, increments 'dest' one extra time after writing each sample, to allow
// easy interleving of two channels into a stereo output buffer.
// Sets flag that tells some Multi_Buffer types that sound was added to buffer, long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 );
// so they know that it needs to be mixed in. Only needs to be called once
// per time frame that sound was added. Not needed if not using Multi_Buffer. // Additional optional features
void set_modified() { modified_ = true; }
// Current output sample rate
// Sets high-pass filter frequency, from 0 to 20000 Hz, where higher values reduce bass more long sample_rate() const;
void bass_freq( int frequency );
// Length of buffer, in milliseconds
int length() const; // Length of buffer in milliseconds int length() const;
int sample_rate() const; // Current output sample rate
int clock_rate() const; // Number of source time units per second // Number of source time units per second
int output_latency() const; // Number of samples delay from offset() to read_samples() long clock_rate() const;
// Low-level features // Set frequency high-pass filter frequency, where higher values reduce bass more
void bass_freq( int frequency );
// Removes the first n samples
void remove_samples( int n ); // Number of samples delay from synthesis to samples read out
int output_latency() const;
// Returns number of clocks needed until n samples will be available.
// If buffer cannot even hold n samples, returns number of clocks // Remove all available samples and clear buffer to silence. If 'entire_buffer' is
// until buffer becomes full. // false, just clears out any samples waiting rather than the entire buffer.
blip_time_t count_clocks( int n ) const; void clear( int entire_buffer = 1 );
// Number of samples that should be mixed before calling end_frame( t ) // Number of samples available for reading with read_samples()
int count_samples( blip_time_t t ) const; long samples_avail() const;
// Mixes n samples into buffer // Remove 'count' samples from those waiting to be read
void mix_samples( const blip_sample_t in [], int n ); void remove_samples( long count );
// Resampled time (sorry, poor documentation right now) // Experimental features
// Resampled time is fixed-point, in terms of output samples. // Count number of clocks needed until 'count' samples will be available.
// If buffer can't even hold 'count' samples, returns number of clocks until
// Converts clock count to resampled time // buffer becomes full.
blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } blip_time_t count_clocks( long count ) const;
// Converts clock time since beginning of current time frame to resampled time // Number of raw samples that can be mixed within frame of specified duration.
blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } long count_samples( blip_time_t duration ) const;
// Returns factor that converts clock rate to resampled time // Mix 'count' samples from 'buf' into buffer.
blip_resampled_time_t clock_rate_factor( int clock_rate ) const; void mix_samples( blip_sample_t const* buf, long count );
// State save/load // not documented yet
void set_modified() { modified_ = 1; }
// Saves state, including high-pass filter and tails of last deltas. int clear_modified() { int b = modified_; modified_ = 0; return b; }
// All samples must have been read from buffer before calling this typedef blip_ulong blip_resampled_time_t;
// (that is, samples_avail() must return 0). void remove_silence( long count );
void save_state( blip_buffer_state_t* out ); blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; }
blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; }
// Loads state. State must have been saved from Blip_Buffer with same blip_resampled_time_t clock_rate_factor( long clock_rate ) const;
// settings during same run of program; states can NOT be stored on disk. public:
// Clears buffer before loading state. Blip_Buffer();
void load_state( const blip_buffer_state_t& in ); ~Blip_Buffer();
private: // Deprecated
// noncopyable typedef blip_resampled_time_t resampled_time_t;
Blip_Buffer( const Blip_Buffer& ); blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); }
Blip_Buffer& operator = ( const Blip_Buffer& ); blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); }
private:
// Implementation // noncopyable
public: Blip_Buffer( const Blip_Buffer& );
BLARGG_DISABLE_NOTHROW Blip_Buffer& operator = ( const Blip_Buffer& );
Blip_Buffer(); public:
~Blip_Buffer(); typedef blip_time_t buf_t_;
void remove_silence( int n ); blip_ulong factor_;
}; blip_resampled_time_t offset_;
buf_t_* buffer_;
blip_long buffer_size_;
//// Adds amplitude changes to Blip_Buffer blip_long reader_accum_;
int bass_shift_;
template<int quality,int range> class Blip_Synth; private:
long sample_rate_;
typedef Blip_Synth<8, 1> Blip_Synth_Fast; // faster, but less equalizer control long clock_rate_;
typedef Blip_Synth<12,1> Blip_Synth_Norm; // good for most things int bass_freq_;
typedef Blip_Synth<16,1> Blip_Synth_Good; // sharper filter cutoff int length_;
int modified_;
template<int quality,int range> friend class Blip_Reader;
class Blip_Synth { };
public:
#ifdef HAVE_CONFIG_H
// Sets volume of amplitude delta unit #include "config.h"
void volume( double v ) { impl.volume_unit( 1.0 / range * v ); } #endif
// Configures low-pass filter // Number of bits in resample ratio fraction. Higher values give a more accurate ratio
void treble_eq( const blip_eq_t& eq ) { impl.treble_eq( eq ); } // but reduce maximum buffer size.
#ifndef BLIP_BUFFER_ACCURACY
// Gets/sets default Blip_Buffer #define BLIP_BUFFER_ACCURACY 16
Blip_Buffer* output() const { return impl.buf; } #endif
void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; }
// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in
// Extends waveform to time t at current amplitude, then changes its amplitude to a // noticeable broadband noise when synthesizing high frequency square waves.
// Using this requires a separate Blip_Synth for each waveform. // Affects size of Blip_Synth objects since they store the waveform directly.
void update( blip_time_t t, int a ); #ifndef BLIP_PHASE_BITS
#if BLIP_BUFFER_FAST
// Low-level interface #define BLIP_PHASE_BITS 8
#else
// If no Blip_Buffer* is specified, uses one set by output() above #define BLIP_PHASE_BITS 6
#endif
// Adds amplitude transition at time t. Delta can be positive or negative. #endif
// The actual change in amplitude is delta * volume.
void offset( blip_time_t t, int delta, Blip_Buffer* ) const; // Internal
void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } typedef blip_ulong blip_resampled_time_t;
int const blip_widest_impulse_ = 16;
// Same as offset(), except code is inlined for higher performance int const blip_buffer_extra_ = blip_widest_impulse_ + 2;
void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { offset_resampled( buf->to_fixed( t ), delta, buf ); } int const blip_res = 1 << BLIP_PHASE_BITS;
void offset_inline( blip_time_t t, int delta ) const { offset_resampled( impl.buf->to_fixed( t ), delta, impl.buf ); } class blip_eq_t;
// Works directly in terms of fractional output samples. Use resampled time functions in Blip_Buffer class Blip_Synth_Fast_ {
// to convert clock counts to resampled time. public:
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; Blip_Buffer* buf;
int last_amp;
// Implementation int delta_factor;
public:
BLARGG_DISABLE_NOTHROW void volume_unit( double );
Blip_Synth_Fast_();
private: void treble_eq( blip_eq_t const& ) { }
#if BLIP_BUFFER_FAST };
Blip_Synth_Fast_ impl;
typedef char coeff_t; class Blip_Synth_ {
#else public:
Blip_Synth_ impl; Blip_Buffer* buf;
typedef short coeff_t; int last_amp;
// Left halves of first difference of step response for each possible phase int delta_factor;
coeff_t phases [quality / 2 * blip_res];
public: void volume_unit( double );
Blip_Synth() : impl( phases, quality ) { } Blip_Synth_( short* impulses, int width );
#endif void treble_eq( blip_eq_t const& );
}; private:
double volume_unit_;
short* const impulses;
//// Low-pass equalization parameters int const width;
blip_long kernel_unit;
class blip_eq_t { int impulses_size() const { return blip_res / 2 * width + 1; }
double treble, kaiser; void adjust_impulse();
int rolloff_freq, sample_rate, cutoff_freq; };
public:
// Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce // Quality level. Start with blip_good_quality.
// treble, small positive values (0 to 5.0) increase treble. const int blip_med_quality = 8;
blip_eq_t( double treble_db = 0 ); const int blip_good_quality = 12;
const int blip_high_quality = 16;
// See blip_buffer.txt
blip_eq_t( double treble, int rolloff_freq, int sample_rate, int cutoff_freq = 0, // Range specifies the greatest expected change in amplitude. Calculate it
double kaiser = 5.2 ); // by finding the difference between the maximum and minimum expected
// amplitudes (max - min).
// Generate center point and right half of impulse response template<int quality,int range>
virtual void generate( float out [], int count ) const; class Blip_Synth {
virtual ~blip_eq_t() { } public:
// Set overall volume of waveform
enum { oversample = blip_res }; void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); }
static int calc_count( int quality ) { return (quality - 1) * (oversample / 2) + 1; }
}; // Configure low-pass filter (see blip_buffer.txt)
void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); }
#include "Blip_Buffer_impl2.h"
// Get/set Blip_Buffer used for output
#endif Blip_Buffer* output() const { return impl.buf; }
void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; }
// Update amplitude of waveform at given time. Using this requires a separate
// Blip_Synth for each waveform.
void update( blip_time_t time, int amplitude );
// Low-level interface
// Add an amplitude transition of specified delta, optionally into specified buffer
// rather than the one set with output(). Delta can be positive or negative.
// The actual change in amplitude is delta * (volume / range)
void offset( blip_time_t, int delta, Blip_Buffer* ) const;
void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); }
// Works directly in terms of fractional output samples. Contact author for more info.
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
// Same as offset(), except code is inlined for higher performance
void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const {
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
}
void offset_inline( blip_time_t t, int delta ) const {
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
}
private:
#if BLIP_BUFFER_FAST
Blip_Synth_Fast_ impl;
#else
Blip_Synth_ impl;
typedef short imp_t;
imp_t impulses [blip_res * (quality / 2) + 1];
public:
Blip_Synth() : impl( impulses, quality ) { }
#endif
// disable broken defaulted constructors, Blip_Synth_ isn't safe to move/copy
Blip_Synth<quality, range> (const Blip_Synth<quality, range> &) = delete;
Blip_Synth<quality, range> ( Blip_Synth<quality, range> &&) = delete;
Blip_Synth<quality, range>& operator=(const Blip_Synth<quality, range> &) = delete;
};
// Low-pass equalization parameters
class blip_eq_t {
public:
// Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce
// treble, small positive values (0 to 5.0) increase treble.
blip_eq_t( double treble_db = 0 );
// See blip_buffer.txt
blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 );
private:
double treble;
long rolloff_freq;
long sample_rate;
long cutoff_freq;
void generate( float* out, int count ) const;
friend class Blip_Synth_;
};
int const blip_sample_bits = 30;
// Dummy Blip_Buffer to direct sound output to, for easy muting without
// having to stop sound code.
class Silent_Blip_Buffer : public Blip_Buffer {
buf_t_ buf [blip_buffer_extra_ + 1];
public:
// The following cannot be used (an assertion will fail if attempted):
blargg_err_t set_sample_rate( long samples_per_sec, int msec_length );
blip_time_t count_clocks( long count ) const;
void mix_samples( blip_sample_t const* buf, long count );
Silent_Blip_Buffer();
};
#if defined (__GNUC__) || _MSC_VER >= 1100
#define BLIP_RESTRICT __restrict
#else
#define BLIP_RESTRICT
#endif
// Optimized reading from Blip_Buffer, for use in custom sample output
// Begin reading from buffer. Name should be unique to the current block.
#define BLIP_READER_BEGIN( name, blip_buffer ) \
const Blip_Buffer::buf_t_* BLIP_RESTRICT name##_reader_buf = (blip_buffer).buffer_;\
blip_long name##_reader_accum = (blip_buffer).reader_accum_
// Get value to pass to BLIP_READER_NEXT()
#define BLIP_READER_BASS( blip_buffer ) ((blip_buffer).bass_shift_)
// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal
// code at the cost of having no bass control
int const blip_reader_default_bass = 9;
// Current sample
#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16))
// Current raw sample in full internal resolution
#define BLIP_READER_READ_RAW( name ) (name##_reader_accum)
// Advance to next sample
#define BLIP_READER_NEXT( name, bass ) \
(void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass)))
// End reading samples from buffer. The number of samples read must now be removed
// using Blip_Buffer::remove_samples().
#define BLIP_READER_END( name, blip_buffer ) \
(void) ((blip_buffer).reader_accum_ = name##_reader_accum)
// Compatibility with older version
const long blip_unscaled = 65535;
const int blip_low_quality = blip_med_quality;
const int blip_best_quality = blip_high_quality;
// Deprecated; use BLIP_READER macros as follows:
// Blip_Reader r; r.begin( buf ); -> BLIP_READER_BEGIN( r, buf );
// int bass = r.begin( buf ) -> BLIP_READER_BEGIN( r, buf ); int bass = BLIP_READER_BASS( buf );
// r.read() -> BLIP_READER_READ( r )
// r.read_raw() -> BLIP_READER_READ_RAW( r )
// r.next( bass ) -> BLIP_READER_NEXT( r, bass )
// r.next() -> BLIP_READER_NEXT( r, blip_reader_default_bass )
// r.end( buf ) -> BLIP_READER_END( r, buf )
class Blip_Reader {
public:
int begin( Blip_Buffer& );
blip_long read() const { return accum >> (blip_sample_bits - 16); }
blip_long read_raw() const { return accum; }
void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); }
void end( Blip_Buffer& b ) { b.reader_accum_ = accum; }
private:
const Blip_Buffer::buf_t_* buf;
blip_long accum;
};
// End of public interface
#include <assert.h>
template<int quality,int range>
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
int delta, Blip_Buffer* blip_buf ) const
{
// Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the
// need for a longer buffer as set by set_sample_rate().
assert( (blip_long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ );
delta *= impl.delta_factor;
blip_long* BLIP_RESTRICT buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY);
int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1));
#if BLIP_BUFFER_FAST
blip_long left = buf [0] + delta;
// Kind of crappy, but doing shift after multiply results in overflow.
// Alternate way of delaying multiply by delta_factor results in worse
// sub-sample resolution.
blip_long right = (delta >> BLIP_PHASE_BITS) * phase;
left -= right;
right += buf [1];
buf [0] = left;
buf [1] = right;
#else
int const fwd = (blip_widest_impulse_ - quality) / 2;
int const rev = fwd + quality - 2;
int const mid = quality / 2 - 1;
imp_t const* BLIP_RESTRICT imp = impulses + blip_res - phase;
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
// straight forward implementation resulted in better code on GCC for x86
#define ADD_IMP( out, in ) \
buf [out] += (blip_long) imp [blip_res * (in)] * delta
#define BLIP_FWD( i ) {\
ADD_IMP( fwd + i, i );\
ADD_IMP( fwd + 1 + i, i + 1 );\
}
#define BLIP_REV( r ) {\
ADD_IMP( rev - r, r + 1 );\
ADD_IMP( rev + 1 - r, r );\
}
BLIP_FWD( 0 )
if ( quality > 8 ) BLIP_FWD( 2 )
if ( quality > 12 ) BLIP_FWD( 4 )
{
ADD_IMP( fwd + mid - 1, mid - 1 );
ADD_IMP( fwd + mid , mid );
imp = impulses + phase;
}
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
ADD_IMP( rev , 1 );
ADD_IMP( rev + 1, 0 );
#else
// for RISC processors, help compiler by reading ahead of writes
#define BLIP_FWD( i ) {\
blip_long t0 = i0 * delta + buf [fwd + i];\
blip_long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i];\
i0 = imp [blip_res * (i + 2)];\
buf [fwd + i] = t0;\
buf [fwd + 1 + i] = t1;\
}
#define BLIP_REV( r ) {\
blip_long t0 = i0 * delta + buf [rev - r];\
blip_long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r];\
i0 = imp [blip_res * (r - 1)];\
buf [rev - r] = t0;\
buf [rev + 1 - r] = t1;\
}
blip_long i0 = *imp;
BLIP_FWD( 0 )
if ( quality > 8 ) BLIP_FWD( 2 )
if ( quality > 12 ) BLIP_FWD( 4 )
{
blip_long t0 = i0 * delta + buf [fwd + mid - 1];
blip_long t1 = imp [blip_res * mid] * delta + buf [fwd + mid ];
imp = impulses + phase;
i0 = imp [blip_res * mid];
buf [fwd + mid - 1] = t0;
buf [fwd + mid ] = t1;
}
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
blip_long t0 = i0 * delta + buf [rev ];
blip_long t1 = *imp * delta + buf [rev + 1];
buf [rev ] = t0;
buf [rev + 1] = t1;
#endif
#endif
}
#undef BLIP_FWD
#undef BLIP_REV
template<int quality,int range>
#if BLIP_BUFFER_FAST
inline
#endif
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
{
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
}
template<int quality,int range>
#if BLIP_BUFFER_FAST
inline
#endif
void Blip_Synth<quality,range>::update( blip_time_t t, int amp )
{
int delta = amp - impl.last_amp;
impl.last_amp = amp;
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
}
inline blip_eq_t::blip_eq_t( double t ) :
treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { }
inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) :
treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { }
inline int Blip_Buffer::length() const { return length_; }
inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); }
inline long Blip_Buffer::sample_rate() const { return sample_rate_; }
inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; }
inline long Blip_Buffer::clock_rate() const { return clock_rate_; }
inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); }
inline int Blip_Reader::begin( Blip_Buffer& blip_buf )
{
buf = blip_buf.buffer_;
accum = blip_buf.reader_accum_;
return blip_buf.bass_shift_;
}
int const blip_max_length = 0;
int const blip_default_length = 250;
#endif

View file

@ -1,135 +0,0 @@
// Internal stuff here to keep public header uncluttered
// Blip_Buffer $vers
#ifndef BLIP_BUFFER_IMPL_H
#define BLIP_BUFFER_IMPL_H
typedef unsigned blip_resampled_time_t;
#ifndef BLIP_MAX_QUALITY
#define BLIP_MAX_QUALITY 32
#endif
#ifndef BLIP_BUFFER_ACCURACY
#define BLIP_BUFFER_ACCURACY 16
#endif
#ifndef BLIP_PHASE_BITS
#define BLIP_PHASE_BITS 6
#endif
class blip_eq_t;
class Blip_Buffer;
#if BLIP_BUFFER_FAST
// linear interpolation needs 8 bits
#undef BLIP_PHASE_BITS
#define BLIP_PHASE_BITS 8
#undef BLIP_MAX_QUALITY
#define BLIP_MAX_QUALITY 2
#endif
int const blip_res = 1 << BLIP_PHASE_BITS;
int const blip_buffer_extra_ = BLIP_MAX_QUALITY + 2;
class Blip_Buffer_ {
public:
// Writer
typedef int clocks_t;
// Properties of fixed-point sample position
typedef unsigned fixed_t; // unsigned for more range, optimized shifts
enum { fixed_bits = BLIP_BUFFER_ACCURACY }; // bits in fraction
enum { fixed_unit = 1 << fixed_bits }; // 1.0 samples
// Converts clock count to fixed-point sample position
fixed_t to_fixed( clocks_t t ) const { return t * factor_ + offset_; }
// Deltas in buffer are fixed-point with this many fraction bits.
// Less than 16 for extra range.
enum { delta_bits = 14 };
// Pointer to first committed delta sample
typedef int delta_t;
// Pointer to delta corresponding to fixed-point sample position
delta_t* delta_at( fixed_t );
// Reader
delta_t* read_pos() { return buffer_; }
void clear_modified() { modified_ = false; }
int highpass_shift() const { return bass_shift_; }
int integrator() const { return reader_accum_; }
void set_integrator( int n ) { reader_accum_ = n; }
public: //friend class Tracked_Blip_Buffer; private:
bool modified() const { return modified_; }
void remove_silence( int count );
private:
unsigned factor_;
fixed_t offset_;
delta_t* buffer_center_;
int buffer_size_;
int reader_accum_;
int bass_shift_;
delta_t* buffer_;
int sample_rate_;
int clock_rate_;
int bass_freq_;
int length_;
bool modified_;
friend class Blip_Buffer;
};
class Blip_Synth_Fast_ {
public:
int delta_factor;
int last_amp;
Blip_Buffer* buf;
void volume_unit( double );
void treble_eq( blip_eq_t const& ) { }
Blip_Synth_Fast_();
};
class Blip_Synth_ {
public:
int delta_factor;
int last_amp;
Blip_Buffer* buf;
void volume_unit( double );
void treble_eq( blip_eq_t const& );
Blip_Synth_( short phases [], int width );
private:
double volume_unit_;
short* const phases;
int const width;
int kernel_unit;
void adjust_impulse();
void rescale_kernel( int shift );
int impulses_size() const { return blip_res / 2 * width; }
};
class blip_buffer_state_t
{
blip_resampled_time_t offset_;
int reader_accum_;
int buf [blip_buffer_extra_];
friend class Blip_Buffer;
};
inline Blip_Buffer_::delta_t* Blip_Buffer_::delta_at( fixed_t f )
{
assert( (f >> fixed_bits) < (unsigned) buffer_size_ );
return buffer_center_ + (f >> fixed_bits);
}
#endif

View file

@ -1,282 +0,0 @@
// Internal stuff here to keep public header uncluttered
// Blip_Buffer $vers
#ifndef BLIP_BUFFER_IMPL2_H
#define BLIP_BUFFER_IMPL2_H
//// Compatibility
BLARGG_DEPRECATED( int const blip_low_quality = 8; )
BLARGG_DEPRECATED( int const blip_med_quality = 8; )
BLARGG_DEPRECATED( int const blip_good_quality = 12; )
BLARGG_DEPRECATED( int const blip_high_quality = 16; )
BLARGG_DEPRECATED( int const blip_sample_max = 32767; )
// Number of bits in raw sample that covers normal output range. Less than 32 bits to give
// extra amplitude range. That is,
// +1 << (blip_sample_bits-1) = +1.0
// -1 << (blip_sample_bits-1) = -1.0
int const blip_sample_bits = 30;
//// BLIP_READER_
//// Optimized reading from Blip_Buffer, for use in custom sample buffer or mixer
// Begins reading from buffer. Name should be unique to the current {} block.
#define BLIP_READER_BEGIN( name, blip_buffer ) \
const Blip_Buffer::delta_t* BLARGG_RESTRICT name##_reader_buf = (blip_buffer).read_pos();\
int name##_reader_accum = (blip_buffer).integrator()
// Gets value to pass to BLIP_READER_NEXT()
#define BLIP_READER_BASS( blip_buffer ) (blip_buffer).highpass_shift()
// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal
// code at the cost of having no bass_freq() functionality
int const blip_reader_default_bass = 9;
// Current sample as 16-bit signed value
#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16))
// Current raw sample in full internal resolution
#define BLIP_READER_READ_RAW( name ) (name##_reader_accum)
// Advances to next sample
#define BLIP_READER_NEXT( name, bass ) \
(void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass)))
// Ends reading samples from buffer. The number of samples read must now be removed
// using Blip_Buffer::remove_samples().
#define BLIP_READER_END( name, blip_buffer ) \
(void) ((blip_buffer).set_integrator( name##_reader_accum ))
#define BLIP_READER_ADJ_( name, offset ) (name##_reader_buf += offset)
int const blip_reader_idx_factor = sizeof (Blip_Buffer::delta_t);
#define BLIP_READER_NEXT_IDX_( name, bass, idx ) {\
name##_reader_accum -= name##_reader_accum >> (bass);\
name##_reader_accum += name##_reader_buf [(idx)];\
}
#define BLIP_READER_NEXT_RAW_IDX_( name, bass, idx ) {\
name##_reader_accum -= name##_reader_accum >> (bass);\
name##_reader_accum +=\
*(Blip_Buffer::delta_t const*) ((char const*) name##_reader_buf + (idx));\
}
//// BLIP_CLAMP
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
#define BLIP_X86 1
#define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in
#else
#define BLIP_CLAMP_( in ) (blip_sample_t) in != in
#endif
// Clamp sample to blip_sample_t range
#define BLIP_CLAMP( sample, out )\
{ if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 31) ^ 0x7FFF; }
//// Blip_Synth
// (in >> sh & mask) * mul
#define BLIP_SH_AND_MUL( in, sh, mask, mul ) \
((int) (in) / ((1U << (sh)) / (mul)) & (unsigned) ((mask) * (mul)))
// (T*) ptr + (off >> sh)
#define BLIP_PTR_OFF_SH( T, ptr, off, sh ) \
((T*) (BLIP_SH_AND_MUL( off, sh, -1, sizeof (T) ) + (char*) (ptr)))
template<int quality,int range>
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
int delta, Blip_Buffer* blip_buf ) const
{
#if BLIP_BUFFER_FAST
int const half_width = 1;
#else
int const half_width = quality / 2;
#endif
Blip_Buffer::delta_t* BLARGG_RESTRICT buf = blip_buf->delta_at( time );
delta *= impl.delta_factor;
int const phase_shift = BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS;
int const phase = (half_width & (half_width - 1)) ?
(int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) ) * half_width :
(int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) * half_width );
#if BLIP_BUFFER_FAST
int left = buf [0] + delta;
// Kind of crappy, but doing shift after multiply results in overflow.
// Alternate way of delaying multiply by delta_factor results in worse
// sub-sample resolution.
int right = (delta >> BLIP_PHASE_BITS) * phase;
#if BLIP_BUFFER_NOINTERP
// TODO: remove? (just a hack to see how it sounds)
right = 0;
#endif
left -= right;
right += buf [1];
buf [0] = left;
buf [1] = right;
#else
int const fwd = -quality / 2;
int const rev = fwd + quality - 2;
coeff_t const* BLARGG_RESTRICT imp = (coeff_t const*) ((char const*) phases + phase);
int const phase2 = phase + phase - (blip_res - 1) * half_width * sizeof (coeff_t);
#define BLIP_MID_IMP imp = (coeff_t const*) ((char const*) imp - phase2);
#if BLIP_MAX_QUALITY > 16
// General version for any quality
if ( quality != 8 && quality != 12 && quality != 16 )
{
buf += fwd;
// left half
for ( int n = half_width / 2; --n >= 0; )
{
buf [0] += imp [0] * delta;
buf [1] += imp [1] * delta;
imp += 2;
buf += 2;
}
// mirrored right half
BLIP_MID_IMP
for ( int n = half_width / 2; --n >= 0; )
{
buf [0] += imp [-1] * delta;
buf [1] += *(imp -= 2) * delta;
buf += 2;
}
return;
}
#endif
// Unrolled versions for qualities 8, 12, and 16
#if BLIP_X86
// This gives better code for x86
#define BLIP_ADD( out, in ) \
buf [out] += imp [in] * delta
#define BLIP_FWD( i ) {\
BLIP_ADD( fwd + i, i );\
BLIP_ADD( fwd + 1 + i, i + 1 );\
}
#define BLIP_REV( r ) {\
BLIP_ADD( rev - r, r + 1 );\
BLIP_ADD( rev + 1 - r, r );\
}
BLIP_FWD( 0 )
BLIP_FWD( 2 )
if ( quality > 8 ) BLIP_FWD( 4 )
if ( quality > 12 ) BLIP_FWD( 6 )
BLIP_MID_IMP
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
BLIP_REV( 0 )
#else
// Help RISC processors and simplistic compilers by reading ahead of writes
#define BLIP_FWD( i ) {\
int t0 = i0 * delta + buf [fwd + i];\
int t1 = imp [i + 1] * delta + buf [fwd + 1 + i];\
i0 = imp [i + 2];\
buf [fwd + i] = t0;\
buf [fwd + 1 + i] = t1;\
}
#define BLIP_REV( r ) {\
int t0 = i0 * delta + buf [rev - r];\
int t1 = imp [r] * delta + buf [rev + 1 - r];\
i0 = imp [r - 1];\
buf [rev - r] = t0;\
buf [rev + 1 - r] = t1;\
}
int i0 = *imp;
BLIP_FWD( 0 )
if ( quality > 8 ) BLIP_FWD( 2 )
if ( quality > 12 ) BLIP_FWD( 4 )
{
int const mid = half_width - 1;
int t0 = i0 * delta + buf [fwd + mid - 1];
int t1 = imp [mid] * delta + buf [fwd + mid ];
BLIP_MID_IMP
i0 = imp [mid];
buf [fwd + mid - 1] = t0;
buf [fwd + mid ] = t1;
}
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
int t0 = i0 * delta + buf [rev ];
int t1 = *imp * delta + buf [rev + 1];
buf [rev ] = t0;
buf [rev + 1] = t1;
#endif
#endif
}
template<int quality,int range>
#if BLIP_BUFFER_FAST
inline
#endif
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
{
offset_resampled( buf->to_fixed( t ), delta, buf );
}
template<int quality,int range>
#if BLIP_BUFFER_FAST
inline
#endif
void Blip_Synth<quality,range>::update( blip_time_t t, int amp )
{
int delta = amp - impl.last_amp;
impl.last_amp = amp;
offset_resampled( impl.buf->to_fixed( t ), delta, impl.buf );
}
//// blip_eq_t
inline blip_eq_t::blip_eq_t( double t ) :
treble( t ), kaiser( 5.2 ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { }
inline blip_eq_t::blip_eq_t( double t, int rf, int sr, int cf, double k ) :
treble( t ), kaiser( k ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { }
//// Blip_Buffer
inline int Blip_Buffer::length() const { return length_; }
inline int Blip_Buffer::samples_avail() const { return (int) (offset_ >> BLIP_BUFFER_ACCURACY); }
inline int Blip_Buffer::sample_rate() const { return sample_rate_; }
inline int Blip_Buffer::output_latency() const { return BLIP_MAX_QUALITY / 2; }
inline int Blip_Buffer::clock_rate() const { return clock_rate_; }
inline void Blip_Buffer::clock_rate( int cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); }
inline void Blip_Buffer::remove_silence( int count )
{
// fails if you try to remove more samples than available
assert( count <= samples_avail() );
offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
}
#endif

View file

@ -0,0 +1,227 @@
# List of source files required by libgme and any emulators
# This is not 100% accurate (Fir_Resampler for instance) but
# you'll be OK.
set(libgme_SRCS Blip_Buffer.cpp
Classic_Emu.cpp
Data_Reader.cpp
Dual_Resampler.cpp
Effects_Buffer.cpp
Fir_Resampler.cpp
gme.cpp
Gme_File.cpp
M3u_Playlist.cpp
Multi_Buffer.cpp
Music_Emu.cpp
)
find_package(ZLIB QUIET)
# Ay_Apu is very popular around here
if (USE_GME_AY OR USE_GME_KSS)
set(libgme_SRCS ${libgme_SRCS}
Ay_Apu.cpp
)
endif()
# so is Ym2612_Emu
if (USE_GME_VGM OR USE_GME_GYM)
if(GME_YM2612_EMU STREQUAL "Nuked")
add_definitions(-DVGM_YM2612_NUKED)
set(libgme_SRCS ${libgme_SRCS}
Ym2612_Nuked.cpp
)
message("VGM/GYM: Nuked OPN2 emulator will be used")
elseif(GME_YM2612_EMU STREQUAL "MAME")
add_definitions(-DVGM_YM2612_MAME)
set(libgme_SRCS ${libgme_SRCS}
Ym2612_MAME.cpp
)
message("VGM/GYM: MAME YM2612 emulator will be used")
else()
add_definitions(-DVGM_YM2612_GENS)
set(libgme_SRCS ${libgme_SRCS}
Ym2612_GENS.cpp
)
message("VGM/GYM: GENS 2.10 emulator will be used")
endif()
endif()
# But none are as popular as Sms_Apu
if (USE_GME_VGM OR USE_GME_GYM OR USE_GME_KSS)
set(libgme_SRCS ${libgme_SRCS}
Sms_Apu.cpp
)
endif()
if (USE_GME_AY)
set(libgme_SRCS ${libgme_SRCS}
# Ay_Apu.cpp included earlier
Ay_Cpu.cpp
Ay_Emu.cpp
)
endif()
if (USE_GME_GBS)
set(libgme_SRCS ${libgme_SRCS}
Gb_Apu.cpp
Gb_Cpu.cpp
Gb_Oscs.cpp
Gbs_Emu.cpp
)
endif()
if (USE_GME_GYM)
set(libgme_SRCS ${libgme_SRCS}
# Sms_Apu.cpp included earlier
# Ym2612_Emu.cpp included earlier
Gym_Emu.cpp
)
endif()
if (USE_GME_HES)
set(libgme_SRCS ${libgme_SRCS}
Hes_Apu.cpp
Hes_Cpu.cpp
Hes_Emu.cpp
)
endif()
if (USE_GME_KSS)
set(libgme_SRCS ${libgme_SRCS}
# Ay_Apu.cpp included earlier
# Sms_Apu.cpp included earlier
Kss_Cpu.cpp
Kss_Emu.cpp
Kss_Scc_Apu.cpp
)
endif()
if (USE_GME_NSF OR USE_GME_NSFE)
set(libgme_SRCS ${libgme_SRCS}
Nes_Apu.cpp
Nes_Cpu.cpp
Nes_Fme7_Apu.cpp
Nes_Namco_Apu.cpp
Nes_Oscs.cpp
Nes_Vrc6_Apu.cpp
Nsf_Emu.cpp
)
endif()
if (USE_GME_NSFE)
set(libgme_SRCS ${libgme_SRCS}
Nsfe_Emu.cpp
)
endif()
if (USE_GME_SAP)
set(libgme_SRCS ${libgme_SRCS}
Sap_Apu.cpp
Sap_Cpu.cpp
Sap_Emu.cpp
)
endif()
if (USE_GME_SPC)
set(libgme_SRCS ${libgme_SRCS}
Snes_Spc.cpp
Spc_Cpu.cpp
Spc_Dsp.cpp
Spc_Emu.cpp
Spc_Filter.cpp
)
if (GME_SPC_ISOLATED_ECHO_BUFFER)
add_definitions(-DSPC_ISOLATED_ECHO_BUFFER)
endif()
endif()
if (USE_GME_VGM)
set(libgme_SRCS ${libgme_SRCS}
# Sms_Apu.cpp included earlier
# Ym2612_Emu.cpp included earlier
Vgm_Emu.cpp
Vgm_Emu_Impl.cpp
Ym2413_Emu.cpp
)
endif()
# These headers are part of the generic gme interface.
set (EXPORTED_HEADERS gme.h blargg_source.h)
# while building a macOS framework, exported headers must be in the source
# list, or the header files aren't copied to the bundle.
if (BUILD_FRAMEWORK)
set(libgme_SRCS ${libgme_SRCS} ${EXPORTED_HEADERS})
endif()
# On some platforms we may need to change headers or whatnot based on whether
# we're building the library or merely using the library. The following is
# only defined when building the library to allow us to tell which is which.
add_definitions(-DBLARGG_BUILD_DLL)
# For the gme_types.h
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Add library to be compiled.
add_library(gme ${libgme_SRCS})
if(ZLIB_FOUND)
message(" ** ZLib library located, compressed file formats will be supported")
target_compile_definitions(gme PRIVATE -DHAVE_ZLIB_H)
target_include_directories(gme PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(gme ${ZLIB_LIBRARIES})
# Is not to be installed though
set(PKG_CONFIG_ZLIB -lz) # evaluated in libgme.pc.in
else()
message("ZLib library not found, disabling support for compressed formats such as VGZ")
endif()
if(USE_GME_SPC)
if(UNRAR_FOUND)
message(" ** unRAR library located, the RSN file format will be supported")
target_compile_definitions(gme PRIVATE -DRARDLL)
target_include_directories(gme PRIVATE ${UNRAR_INCLUDE_DIRS})
target_link_libraries(gme ${UNRAR_LIBRARIES})
# Is not to be installed though
set(PKG_CONFIG_UNRAR -lunrar) # evaluated in libgme.pc.in
else()
message("unRAR library not found, disabling support for the RSN file format")
endif()
endif()
# The version is the release. The "soversion" is the API version. As long
# as only build fixes are performed (i.e. no backwards-incompatible changes
# to the API), the SOVERSION should be the same even when bumping up VERSION.
# The way gme.h is designed, SOVERSION should very rarely be bumped, if ever.
# Hopefully the API can stay compatible with old versions.
set_target_properties(gme
PROPERTIES VERSION ${GME_VERSION}
SOVERSION 1)
# macOS framework build
if(BUILD_FRAMEWORK)
set_target_properties(gme
PROPERTIES FRAMEWORK TRUE
FRAMEWORK_VERSION A
MACOSX_FRAMEWORK_IDENTIFIER net.mpyne.gme
VERSION ${GME_VERSION}
SOVERSION 0
PUBLIC_HEADER "${EXPORTED_HEADERS}")
endif()
install(TARGETS gme LIBRARY DESTINATION lib${LIB_SUFFIX}
RUNTIME DESTINATION bin # DLL platforms
ARCHIVE DESTINATION lib # DLL platforms
FRAMEWORK DESTINATION /Library/Frameworks) # macOS framework
# Run during cmake phase, so this is available during make
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gme_types.h.in
${CMAKE_CURRENT_BINARY_DIR}/gme_types.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libgme.pc.in
${CMAKE_CURRENT_BINARY_DIR}/libgme.pc @ONLY)
install(FILES ${EXPORTED_HEADERS} DESTINATION include/gme)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgme.pc DESTINATION lib${LIB_SUFFIX}/pkgconfig)

View file

@ -1,124 +1,190 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
#include <string.h>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
General Public License as published by the Free Software Foundation; either can redistribute it and/or modify it under the terms of the GNU Lesser
version 2.1 of the License, or (at your option) any later version. This General Public License as published by the Free Software Foundation; either
module is distributed in the hope that it will be useful, but WITHOUT ANY version 2.1 of the License, or (at your option) any later version. This
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS module is distributed in the hope that it will be useful, but WITHOUT ANY
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
details. You should have received a copy of the GNU Lesser General Public FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
License along with this module; if not, write to the Free Software Foundation, details. You should have received a copy of the GNU Lesser General Public
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#include "blargg_source.h"
Classic_Emu::Classic_Emu()
{ Classic_Emu::Classic_Emu()
buf = NULL; {
stereo_buffer = NULL; buf = 0;
voice_types = NULL; stereo_buffer = 0;
voice_types = 0;
// avoid inconsistency in our duplicated constants
assert( (int) wave_type == (int) Multi_Buffer::wave_type ); // avoid inconsistency in our duplicated constants
assert( (int) noise_type == (int) Multi_Buffer::noise_type ); assert( (int) wave_type == (int) Multi_Buffer::wave_type );
assert( (int) mixed_type == (int) Multi_Buffer::mixed_type ); assert( (int) noise_type == (int) Multi_Buffer::noise_type );
} assert( (int) mixed_type == (int) Multi_Buffer::mixed_type );
}
Classic_Emu::~Classic_Emu()
{ Classic_Emu::~Classic_Emu()
delete stereo_buffer; {
delete effects_buffer_; delete stereo_buffer;
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_( long 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 ); }
}
blargg_err_t Classic_Emu::set_multi_channel ( bool is_enabled )
void Classic_Emu::mute_voices_( int mask ) {
{ RETURN_ERR( Music_Emu::set_multi_channel_( is_enabled ) );
Music_Emu::mute_voices_( mask ); return 0;
for ( int i = voice_count(); i--; ) }
{
if ( mask & (1 << i) ) void Classic_Emu::mute_voices_( int mask )
{ {
set_voice( i, NULL, NULL, NULL ); Music_Emu::mute_voices_( mask );
} for ( int i = voice_count(); i--; )
else {
{ if ( mask & (1 << i) )
Multi_Buffer::channel_t ch = buf->channel( i ); {
assert( (ch.center && ch.left && ch.right) || set_voice( i, 0, 0, 0 );
(!ch.center && !ch.left && !ch.right) ); // all or nothing }
set_voice( i, ch.center, ch.left, ch.right ); else
} {
} Multi_Buffer::channel_t ch = buf->channel( i, (voice_types ? voice_types [i] : 0) );
} assert( (ch.center && ch.left && ch.right) ||
(!ch.center && !ch.left && !ch.right) ); // all or nothing
void Classic_Emu::change_clock_rate( int rate ) set_voice( i, ch.center, ch.left, ch.right );
{ }
clock_rate_ = rate; }
buf->clock_rate( rate ); }
}
void Classic_Emu::change_clock_rate( long rate )
blargg_err_t Classic_Emu::setup_buffer( int rate ) {
{ clock_rate_ = rate;
change_clock_rate( rate ); buf->clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) ); }
set_equalizer( equalizer() );
buf_changed_count = buf->channels_changed_count(); blargg_err_t Classic_Emu::setup_buffer( long rate )
return blargg_ok; {
} change_clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count() ) );
blargg_err_t Classic_Emu::start_track_( int track ) set_equalizer( equalizer() );
{ buf_changed_count = buf->channels_changed_count();
RETURN_ERR( Music_Emu::start_track_( track ) ); return 0;
buf->clear(); }
return blargg_ok;
} blargg_err_t Classic_Emu::start_track_( int track )
{
blargg_err_t Classic_Emu::play_( int count, sample_t out [] ) RETURN_ERR( Music_Emu::start_track_( track ) );
{ buf->clear();
// read from buffer, then refill buffer and repeat if necessary return 0;
int remain = count; }
while ( remain )
{ blargg_err_t Classic_Emu::play_( long count, sample_t* out )
buf->disable_immediate_removal(); {
remain -= buf->read_samples( &out [count - remain], remain ); long remain = count;
if ( remain ) while ( remain )
{ {
if ( buf_changed_count != buf->channels_changed_count() ) remain -= buf->read_samples( &out [count - remain], remain );
{ if ( remain )
buf_changed_count = buf->channels_changed_count(); {
remute_voices(); if ( buf_changed_count != buf->channels_changed_count() )
} {
buf_changed_count = buf->channels_changed_count();
// TODO: use more accurate length calculation remute_voices();
int msec = buf->length(); }
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100; int msec = buf->length();
RETURN_ERR( run_clocks( clocks_emulated, msec ) ); blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000;
assert( clocks_emulated ); RETURN_ERR( run_clocks( clocks_emulated, msec ) );
buf->end_frame( clocks_emulated ); assert( clocks_emulated );
} buf->end_frame( clocks_emulated );
} }
return blargg_ok; }
} return 0;
}
// Rom_Data
blargg_err_t Rom_Data_::load_rom_data_( Data_Reader& in,
int header_size, void* header_out, int fill, long pad_size )
{
long file_offset = pad_size - header_size;
rom_addr = 0;
mask = 0;
size_ = 0;
rom.clear();
file_size_ = in.remain();
if ( file_size_ <= header_size ) // <= because there must be data after header
return gme_wrong_file_type;
blargg_err_t err = rom.resize( file_offset + file_size_ + pad_size );
if ( !err )
err = in.read( rom.begin() + file_offset, file_size_ );
if ( err )
{
rom.clear();
return err;
}
file_size_ -= header_size;
memcpy( header_out, &rom [file_offset], header_size );
memset( rom.begin() , fill, pad_size );
memset( rom.end() - pad_size, fill, pad_size );
return 0;
}
void Rom_Data_::set_addr_( long addr, int unit )
{
rom_addr = addr - unit - pad_extra;
long rounded = (addr + file_size_ + unit - 1) / unit * unit;
if ( rounded <= 0 )
{
rounded = 0;
}
else
{
int shift = 0;
unsigned long max_addr = (unsigned long) (rounded - 1);
while ( max_addr >> shift )
shift++;
mask = (1L << shift) - 1;
}
if ( addr < 0 )
addr = 0;
size_ = rounded;
if ( rom.resize( rounded - rom_addr + pad_extra ) ) { } // OK if shrink fails
if ( 0 )
{
debug_printf( "addr: %X\n", addr );
debug_printf( "file_size: %d\n", file_size_ );
debug_printf( "rounded: %d\n", rounded );
debug_printf( "mask: $%X\n", mask );
}
}

View file

@ -1,79 +1,128 @@
// 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 https://bitbucket.org/mpyne/game-music-emu/
#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: public:
// Derived interface Classic_Emu();
~Classic_Emu();
// Advertises type of sound on each voice, so Effects_Buffer can better choose void set_buffer( Multi_Buffer* ) override;
// what effect to apply (pan, echo, surround). Constant can have value added so blargg_err_t set_multi_channel( bool is_enabled ) override;
// that voices of the same type can be spread around the stereo sound space. protected:
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type }; // Services
void set_voice_types( int const types [] ) { voice_types = types; } enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
void set_voice_types( int const* t ) { voice_types = t; }
// Sets up Blip_Buffers after loading file blargg_err_t setup_buffer( long clock_rate );
blargg_err_t setup_buffer( int clock_rate ); long clock_rate() const { return clock_rate_; }
void change_clock_rate( long ); // experimental
// Clock rate of Blip_buffers
int clock_rate() const { return clock_rate_; } // Overridable
virtual void set_voice( int index, Blip_Buffer* center,
// Changes clock rate of Blip_Buffers (experimental) Blip_Buffer* left, Blip_Buffer* right ) = 0;
void change_clock_rate( int ); virtual void update_eq( blip_eq_t const& ) = 0;
virtual blargg_err_t start_track_( int track ) override;
// Overrides should do the indicated task virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) = 0;
protected:
// Set Blip_Buffer(s) voice outputs to, or mute voice if pointer is NULL blargg_err_t set_sample_rate_( long sample_rate ) override;
virtual void set_voice( int index, Blip_Buffer* center, void mute_voices_( int ) override;
Blip_Buffer* left, Blip_Buffer* right ) BLARGG_PURE( ; ) void set_equalizer_( equalizer_t const& ) override;
blargg_err_t play_( long, sample_t* ) override;
// Update equalization private:
virtual void update_eq( blip_eq_t const& ) BLARGG_PURE( ; ) Multi_Buffer* buf;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer
// Start track long clock_rate_;
virtual blargg_err_t start_track_( int track ) BLARGG_PURE( ; ) unsigned buf_changed_count;
int const* voice_types;
// Run for at most msec or time_io clocks, then set time_io to number of clocks };
// actually run for. After returning, Blip_Buffers have time frame of time_io clocks
// ended. inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) BLARGG_PURE( ; ) {
assert( !buf && new_buf );
// Internal buf = new_buf;
public: }
Classic_Emu();
~Classic_Emu(); // ROM data handler, used by several Classic_Emu derivitives. Loads file data
virtual void set_buffer( Multi_Buffer* ); // with padding on both sides, allowing direct use in bank mapping. The main purpose
// is to allow all file data to be loaded with only one read() call (for efficiency).
protected:
virtual blargg_err_t set_sample_rate_( int sample_rate ); class Rom_Data_ {
virtual void mute_voices_( int ); public:
virtual void set_equalizer_( equalizer_t const& ); typedef unsigned char byte;
virtual blargg_err_t play_( int, sample_t [] ); protected:
enum { pad_extra = 8 };
private: blargg_vector<byte> rom;
Multi_Buffer* buf; long file_size_;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer blargg_long rom_addr;
int clock_rate_; blargg_long mask;
unsigned buf_changed_count; blargg_long size_; // TODO: eliminate
int const* voice_types;
}; blargg_err_t load_rom_data_( Data_Reader& in, int header_size, void* header_out,
int fill, long pad_size );
inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf ) void set_addr_( long addr, int unit );
{ };
assert( !buf && new_buf );
buf = new_buf; template<int unit>
} class Rom_Data : public Rom_Data_ {
enum { pad_size = unit + pad_extra };
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { } public:
// Load file data, using already-loaded header 'h' if not NULL. Copy header
inline void Classic_Emu::update_eq( blip_eq_t const& ) { } // from loaded file data into *out and fill unmapped bytes with 'fill'.
blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill )
inline blargg_err_t Classic_Emu::run_clocks( blip_time_t&, int ) { return blargg_ok; } {
return load_rom_data_( in, header_size, header_out, fill, pad_size );
#endif }
// Size of file data read in (excluding header)
long file_size() const { return file_size_; }
// Pointer to beginning of file data
byte* begin() const { return rom.begin() + pad_size; }
// Set address that file data should start at
void set_addr( long addr ) { set_addr_( addr, unit ); }
// Free data
void clear() { rom.clear(); }
// Size of data + start addr, rounded to a multiple of unit
long size() const { return size_; }
// Pointer to unmapped page filled with same value
byte* unmapped() { return rom.begin(); }
// Mask address to nearest power of two greater than size()
blargg_long mask_addr( blargg_long addr ) const
{
#ifdef check
check( addr <= mask );
#endif
return addr & mask;
}
// Pointer to page starting at addr. Returns unmapped() if outside data.
byte* at_addr( blargg_long addr )
{
blargg_ulong offset = mask_addr( addr ) - rom_addr;
if ( offset > blargg_ulong (rom.size() - pad_size) )
offset = 0; // unmapped
return &rom [offset];
}
};
#ifndef GME_APU_HOOK
#define GME_APU_HOOK( emu, addr, data ) ((void) 0)
#endif
#ifndef GME_FRAME_HOOK
#define GME_FRAME_HOOK( emu ) ((void) 0)
#else
#define GME_FRAME_HOOK_DEFINED 1
#endif
#endif

768
Frameworks/GME/gme/Data_Reader.cpp Executable file → Normal file
View file

@ -1,315 +1,453 @@
// 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>
#include <algorithm>
/* 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 /* Copyright (C) 2005-2006 Shay Green. This module is free software; you
General Public License as published by the Free Software Foundation; either can redistribute it and/or modify it under the terms of the GNU Lesser
version 2.1 of the License, or (at your option) any later version. This General Public License as published by the Free Software Foundation; either
module is distributed in the hope that it will be useful, but WITHOUT ANY version 2.1 of the License, or (at your option) any later version. This
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS module is distributed in the hope that it will be useful, but WITHOUT ANY
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
details. You should have received a copy of the GNU Lesser General Public FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
License along with this module; if not, write to the Free Software Foundation, details. You should have received a copy of the GNU Lesser General Public
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#include "blargg_source.h"
const char Data_Reader::eof_error [] = "Unexpected end of file";
#ifdef HAVE_ZLIB_H
blargg_err_t Data_Reader::read( void* p, long s ) #include <zlib.h>
{ #include <stdlib.h>
long result = read_avail( p, s ); #include <errno.h>
if ( result != s ) static const unsigned char gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
{ #endif /* HAVE_ZLIB_H */
if ( result >= 0 && result < s )
return eof_error; using std::min;
using std::max;
return "Read error";
} const char Data_Reader::eof_error [] = "Unexpected end of file";
return 0; #define RETURN_VALIDITY_CHECK( cond ) \
} do { if ( unlikely( !(cond) ) ) return "Corrupt file"; } while(0)
blargg_err_t Data_Reader::skip( long count ) blargg_err_t Data_Reader::read( void* p, long s )
{ {
char buf [512]; RETURN_VALIDITY_CHECK( s > 0 );
while ( count )
{ long result = read_avail( p, s );
long n = sizeof buf; if ( result != s )
if ( n > count ) {
n = count; if ( result >= 0 && result < s )
count -= n; return eof_error;
RETURN_ERR( read( buf, n ) );
} return "Read error";
return 0; }
}
return 0;
long File_Reader::remain() const { return size() - tell(); } }
blargg_err_t File_Reader::skip( long n ) blargg_err_t Data_Reader::skip( long count )
{ {
assert( n >= 0 ); RETURN_VALIDITY_CHECK( count >= 0 );
if ( !n )
return 0; char buf [512];
return seek( tell() + n ); while ( count )
} {
long n = sizeof buf;
// Subset_Reader if ( n > count )
n = count;
Subset_Reader::Subset_Reader( Data_Reader* dr, long size ) count -= n;
{ RETURN_ERR( read( buf, n ) );
in = dr; }
remain_ = dr->remain(); return 0;
if ( remain_ > size ) }
remain_ = size;
} long File_Reader::remain() const { return size() - tell(); }
long Subset_Reader::remain() const { return remain_; } blargg_err_t File_Reader::skip( long n )
{
long Subset_Reader::read_avail( void* p, long s ) RETURN_VALIDITY_CHECK( n >= 0 );
{
if ( s > remain_ ) if ( !n )
s = remain_; return 0;
remain_ -= s; return seek( tell() + n );
return in->read_avail( p, s ); }
}
// Subset_Reader
// Remaining_Reader
Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r ) {
{ in = dr;
header = (char const*) h; remain_ = dr->remain();
header_end = header + size; if ( remain_ > size )
in = r; remain_ = max( 0l, size );
} }
long Remaining_Reader::remain() const { return header_end - header + in->remain(); } long Subset_Reader::remain() const { return remain_; }
long Remaining_Reader::read_first( void* out, long count ) long Subset_Reader::read_avail( void* p, long s )
{ {
long first = header_end - header; s = max( 0l, s );
if ( first ) if ( s > remain_ )
{ s = remain_;
if ( first > count ) remain_ -= s;
first = count; return in->read_avail( p, s );
void const* old = header; }
header += first;
memcpy( out, old, first ); // Remaining_Reader
}
return first; Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
} {
header = (char const*) h;
long Remaining_Reader::read_avail( void* out, long count ) header_end = header + max( 0l, size );
{ in = r;
long first = read_first( out, count ); }
long second = count - first;
if ( second ) long Remaining_Reader::remain() const { return header_end - header + in->remain(); }
{
second = in->read_avail( (char*) out + first, second ); long Remaining_Reader::read_first( void* out, long count )
if ( second <= 0 ) {
return second; count = max( 0l, count );
} long first = header_end - header;
return first + second; if ( first )
} {
if ( first > count || first < 0 )
blargg_err_t Remaining_Reader::read( void* out, long count ) first = count;
{ void const* old = header;
long first = read_first( out, count ); header += first;
long second = count - first; memcpy( out, old, (size_t) first );
if ( !second ) }
return 0; return first;
return in->read( (char*) out + first, second ); }
}
long Remaining_Reader::read_avail( void* out, long count )
// Mem_File_Reader {
count = max( 0l, count );
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : long first = read_first( out, count );
begin( (const char*) p ), long second = max( 0l, count - first );
size_( s ) if ( second )
{ {
pos = 0; second = in->read_avail( (char*) out + first, second );
} if ( second <= 0 )
return second;
long Mem_File_Reader::size() const { return size_; } }
return first + second;
long Mem_File_Reader::read_avail( void* p, long s ) }
{
long r = remain(); blargg_err_t Remaining_Reader::read( void* out, long count )
if ( s > r ) {
s = r; count = max( 0l, count );
memcpy( p, begin + pos, s ); long first = read_first( out, count );
pos += s; long second = max( 0l, count - first );
return s; if ( !second )
} return 0;
return in->read( (char*) out + first, second );
long Mem_File_Reader::tell() const { return pos; } }
blargg_err_t Mem_File_Reader::seek( long n ) // Mem_File_Reader
{
if ( n > size_ ) Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
return eof_error; m_begin( (const char*) p ),
pos = n; m_size( max( 0l, s ) ),
return 0; m_pos( 0l )
} {
#ifdef HAVE_ZLIB_H
// Callback_Reader if( !m_begin )
return;
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ), if ( gz_decompress() )
data( d ) {
{ debug_printf( "Loaded compressed data\n" );
remain_ = size; m_ownedPtr = true;
} }
#endif /* HAVE_ZLIB_H */
long Callback_Reader::remain() const { return remain_; } }
long Callback_Reader::read_avail( void* out, long count ) #ifdef HAVE_ZLIB_H
{ Mem_File_Reader::~Mem_File_Reader()
if ( count > remain_ ) {
count = remain_; if ( m_ownedPtr )
if ( Callback_Reader::read( out, count ) ) free( const_cast<char*>( m_begin ) ); // see gz_compress for the malloc
count = -1; }
return count; #endif
}
long Mem_File_Reader::size() const { return m_size; }
blargg_err_t Callback_Reader::read( void* out, long count )
{ long Mem_File_Reader::read_avail( void* p, long s )
if ( count > remain_ ) {
return eof_error; long r = remain();
return callback( data, out, count ); if ( s > r || s < 0 )
} s = r;
memcpy( p, m_begin + m_pos, static_cast<size_t>(s) );
// Std_File_Reader m_pos += s;
return s;
Std_File_Reader::Std_File_Reader() : file_( 0 ) { } }
Std_File_Reader::~Std_File_Reader() { close(); } long Mem_File_Reader::tell() const { return m_pos; }
blargg_err_t Std_File_Reader::open( const char* path ) blargg_err_t Mem_File_Reader::seek( long n )
{ {
file_ = fopen( path, "rb" ); RETURN_VALIDITY_CHECK( n >= 0 );
if ( !file_ ) if ( n > m_size )
return "Couldn't open file"; return eof_error;
return 0; m_pos = n;
} return 0;
}
long Std_File_Reader::size() const
{ #ifdef HAVE_ZLIB_H
long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END ); bool Mem_File_Reader::gz_decompress()
long result = tell(); {
fseek( (FILE*) file_, pos, SEEK_SET ); if ( m_size >= 2 && memcmp(m_begin, gz_magic, 2) != 0 )
return result; {
} /* Don't try to decompress non-GZ files, just assign input pointer */
return false;
long Std_File_Reader::read_avail( void* p, long s ) }
{
return fread( p, 1, s, (FILE*) file_ ); using vec_size = size_t;
} const vec_size full_length = static_cast<vec_size>( m_size );
const vec_size half_length = static_cast<vec_size>( m_size / 2 );
blargg_err_t Std_File_Reader::read( void* p, long s )
{ // We use malloc/friends here so we can realloc to grow buffer if needed
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) ) char *raw_data = reinterpret_cast<char *> ( malloc( full_length ) );
return 0; size_t raw_data_size = full_length;
if ( feof( (FILE*) file_ ) ) if ( !raw_data )
return eof_error; return false;
return "Couldn't read from file";
} z_stream strm;
strm.next_in = const_cast<Bytef *>( reinterpret_cast<const Bytef *>( m_begin ) );
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); } strm.avail_in = static_cast<uInt>( m_size );
strm.total_out = 0;
blargg_err_t Std_File_Reader::seek( long n ) strm.zalloc = Z_NULL;
{ strm.zfree = Z_NULL;
if ( !fseek( (FILE*) file_, n, SEEK_SET ) )
return 0; bool done = false;
if ( n > size() )
return eof_error; // Adding 16 sets bit 4, which enables zlib to auto-detect the
return "Error seeking in file"; // header.
} if ( inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK )
{
void Std_File_Reader::close() free( raw_data );
{ return false;
if ( file_ ) }
{
fclose( (FILE*) file_ ); while ( !done )
file_ = 0; {
} /* If our output buffer is too small */
} if ( strm.total_out >= raw_data_size )
{
// Gzip_File_Reader raw_data_size += half_length;
raw_data = reinterpret_cast<char *>( realloc( raw_data, raw_data_size ) );
#ifdef HAVE_ZLIB_H if ( !raw_data ) {
return false;
#include "zlib.h" }
}
static const char* get_gzip_eof( const char* path, long* eof )
{ strm.next_out = reinterpret_cast<Bytef *>( raw_data + strm.total_out );
FILE* file = fopen( path, "rb" ); strm.avail_out = static_cast<uInt>( static_cast<uLong>( raw_data_size ) - strm.total_out );
if ( !file )
return "Couldn't open file"; /* Inflate another chunk. */
int err = inflate( &strm, Z_SYNC_FLUSH );
unsigned char buf [4]; if ( err == Z_STREAM_END )
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B ) done = true;
{ else if ( err != Z_OK )
fseek( file, -4, SEEK_END ); break;
fread( buf, 4, 1, file ); }
*eof = get_le32( buf );
} if ( inflateEnd(&strm) != Z_OK )
else {
{ free( raw_data );
fseek( file, 0, SEEK_END ); return false;
*eof = ftell( file ); }
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0; m_begin = raw_data;
fclose( file ); m_size = static_cast<long>( strm.total_out );
return err;
} return true;
}
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
#endif /* HAVE_ZLIB_H */
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
blargg_err_t Gzip_File_Reader::open( const char* path ) // Callback_Reader
{
close(); Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ),
RETURN_ERR( get_gzip_eof( path, &size_ ) ); data( d )
{
file_ = gzopen( path, "rb" ); remain_ = max( 0l, size );
if ( !file_ ) }
return "Couldn't open file";
long Callback_Reader::remain() const { return remain_; }
return 0;
} long Callback_Reader::read_avail( void* out, long count )
{
long Gzip_File_Reader::size() const { return size_; } if ( count > remain_ )
count = remain_;
long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); } if ( count < 0 || Callback_Reader::read( out, count ) )
count = -1;
long Gzip_File_Reader::tell() const { return gztell( file_ ); } return count;
}
blargg_err_t Gzip_File_Reader::seek( long n )
{ blargg_err_t Callback_Reader::read( void* out, long count )
if ( gzseek( file_, n, SEEK_SET ) >= 0 ) {
return 0; RETURN_VALIDITY_CHECK( count >= 0 );
if ( n > size_ ) if ( count > remain_ )
return eof_error; return eof_error;
return "Error seeking in file"; return callback( data, out, (int) count );
} }
void Gzip_File_Reader::close() // Std_File_Reader
{
if ( file_ ) #ifdef HAVE_ZLIB_H
{
gzclose( file_ ); static const char* get_gzip_eof( const char* path, long* eof )
file_ = 0; {
} FILE* file = fopen( path, "rb" );
} if ( !file )
return "Couldn't open file";
#endif
unsigned char buf [4];
bool found_eof = false;
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{
fseek( file, -4, SEEK_END );
if ( fread( buf, 4, 1, file ) > 0 ) {
*eof = get_le32( buf );
found_eof = true;
}
}
if ( !found_eof )
{
fseek( file, 0, SEEK_END );
*eof = ftell( file );
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : nullptr;
fclose( file );
return err;
}
#endif
Std_File_Reader::Std_File_Reader() :
file_( nullptr )
#ifdef HAVE_ZLIB_H
, size_( 0 )
#endif
{ }
Std_File_Reader::~Std_File_Reader() { close(); }
blargg_err_t Std_File_Reader::open( const char* path )
{
#ifdef HAVE_ZLIB_H
// zlib transparently handles uncompressed data if magic header
// not present but we still need to grab size
RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" );
#else
file_ = fopen( path, "rb" );
#endif
if ( !file_ )
return "Couldn't open file";
return nullptr;
}
long Std_File_Reader::size() const
{
#ifdef HAVE_ZLIB_H
if ( file_ )
return size_; // Set for both compressed and uncompressed modes
#endif
long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END );
long result = tell();
fseek( (FILE*) file_, pos, SEEK_SET );
return result;
}
long Std_File_Reader::read_avail( void* p, long s )
{
#ifdef HAVE_ZLIB_H
if ( file_ && s > 0 && static_cast<unsigned long>(s) <= UINT_MAX ) {
return gzread( reinterpret_cast<gzFile>(file_),
p, static_cast<unsigned>(s) );
}
return 0l;
#else
const size_t readLength = static_cast<size_t>( max( 0l, s ) );
const auto result = fread( p, 1, readLength, reinterpret_cast<FILE*>(file_) );
return static_cast<long>( result );
#endif /* HAVE_ZLIB_H */
}
blargg_err_t Std_File_Reader::read( void* p, long s )
{
RETURN_VALIDITY_CHECK( s > 0 && static_cast<unsigned long>(s) <= UINT_MAX );
#ifdef HAVE_ZLIB_H
if ( file_ )
{
const auto &gzfile = reinterpret_cast<gzFile>( file_ );
if ( s == gzread( gzfile, p, static_cast<unsigned>( s ) ) )
return nullptr;
if ( gzeof( gzfile ) )
return eof_error;
return "Couldn't read from GZ file";
}
#endif
const auto &file = reinterpret_cast<FILE*>( file_ );
if ( s == static_cast<long>( fread( p, 1, static_cast<size_t>(s), file ) ) )
return 0;
if ( feof( file ) )
return eof_error;
return "Couldn't read from file";
}
long Std_File_Reader::tell() const
{
#ifdef HAVE_ZLIB_H
if ( file_ )
return gztell( reinterpret_cast<gzFile>( file_ ) );
#endif
return ftell( reinterpret_cast<FILE*>( file_ ) );
}
blargg_err_t Std_File_Reader::seek( long n )
{
#ifdef HAVE_ZLIB_H
if ( file_ )
{
if ( gzseek( reinterpret_cast<gzFile>( file_ ), n, SEEK_SET ) >= 0 )
return nullptr;
if ( n > size_ )
return eof_error;
return "Error seeking in GZ file";
}
#endif
if ( !fseek( reinterpret_cast<FILE*>( file_ ), n, SEEK_SET ) )
return nullptr;
if ( n > size() )
return eof_error;
return "Error seeking in file";
}
void Std_File_Reader::close()
{
if ( file_ )
{
#ifdef HAVE_ZLIB_H
gzclose( reinterpret_cast<gzFile>( file_ ) );
#else
fclose( reinterpret_cast<FILE*>( file_ ) );
#endif
file_ = nullptr;
}
}

300
Frameworks/GME/gme/Data_Reader.h Executable file → Normal file
View file

@ -1,151 +1,149 @@
// 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 #ifdef HAVE_ZLIB_H
class Data_Reader { #include <zlib.h>
public: #endif
virtual ~Data_Reader() { }
// Supports reading and finding out how many bytes are remaining
static const char eof_error []; // returned by read() when request goes beyond end class Data_Reader {
public:
// Read at most count bytes and return number actually read, or <= 0 if error virtual ~Data_Reader() { }
virtual long read_avail( void*, long n ) = 0;
static const char eof_error []; // returned by read() when request goes beyond end
// Read exactly count bytes and return error if they couldn't be read
virtual blargg_err_t read( void*, long count ); // Read at most count bytes and return number actually read, or <= 0 if error
virtual long read_avail( void*, long n ) = 0;
// Number of bytes remaining until end of file
virtual long remain() const = 0; // Read exactly count bytes and return error if they couldn't be read
virtual blargg_err_t read( void*, long count );
// Read and discard count bytes
virtual blargg_err_t skip( long count ); // Number of bytes remaining until end of file
virtual long remain() const = 0;
public:
Data_Reader() { } // Read and discard count bytes
typedef blargg_err_t error_t; // deprecated virtual blargg_err_t skip( long count );
private:
// noncopyable public:
Data_Reader( const Data_Reader& ); Data_Reader() { }
Data_Reader& operator = ( const Data_Reader& ); typedef blargg_err_t error_t; // deprecated
}; private:
// noncopyable
// Supports seeking in addition to Data_Reader operations Data_Reader( const Data_Reader& );
class File_Reader : public Data_Reader { Data_Reader& operator = ( const Data_Reader& );
public: };
// Size of file
virtual long size() const = 0; // Supports seeking in addition to Data_Reader operations
class File_Reader : public Data_Reader {
// Current position in file public:
virtual long tell() const = 0; // Size of file
virtual long size() const = 0;
// Go to new position
virtual blargg_err_t seek( long ) = 0; // Current position in file
virtual long tell() const = 0;
long remain() const;
blargg_err_t skip( long n ); // Go to new position
}; virtual blargg_err_t seek( long ) = 0;
// Disk file reader long remain() const;
class Std_File_Reader : public File_Reader { blargg_err_t skip( long n );
public: };
blargg_err_t open( const char* path );
void close(); // Disk file reader
class Std_File_Reader : public File_Reader {
public: public:
Std_File_Reader(); blargg_err_t open( const char* path );
~Std_File_Reader(); void close();
long size() const;
blargg_err_t read( void*, long ); public:
long read_avail( void*, long ); Std_File_Reader();
long tell() const; ~Std_File_Reader();
blargg_err_t seek( long ); long size() const;
private: blargg_err_t read( void*, long );
void* file_; long read_avail( void*, long );
}; long tell() const;
blargg_err_t seek( long );
// Treats range of memory as a file private:
class Mem_File_Reader : public File_Reader { void* file_; // Either FILE* or zlib's gzFile
public: #ifdef HAVE_ZLIB_H
Mem_File_Reader( const void*, long size ); long size_; // TODO: Fix ABI compat
#endif /* HAVE_ZLIB_H */
public: };
long size() const;
long read_avail( void*, long ); // Treats range of memory as a file
long tell() const; class Mem_File_Reader : public File_Reader {
blargg_err_t seek( long ); public:
private: Mem_File_Reader( const void*, long size );
const char* const begin; #ifdef HAVE_ZLIB_H
const long size_; ~Mem_File_Reader( );
long pos; #endif /* HAVE_ZLIB_H */
};
public:
// Makes it look like there are only count bytes remaining long size() const;
class Subset_Reader : public Data_Reader { long read_avail( void*, long );
public: long tell() const;
Subset_Reader( Data_Reader*, long count ); blargg_err_t seek( long );
private:
public: #ifdef HAVE_ZLIB_H
long remain() const; bool gz_decompress();
long read_avail( void*, long ); #endif /* HAVE_ZLIB_H */
private:
Data_Reader* in; const char* m_begin;
long remain_; long m_size;
}; long m_pos;
#ifdef HAVE_ZLIB_H
// Joins already-read header and remaining data into original file (to avoid seeking) bool m_ownedPtr = false; // set if we must free m_begin
class Remaining_Reader : public Data_Reader { #endif /* HAVE_ZLIB_H */
public: };
Remaining_Reader( void const* header, long size, Data_Reader* );
public: // Makes it look like there are only count bytes remaining
long remain() const; class Subset_Reader : public Data_Reader {
long read_avail( void*, long ); public:
blargg_err_t read( void*, long ); Subset_Reader( Data_Reader*, long count );
private:
char const* header; public:
char const* header_end; long remain() const;
Data_Reader* in; long read_avail( void*, long );
long read_first( void* out, long count ); private:
}; Data_Reader* in;
long remain_;
// Invokes callback function to read data. Size of data must be specified in advance. };
class Callback_Reader : public Data_Reader {
public: // Joins already-read header and remaining data into original file (to avoid seeking)
typedef const char* (*callback_t)( void* data, void* out, long count ); class Remaining_Reader : public Data_Reader {
Callback_Reader( callback_t, long size, void* data = 0 ); public:
public: Remaining_Reader( void const* header, long size, Data_Reader* );
long read_avail( void*, long );
blargg_err_t read( void*, long ); public:
long remain() const; long remain() const;
private: long read_avail( void*, long );
callback_t const callback; blargg_err_t read( void*, long );
void* const data; private:
long remain_; char const* header;
}; char const* header_end;
Data_Reader* in;
#ifdef HAVE_ZLIB_H long read_first( void* out, long count );
// Gzip compressed file reader };
class Gzip_File_Reader : public File_Reader {
public: // Invokes callback function to read data. Size of data must be specified in advance.
blargg_err_t open( const char* path ); class Callback_Reader : public Data_Reader {
void close(); public:
typedef const char* (*callback_t)( void* data, void* out, int count );
public: Callback_Reader( callback_t, long size, void* data = 0 );
Gzip_File_Reader(); public:
~Gzip_File_Reader(); long read_avail( void*, long );
long size() const; blargg_err_t read( void*, long );
long read_avail( void*, long ); long remain() const;
long tell() const; private:
blargg_err_t seek( long ); callback_t const callback;
private: void* const data;
void* file_; long remain_;
long size_; };
};
#endif #endif
#endif

View file

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

View file

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

View file

@ -1,315 +1,139 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Dual_Resampler.h" #include "Dual_Resampler.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include <stdlib.h>
can redistribute it and/or modify it under the terms of the GNU Lesser #include <string.h>
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 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
module is distributed in the hope that it will be useful, but WITHOUT ANY can redistribute it and/or modify it under the terms of the GNU Lesser
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS General Public License as published by the Free Software Foundation; either
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more version 2.1 of the License, or (at your option) any later version. This
details. You should have received a copy of the GNU Lesser General Public module is distributed in the hope that it will be useful, but WITHOUT ANY
License along with this module; if not, write to the Free Software Foundation, WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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
#include "blargg_source.h" License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
// TODO: fix this. hack since resampler holds back some output.
int const resampler_extra = 34; #include "blargg_source.h"
int const stereo = 2; Dual_Resampler::Dual_Resampler() :
sample_buf_size(0),
Dual_Resampler::Dual_Resampler() { } oversamples_per_frame(-1),
buf_pos(-1),
Dual_Resampler::~Dual_Resampler() { } resampler_size(0)
{
blargg_err_t Dual_Resampler::reset( int pairs ) }
{
// expand allocations a bit Dual_Resampler::~Dual_Resampler() { }
RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
resize( pairs ); blargg_err_t Dual_Resampler::reset( int pairs )
resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2); {
RETURN_ERR( resampler.resize_buffer( resampler_size ) ); // expand allocations a bit
resampler.clear(); RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
return blargg_ok; resize( pairs );
} resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2);
return resampler.buffer_size( resampler_size );
void Dual_Resampler::resize( int pairs ) }
{
int new_sample_buf_size = pairs * 2; void Dual_Resampler::resize( int pairs )
//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 ) int new_sample_buf_size = pairs * 2;
{ if ( sample_buf_size != new_sample_buf_size )
if ( (unsigned) new_sample_buf_size > sample_buf.size() ) {
{ if ( (unsigned) new_sample_buf_size > sample_buf.size() )
check( false ); {
return; check( false );
} return;
sample_buf_size = new_sample_buf_size; }
oversamples_per_frame = int (pairs * resampler.rate()) * 2 + 2; sample_buf_size = new_sample_buf_size;
clear(); oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2;
} clear();
} }
}
void Dual_Resampler::clear()
{ void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, dsample_t* out )
buf_pos = buffered = 0; {
resampler.clear(); long pair_count = sample_buf_size >> 1;
} blip_time_t blip_time = blip_buf.count_clocks( pair_count );
int sample_count = oversamples_per_frame - resampler.written();
int Dual_Resampler::play_frame_( Stereo_Buffer& stereo_buf, dsample_t out [], Stereo_Buffer** secondary_buf_set, int secondary_buf_set_count ) int new_count = play_frame( blip_time, sample_count, resampler.buffer() );
{ assert( new_count < resampler_size );
int pair_count = sample_buf_size >> 1;
blip_time_t blip_time = stereo_buf.center()->count_clocks( pair_count ); blip_buf.end_frame( blip_time );
int sample_count = oversamples_per_frame - resampler.written() + resampler_extra; assert( blip_buf.samples_avail() == pair_count );
int new_count = set_callback.f( set_callback.data, blip_time, sample_count, resampler.buffer() ); resampler.write( new_count );
assert( new_count < resampler_size );
#ifdef NDEBUG // Avoid warning when asserts are disabled
stereo_buf.end_frame( blip_time ); resampler.read( sample_buf.begin(), sample_buf_size );
assert( stereo_buf.samples_avail() == pair_count * 2 ); #else
if ( secondary_buf_set && secondary_buf_set_count ) long count = resampler.read( sample_buf.begin(), sample_buf_size );
{ assert( count == (long) sample_buf_size );
for ( int i = 0; i < secondary_buf_set_count; i++ ) #endif
{
Stereo_Buffer * second_buf = secondary_buf_set[i]; mix_samples( blip_buf, out );
blip_time_t blip_time_2 = second_buf->center()->count_clocks( pair_count ); blip_buf.remove_samples( pair_count );
second_buf->end_frame( blip_time_2 ); }
assert( second_buf->samples_avail() == pair_count * 2 );
} void Dual_Resampler::dual_play( long count, dsample_t* out, Blip_Buffer& blip_buf )
} {
// empty extra buffer
resampler.write( new_count ); long remain = sample_buf_size - buf_pos;
if ( remain )
int count = resampler.read( sample_buf.begin(), sample_buf_size ); {
if ( remain > count )
mix_samples( stereo_buf, out, count, secondary_buf_set, secondary_buf_set_count ); remain = count;
count -= remain;
pair_count = count >> 1; memcpy( out, &sample_buf [buf_pos], remain * sizeof *out );
stereo_buf.left()->remove_samples( pair_count ); out += remain;
stereo_buf.right()->remove_samples( pair_count ); buf_pos += remain;
stereo_buf.center()->remove_samples( pair_count ); }
if ( secondary_buf_set && secondary_buf_set_count ) // entire frames
{ while ( count >= (long) sample_buf_size )
for ( int i = 0; i < secondary_buf_set_count; i++ ) {
{ play_frame_( blip_buf, out );
Stereo_Buffer * second_buf = secondary_buf_set[i]; out += sample_buf_size;
second_buf->left()->remove_samples( pair_count ); count -= sample_buf_size;
second_buf->right()->remove_samples( pair_count ); }
second_buf->center()->remove_samples( pair_count );
} // extra
} if ( count )
{
return count; play_frame_( blip_buf, sample_buf.begin() );
} buf_pos = count;
memcpy( out, sample_buf.begin(), count * sizeof *out );
void Dual_Resampler::dual_play( int count, dsample_t out [], Stereo_Buffer& stereo_buf, Stereo_Buffer** secondary_buf_set, int secondary_buf_set_count ) out += count;
{ }
// empty extra buffer }
int remain = buffered - buf_pos;
if ( remain ) void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, dsample_t* out )
{ {
if ( remain > count ) Blip_Reader sn;
remain = count; int bass = sn.begin( blip_buf );
count -= remain; const dsample_t* in = sample_buf.begin();
memcpy( out, &sample_buf [buf_pos], remain * sizeof *out );
out += remain; for ( int n = sample_buf_size >> 1; n--; )
buf_pos += remain; {
} int s = sn.read();
blargg_long l = (blargg_long) in [0] * 2 + s;
// entire frames if ( (int16_t) l != l )
while ( count >= sample_buf_size ) l = 0x7FFF - (l >> 24);
{
buf_pos = buffered = play_frame_( stereo_buf, out, secondary_buf_set, secondary_buf_set_count ); sn.next( bass );
out += buffered; blargg_long r = (blargg_long) in [1] * 2 + s;
count -= buffered; if ( (int16_t) r != r )
} r = 0x7FFF - (r >> 24);
while (count > 0) in += 2;
{ out [0] = l;
buffered = play_frame_( stereo_buf, sample_buf.begin(), secondary_buf_set, secondary_buf_set_count ); out [1] = r;
if ( buffered >= count ) out += 2;
{ }
buf_pos = count;
memcpy( out, sample_buf.begin(), count * sizeof *out ); sn.end( blip_buf );
out += count; }
count = 0;
}
else
{
memcpy( out, sample_buf.begin(), buffered * sizeof *out );
out += buffered;
count -= buffered;
}
}
}
void Dual_Resampler::mix_samples( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count, Stereo_Buffer** secondary_buf_set, int secondary_buf_set_count )
{
// lol hax
if ( ((Tracked_Blip_Buffer*)stereo_buf.left())->non_silent() | ((Tracked_Blip_Buffer*)stereo_buf.right())->non_silent() )
mix_stereo( stereo_buf, out_, count );
else
mix_mono( stereo_buf, out_, count );
if ( secondary_buf_set && secondary_buf_set_count )
{
for ( int i = 0; i < secondary_buf_set_count; i++ )
{
Stereo_Buffer * second_buf = secondary_buf_set[i];
if ( ((Tracked_Blip_Buffer*)second_buf->left())->non_silent() | ((Tracked_Blip_Buffer*)second_buf->right())->non_silent() )
mix_extra_stereo( *second_buf, out_, count );
else
mix_extra_mono( *second_buf, out_, count );
}
}
}
void Dual_Resampler::mix_mono( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( sn, *stereo_buf.center() );
count >>= 1;
BLIP_READER_ADJ_( sn, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
stereo_dsample_t const* BLARGG_RESTRICT in =
(stereo_dsample_t const*) sample_buf.begin() + count;
int offset = -count;
int const gain = gain_;
do
{
int s = BLIP_READER_READ_RAW( sn ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( sn, bass, offset );
int l = (in [offset] [0] * gain >> gain_bits) + s;
int r = (in [offset] [1] * gain >> gain_bits) + s;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( sn, *stereo_buf.center() );
}
void Dual_Resampler::mix_stereo( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( snc, *stereo_buf.center() );
BLIP_READER_BEGIN( snl, *stereo_buf.left() );
BLIP_READER_BEGIN( snr, *stereo_buf.right() );
count >>= 1;
BLIP_READER_ADJ_( snc, count );
BLIP_READER_ADJ_( snl, count );
BLIP_READER_ADJ_( snr, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
stereo_dsample_t const* BLARGG_RESTRICT in =
(stereo_dsample_t const*) sample_buf.begin() + count;
int offset = -count;
int const gain = gain_;
do
{
int sc = BLIP_READER_READ_RAW( snc ) >> (blip_sample_bits - 16);
int sl = BLIP_READER_READ_RAW( snl ) >> (blip_sample_bits - 16);
int sr = BLIP_READER_READ_RAW( snr ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( snc, bass, offset );
BLIP_READER_NEXT_IDX_( snl, bass, offset );
BLIP_READER_NEXT_IDX_( snr, bass, offset );
int l = (in [offset] [0] * gain >> gain_bits) + sl + sc;
int r = (in [offset] [1] * gain >> gain_bits) + sr + sc;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( snc, *stereo_buf.center() );
BLIP_READER_END( snl, *stereo_buf.left() );
BLIP_READER_END( snr, *stereo_buf.right() );
}
void Dual_Resampler::mix_extra_mono( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( sn, *stereo_buf.center() );
count >>= 1;
BLIP_READER_ADJ_( sn, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
int offset = -count;
do
{
int s = BLIP_READER_READ_RAW( sn ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( sn, bass, offset );
int l = out [offset] [0] + s;
int r = out [offset] [1] + s;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( sn, *stereo_buf.center() );
}
void Dual_Resampler::mix_extra_stereo( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( snc, *stereo_buf.center() );
BLIP_READER_BEGIN( snl, *stereo_buf.left() );
BLIP_READER_BEGIN( snr, *stereo_buf.right() );
count >>= 1;
BLIP_READER_ADJ_( snc, count );
BLIP_READER_ADJ_( snl, count );
BLIP_READER_ADJ_( snr, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
int offset = -count;
do
{
int sc = BLIP_READER_READ_RAW( snc ) >> (blip_sample_bits - 16);
int sl = BLIP_READER_READ_RAW( snl ) >> (blip_sample_bits - 16);
int sr = BLIP_READER_READ_RAW( snr ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( snc, bass, offset );
BLIP_READER_NEXT_IDX_( snl, bass, offset );
BLIP_READER_NEXT_IDX_( snr, bass, offset );
int l = out [offset] [0] + sl + sc;
int r = out [offset] [1] + sr + sc;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( snc, *stereo_buf.center() );
BLIP_READER_END( snl, *stereo_buf.left() );
BLIP_READER_END( snr, *stereo_buf.right() );
}

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,149 +1,90 @@
// Multi-channel effects buffer with echo and individual panning for each channel // Multi-channel effects buffer with panning, echo and reverb
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 #include <vector>
class Effects_Buffer : public Multi_Buffer { // Effects_Buffer uses several buffers and outputs stereo sample pairs.
public: class Effects_Buffer : public Multi_Buffer {
// To reduce memory usage, fewer buffers can be used (with a best-fit public:
// approach if there are too few), and maximum echo delay can be reduced // nVoices indicates the number of voices for which buffers will be allocated
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 ); // to make Effects_Buffer work as "mix everything to one", nVoices will be 1
// If center_only is true, only center buffers are created and
struct pan_vol_t // less memory is used.
{ Effects_Buffer( int nVoices = 1, bool center_only = false );
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal
float pan; // -1.0 = left, 0.0 = center, +1.0 = right // Channel Effect Center Pan
}; // ---------------------------------
// 0,5 reverb pan_1
// Global configuration // 1,6 reverb pan_2
struct config_t // 2,7 echo -
{ // 3 echo -
bool enabled; // false = disable all effects // 4 echo -
// Current sound is echoed at adjustable left/right delay, // Channel configuration
// with reduced treble and volume (feedback). struct config_t {
float treble; // 1.0 = full treble, 0.1 = very little, 0.0 = silent double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right
int delay [2]; // left, right delays (msec) double pan_2;
float feedback; // 0.0 = no echo, 0.5 = each echo half previous, 1.0 = cacophony double echo_delay; // msec
pan_vol_t side_chans [2]; // left and right side channel volume and pan double echo_level; // 0.0 to 1.0
}; double reverb_delay; // msec
config_t& config() { return config_; } double delay_variance; // difference between left/right delays (msec)
double reverb_level; // 0.0 to 1.0
// Limits of delay (msec) bool effects_enabled; // if false, use optimized simple mixer
int min_delay() const; config_t();
int max_delay() const; };
// Per-channel configuration. Two or more channels with matching parameters are // Set configuration of buffer
// optimized to internally use the same buffer. virtual void config( const config_t& );
struct chan_config_t : pan_vol_t void set_depth( double );
{
// (inherited from pan_vol_t) public:
//float vol; // these only affect center channel ~Effects_Buffer();
//float pan; blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length );
bool surround; // if true, negates left volume to put sound in back void clock_rate( long );
bool echo; // false = channel doesn't have any echo void bass_freq( int );
}; void clear();
chan_config_t& chan_config( int i ) { return chans [i + extra_chans].cfg; } channel_t channel( int, int );
void end_frame( blip_time_t );
// Applies any changes made to config() and chan_config() long read_samples( blip_sample_t*, long );
virtual void apply_config(); long samples_avail() const;
private:
// Implementation typedef long fixed_t;
public: int max_voices;
~Effects_Buffer(); enum { max_buf_count = 7 };
blargg_err_t set_sample_rate( int samples_per_sec, int msec = blip_default_length ); std::vector<Blip_Buffer> bufs;
blargg_err_t set_channel_count( int, int const* = NULL ); enum { chan_types_count = 3 };
void clock_rate( int ); std::vector<channel_t> chan_types;
void bass_freq( int ); config_t config_;
void clear(); long stereo_remain;
channel_t channel( int ); long effect_remain;
void end_frame( blip_time_t ); int buf_count;
int read_samples( blip_sample_t [], int ); bool effects_enabled;
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; }
enum { stereo = 2 }; std::vector<std::vector<blip_sample_t> > reverb_buf;
typedef int fixed_t; std::vector<std::vector<blip_sample_t> > echo_buf;
std::vector<int> reverb_pos;
protected: std::vector<int> echo_pos;
enum { extra_chans = stereo * stereo };
struct {
private: fixed_t pan_1_levels [2];
config_t config_; fixed_t pan_2_levels [2];
int clock_rate_; int echo_delay_l;
int bass_freq_; int echo_delay_r;
fixed_t echo_level;
int echo_size; int reverb_delay_l;
int reverb_delay_r;
struct chan_t fixed_t reverb_level;
{ } chans;
fixed_t vol [stereo];
chan_config_t cfg; void mix_mono( blip_sample_t*, blargg_long );
channel_t channel; void mix_stereo( blip_sample_t*, blargg_long );
}; void mix_enhanced( blip_sample_t*, blargg_long );
blargg_vector<chan_t> chans; void mix_mono_enhanced( blip_sample_t*, blargg_long );
};
struct buf_t : Tracked_Blip_Buffer
{ #endif
// nasty: Blip_Buffer has something called fixed_t
Effects_Buffer::fixed_t vol [stereo];
bool echo;
void* operator new ( size_t, void* p ) { return p; }
void operator delete ( void* ) { }
~buf_t() { }
};
buf_t* bufs;
int bufs_size;
int bufs_max; // bufs_size <= bufs_max, to limit memory usage
Stereo_Mixer mixer;
struct {
int delay [stereo];
fixed_t treble;
fixed_t feedback;
fixed_t low_pass [stereo];
} s;
blargg_vector<fixed_t> echo;
int echo_pos;
bool no_effects;
bool no_echo;
void assign_buffers();
void clear_echo();
void mix_effects( blip_sample_t out [], int pair_count );
blargg_err_t new_bufs( int size );
void delete_bufs();
};
// Simpler interface and lower memory usage
class Simple_Effects_Buffer : public Effects_Buffer {
public:
struct config_t
{
bool enabled; // false = disable all effects
float echo; // 0.0 = none, 1.0 = lots
float stereo; // 0.0 = channels in center, 1.0 = channels on left/right
bool surround; // true = put some channels in back
};
config_t& config() { return config_; }
// Applies any changes made to config()
void apply_config();
// Implementation
public:
Simple_Effects_Buffer();
private:
config_t config_;
void chan_config(); // hide
};
#endif

View file

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

View file

@ -1,101 +1,186 @@
// Finite impulse response (FIR) resampler with adjustable FIR size // Finite impulse response (FIR) resampler with adjustable FIR size
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef FIR_RESAMPLER_H #ifndef FIR_RESAMPLER_H
#define FIR_RESAMPLER_H #define FIR_RESAMPLER_H
#include "Resampler.h" #include "blargg_common.h"
#include <string.h>
template<int width>
class Fir_Resampler; class Fir_Resampler_ {
public:
// Use one of these typedefs
typedef Fir_Resampler< 8> Fir_Resampler_Fast; // Use Fir_Resampler<width> (below)
typedef Fir_Resampler<16> Fir_Resampler_Norm;
typedef Fir_Resampler<24> Fir_Resampler_Good; // Set input/output resampling ratio and optionally low-pass rolloff and gain.
// Returns actual ratio used (rounded to internal precision).
// Implementation double time_ratio( double factor, double rolloff = 0.999, double gain = 1.0 );
class Fir_Resampler_ : public Resampler {
protected: // Current input/output ratio
virtual blargg_err_t set_rate_( double ); double ratio() const { return ratio_; }
virtual void clear_();
// Input
protected:
enum { stereo = 2 }; typedef short sample_t;
enum { max_res = 32 }; // TODO: eliminate and keep impulses on freestore?
sample_t const* imp; // Resize and clear input buffer
int const width_; blargg_err_t buffer_size( int );
sample_t* impulses;
// Clear input buffer. At least two output samples will be available after
Fir_Resampler_( int width, sample_t [] ); // two input samples are written.
}; void clear();
// Width is number of points in FIR. More points give better quality and // Number of input samples that can be written
// rolloff effectiveness, and take longer to calculate. int max_write() const { return buf.end() - write_pos; }
template<int width>
class Fir_Resampler : public Fir_Resampler_ { // Pointer to place to write input samples
enum { min_width = (width < 4 ? 4 : width) }; sample_t* buffer() { return write_pos; }
enum { adj_width = min_width / 4 * 4 + 2 };
enum { write_offset = adj_width * stereo }; // Notify resampler that 'count' input samples have been written
short impulses [max_res * (adj_width + 2)]; void write( long count );
public:
Fir_Resampler() : Fir_Resampler_( adj_width, impulses ) { } // Number of input samples in buffer
int written() const { return write_pos - &buf [write_offset]; }
protected:
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int ); // Skip 'count' input samples. Returns number of samples actually skipped.
}; int skip_input( long count );
template<int width> // Output
Resampler::sample_t const* Fir_Resampler<width>::resample_( sample_t** out_,
sample_t const* out_end, sample_t const in [], int in_size ) // Number of extra input samples needed until 'count' output samples are available
{ int input_needed( blargg_long count ) const;
in_size -= write_offset;
if ( in_size > 0 ) // Number of output samples available
{ int avail() const { return avail_( write_pos - &buf [width_ * stereo] ); }
sample_t* BLARGG_RESTRICT out = *out_;
sample_t const* const in_end = in + in_size; public:
sample_t const* imp = this->imp; ~Fir_Resampler_();
protected:
do enum { stereo = 2 };
{ enum { max_res = 32 };
// accumulate in extended precision blargg_vector<sample_t> buf;
int pt = imp [0]; sample_t* write_pos;
int l = pt * in [0]; int res;
int r = pt * in [1]; int imp_phase;
if ( out >= out_end ) int const width_;
break; int const write_offset;
for ( int n = (adj_width - 2) / 2; n; --n ) blargg_ulong skip_bits;
{ int step;
pt = imp [1]; int input_per_cycle;
l += pt * in [2]; double ratio_;
r += pt * in [3]; sample_t* impulses;
// pre-increment more efficient on some RISC processors Fir_Resampler_( int width, sample_t* );
imp += 2; int avail_( blargg_long input_count ) const;
pt = imp [0]; };
r += pt * in [5];
in += 4; // Width is number of points in FIR. Must be even and 4 or more. More points give
l += pt * in [0]; // better quality and rolloff effectiveness, and take longer to calculate.
} template<int width>
pt = imp [1]; class Fir_Resampler : public Fir_Resampler_ {
l += pt * in [2]; static_assert( width >= 4 && width % 2 == 0, "FIR width must be even and have 4 or more points" );
r += pt * in [3]; short impulses [max_res] [width];
public:
// these two "samples" after the end of the impulse give the Fir_Resampler() : Fir_Resampler_( width, impulses [0] ) { }
// proper offsets to the next input sample and next impulse
in = (sample_t const*) ((char const*) in + imp [2]); // some negative value // Read at most 'count' samples. Returns number of samples actually read.
imp = (sample_t const*) ((char const*) imp + imp [3]); // small positive or large negative typedef short sample_t;
int read( sample_t* out, blargg_long count );
out [0] = sample_t (l >> 15); };
out [1] = sample_t (r >> 15);
out += 2; // End of public interface
}
while ( in < in_end ); inline void Fir_Resampler_::write( long count )
{
this->imp = imp; write_pos += count;
*out_ = out; assert( write_pos <= buf.end() );
} }
return in;
} template<int width>
int Fir_Resampler<width>::read( sample_t* out_begin, blargg_long count )
#endif {
sample_t* out = out_begin;
const sample_t* in = buf.begin();
sample_t* end_pos = write_pos;
blargg_ulong skip = skip_bits >> imp_phase;
sample_t const* imp = impulses [imp_phase];
int remain = res - imp_phase;
int const step = this->step;
count >>= 1;
// Resampling can add noise so don't actually do it if we've matched sample
// rate
const double ratio1 = ratio() - 1.0;
const bool should_resample =
( ratio1 >= 0 ? ratio1 : -ratio1 ) >= 0.00001;
if ( end_pos - in >= width * stereo )
{
end_pos -= width * stereo;
do
{
count--;
if ( count < 0 )
break;
if( !should_resample )
{
out [0] = static_cast<sample_t>( in [0] );
out [1] = static_cast<sample_t>( in [1] );
}
else
{
// accumulate in extended precision
blargg_long l = 0;
blargg_long r = 0;
const sample_t* i = in;
for ( int n = width / 2; n; --n )
{
int pt0 = imp [0];
l += pt0 * i [0];
r += pt0 * i [1];
int pt1 = imp [1];
imp += 2;
l += pt1 * i [2];
r += pt1 * i [3];
i += 4;
}
remain--;
l >>= 15;
r >>= 15;
in += (skip * stereo) & stereo;
skip >>= 1;
if ( !remain )
{
imp = impulses [0];
skip = skip_bits;
remain = res;
}
out [0] = (sample_t) l;
out [1] = (sample_t) r;
}
in += step;
out += 2;
}
while ( in <= end_pos );
}
imp_phase = res - remain;
int left = write_pos - in;
write_pos = &buf [left];
memmove( buf.begin(), in, left * sizeof *in );
return out - out_begin;
}
#endif

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,82 +1,91 @@
// Nintendo Game Boy CPU emulator // Nintendo Game Boy CPU emulator
// Treats every instruction as taking 4 cycles
// Game_Music_Emu $vers
#ifndef GB_CPU_H // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#define GB_CPU_H #ifndef GB_CPU_H
#define GB_CPU_H
#include "blargg_common.h"
#include "blargg_common.h"
class Gb_Cpu { #include "blargg_endian.h"
public:
typedef int addr_t; typedef unsigned gb_addr_t; // 16-bit CPU address
typedef BOOST::uint8_t byte;
class Gb_Cpu {
enum { mem_size = 0x10000 }; enum { clocks_per_instr = 4 };
public:
// Clears registers and map all pages to unmapped // Clear registers and map all pages to unmapped
void reset( void* unmapped = NULL ); void reset( void* unmapped = 0 );
// Maps code memory (memory accessed via the program counter). Start and size // Map 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_size = 0x2000 };
enum { page_size = 1 << page_bits }; void map_code( gb_addr_t start, unsigned size, void* code );
void map_code( addr_t start, int size, void* code );
uint8_t* get_code( gb_addr_t );
// Accesses emulated memory as CPU does
byte* get_code( addr_t ); // Push a byte on the stack
void push_byte( int );
// Game Boy Z-80 registers. NOT kept updated during emulation.
struct core_regs_t { // Game Boy Z80 registers. *Not* kept updated during a call to run().
BOOST::uint16_t bc, de, hl, fa; struct core_regs_t {
}; #if BLARGG_BIG_ENDIAN
uint8_t b, c, d, e, h, l, flags, a;
struct registers_t : core_regs_t { #else
int pc; // more than 16 bits to allow overflow detection uint8_t c, b, e, d, l, h, a, flags;
BOOST::uint16_t sp; #endif
}; };
registers_t r;
struct registers_t : core_regs_t {
// Base address for RST vectors, to simplify GBS player (normally 0) long pc; // more than 16 bits to allow overflow detection
addr_t rst_base; uint16_t sp;
};
// Current time. registers_t r;
int time() const { return cpu_state->time; }
// Interrupt enable flag set by EI and cleared by DI
// Changes time. Must not be called during emulation. //bool interrupts_enabled; // unused
// Should be negative, because emulation stops once it becomes >= 0.
void set_time( int t ) { cpu_state->time = t; } // Base address for RST vectors (normally 0)
gb_addr_t rst_base;
// Emulator reads this many bytes past end of a page
enum { cpu_padding = 8 }; // If CPU executes opcode 0xFF at this address, it treats as illegal instruction
enum { idle_addr = 0xF00D };
// Implementation // Run CPU for at least 'count' cycles and return false, or return true if
public: // illegal instruction is encountered.
Gb_Cpu() : rst_base( 0 ) { cpu_state = &cpu_state_; } bool run( blargg_long count );
enum { page_count = mem_size >> page_bits };
// Number of clock cycles remaining for most recent run() call
struct cpu_state_t { blargg_long remain() const { return state->remain * clocks_per_instr; }
byte* code_map [page_count + 1];
int time; // Can read this many bytes past end of a page
}; enum { cpu_padding = 8 };
cpu_state_t* cpu_state; // points to state_ or a local copy within run()
cpu_state_t cpu_state_; public:
Gb_Cpu() : rst_base( 0 ) { state = &state_; }
private: enum { page_shift = 13 };
void set_code_page( int, void* ); enum { page_count = 0x10000 >> page_shift };
}; private:
// noncopyable
#define GB_CPU_PAGE( addr ) ((unsigned) (addr) >> Gb_Cpu::page_bits) Gb_Cpu( const Gb_Cpu& );
Gb_Cpu& operator = ( const Gb_Cpu& );
#if BLARGG_NONPORTABLE
#define GB_CPU_OFFSET( addr ) (addr) struct state_t {
#else uint8_t* code_map [page_count + 1];
#define GB_CPU_OFFSET( addr ) ((addr) & (Gb_Cpu::page_size - 1)) blargg_long remain;
#endif };
state_t* state; // points to state_ or a local copy within run()
inline BOOST::uint8_t* Gb_Cpu::get_code( addr_t addr ) state_t state_;
{
return cpu_state_.code_map [GB_CPU_PAGE( addr )] + GB_CPU_OFFSET( addr ); void set_code_page( int, uint8_t* );
} };
#endif inline uint8_t* Gb_Cpu::get_code( gb_addr_t addr )
{
return state->code_map [addr >> page_shift] + addr
#if !BLARGG_NONPORTABLE
% (unsigned) page_size
#endif
;
}
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -1,167 +1,293 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Gbs_Emu.h" #include "Gbs_Emu.h"
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you #include "blargg_endian.h"
can redistribute it and/or modify it under the terms of the GNU Lesser #include <string.h>
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 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
module is distributed in the hope that it will be useful, but WITHOUT ANY can redistribute it and/or modify it under the terms of the GNU Lesser
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS General Public License as published by the Free Software Foundation; either
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more version 2.1 of the License, or (at your option) any later version. This
details. You should have received a copy of the GNU Lesser General Public module is distributed in the hope that it will be useful, but WITHOUT ANY
License along with this module; if not, write to the Free Software Foundation, WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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
#include "blargg_source.h" License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
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 }; #include "blargg_source.h"
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::handheld_eq =
Gbs_Emu::Gbs_Emu() Music_Emu::make_equalizer( -47.0, 2000 );
{ Gbs_Emu::equalizer_t const Gbs_Emu::headphones_eq =
sound_hardware = sound_gbs; Music_Emu::make_equalizer( 0.0, 300 );
enable_clicking( false );
set_type( gme_gbs_type ); Gbs_Emu::Gbs_Emu()
set_silence_lookahead( 6 ); {
set_max_initial_silence( 21 ); set_type( gme_gbs_type );
set_gain( 1.2 );
static const char* const names [Gb_Apu::osc_count] = {
// kind of midway between headphones and speaker "Square 1", "Square 2", "Wave", "Noise"
static equalizer_t const eq = { -1.0, 120, 0,0,0,0,0,0,0,0 }; };
set_equalizer( eq ); set_voice_names( names );
}
static int const types [Gb_Apu::osc_count] = {
Gbs_Emu::~Gbs_Emu() { } wave_type | 1, wave_type | 2, wave_type | 0, mixed_type | 0
};
void Gbs_Emu::unload() set_voice_types( types );
{
core_.unload(); set_silence_lookahead( 6 );
Music_Emu::unload(); set_max_initial_silence( 21 );
} set_gain( 1.2 );
// Track info set_equalizer( make_equalizer( -1.0, 120 ) );
}
static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
{ Gbs_Emu::~Gbs_Emu() { }
GME_COPY_FIELD( h, out, game );
GME_COPY_FIELD( h, out, author ); void Gbs_Emu::unload()
GME_COPY_FIELD( h, out, copyright ); {
} rom.clear();
Music_Emu::unload();
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) ); // Track info
out.hash_( &h.track_count, sizeof(h.track_count) );
out.hash_( &h.first_track, sizeof(h.first_track) ); static void copy_gbs_fields( Gbs_Emu::header_t const& h, track_info_t* out )
out.hash_( &h.load_addr[0], sizeof(h.load_addr) ); {
out.hash_( &h.init_addr[0], sizeof(h.init_addr) ); GME_COPY_FIELD( h, out, game );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) ); GME_COPY_FIELD( h, out, author );
out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) ); GME_COPY_FIELD( h, out, copyright );
out.hash_( &h.timer_modulo, sizeof(h.timer_modulo) ); }
out.hash_( &h.timer_mode, sizeof(h.timer_mode) );
out.hash_( data, data_size ); blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const
} {
copy_gbs_fields( header_, out );
blargg_err_t Gbs_Emu::track_info_( track_info_t* out, int ) const return 0;
{ }
copy_gbs_fields( header(), out );
return blargg_ok; static blargg_err_t check_gbs_header( void const* header )
} {
if ( memcmp( header, "GBS", 3 ) )
struct Gbs_File : Gme_Info_ return gme_wrong_file_type;
{ return 0;
Gbs_Emu::header_t const* h; }
Gbs_File() { set_type( gme_gbs_type ); } struct Gbs_File : Gme_Info_
{
blargg_err_t load_mem_( byte const begin [], int size ) Gbs_Emu::header_t h;
{
h = ( Gbs_Emu::header_t * ) begin; Gbs_File() { set_type( gme_gbs_type ); }
set_track_count( h->track_count ); blargg_err_t load_( Data_Reader& in )
if ( !h->valid_tag() ) {
return blargg_err_file_type; blargg_err_t err = in.read( &h, Gbs_Emu::header_size );
if ( err )
return blargg_ok; return (err == in.eof_error ? gme_wrong_file_type : err);
}
set_track_count( h.track_count );
blargg_err_t track_info_( track_info_t* out, int ) const return check_gbs_header( &h );
{ }
copy_gbs_fields( Gbs_Emu::header_t( *h ), out );
return blargg_ok; blargg_err_t track_info_( track_info_t* out, int ) const
} {
copy_gbs_fields( h, out );
blargg_err_t hash_( Hash_Function& out ) const return 0;
{ }
hash_gbs_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out ); };
return blargg_ok;
} static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; }
}; static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; }
static Music_Emu* new_gbs_emu () { return BLARGG_NEW Gbs_Emu ; } static gme_type_t_ const gme_gbs_type_ = { "Game Boy", 0, &new_gbs_emu, &new_gbs_file, "GBS", 1 };
static Music_Emu* new_gbs_file() { return BLARGG_NEW Gbs_File; } extern gme_type_t const gme_gbs_type = &gme_gbs_type_;
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 ) assert( offsetof (header_t,copyright [32]) == header_size );
{ RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
RETURN_ERR( core_.load( in ) );
set_warning( core_.warning() ); set_track_count( header_.track_count );
set_track_count( header().track_count ); RETURN_ERR( check_gbs_header( &header_ ) );
set_voice_count( Gb_Apu::osc_count );
core_.apu().volume( gain() ); if ( header_.vers != 1 )
set_warning( "Unknown file version" );
static const char* const names [Gb_Apu::osc_count] = {
"Square 1", "Square 2", "Wave", "Noise" if ( header_.timer_mode & 0x78 )
}; set_warning( "Invalid timer mode" );
set_voice_names( names );
unsigned load_addr = get_le16( header_.load_addr );
static int const types [Gb_Apu::osc_count] = { if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F ||
wave_type+1, wave_type+2, wave_type+3, mixed_type+1 load_addr < 0x400 )
}; set_warning( "Invalid load/init/play address" );
set_voice_types( types );
set_voice_count( Gb_Apu::osc_count );
return setup_buffer( 4194304 );
} apu.volume( gain() );
void Gbs_Emu::update_eq( blip_eq_t const& eq ) return setup_buffer( 4194304 );
{ }
core_.apu().treble_eq( eq );
} void Gbs_Emu::update_eq( blip_eq_t const& eq )
{
void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) apu.treble_eq( eq );
{ }
core_.apu().set_output( i, c, l, r );
} void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
void Gbs_Emu::set_tempo_( double t ) apu.osc_output( i, c, l, r );
{ }
core_.set_tempo( t );
} // Emulation
blargg_err_t Gbs_Emu::start_track_( int track ) // see gb_cpu_io.h for read/write functions
{
sound_t mode = sound_hardware; void Gbs_Emu::set_bank( int n )
if ( mode == sound_gbs ) {
mode = (header().timer_mode & 0x80) ? sound_cgb : sound_dmg; // Only valid for MBC1 cartridges, but hopefully shouldn't hurt
n &= 0x1f;
RETURN_ERR( core_.start_track( track, (Gb_Apu::mode_t) mode ) ); if (n == 0)
{
// clear buffer AFTER track is started, eliminating initial click n = 1;
return Classic_Emu::start_track_( track ); }
}
blargg_long addr = n * (blargg_long) bank_size;
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int ) if (addr > rom.size())
{ {
return core_.end_frame( duration ); return;
} }
cpu::map_code( bank_size, bank_size, rom.at_addr( rom.mask_addr( addr ) ) );
blargg_err_t Gbs_Emu::hash_( Hash_Function& out ) const }
{
hash_gbs_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out ); void Gbs_Emu::update_timer()
return blargg_ok; {
} if ( header_.timer_mode & 0x04 )
{
static byte const rates [4] = { 10, 4, 6, 8 };
int shift = rates [ram [hi_page + 7] & 3] - (header_.timer_mode >> 7);
play_period = (256L - ram [hi_page + 6]) << shift;
}
else
{
play_period = 70224; // 59.73 Hz
}
if ( tempo() != 1.0 )
play_period = blip_time_t (play_period / tempo());
}
static uint8_t const sound_data [Gb_Apu::register_count] = {
0x80, 0xBF, 0x00, 0x00, 0xBF, // square 1
0x00, 0x3F, 0x00, 0x00, 0xBF, // square 2
0x7F, 0xFF, 0x9F, 0x00, 0xBF, // wave
0x00, 0xFF, 0x00, 0x00, 0xBF, // noise
0x77, 0xF3, 0xF1, // vin/volume, status, power mode
0, 0, 0, 0, 0, 0, 0, 0, 0, // unused
0xAC, 0xDD, 0xDA, 0x48, 0x36, 0x02, 0xCF, 0x16, // waveform data
0x2C, 0x04, 0xE5, 0x2C, 0xAC, 0xDD, 0xDA, 0x48
};
void Gbs_Emu::cpu_jsr( gb_addr_t addr )
{
check( cpu::r.sp == get_le16( header_.stack_ptr ) );
cpu::r.pc = addr;
cpu_write( --cpu::r.sp, idle_addr >> 8 );
cpu_write( --cpu::r.sp, idle_addr&0xFF );
}
void Gbs_Emu::set_tempo_( double t )
{
apu.set_tempo( t );
update_timer();
}
blargg_err_t Gbs_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( ram, 0, 0x4000 );
memset( ram + 0x4000, 0xFF, 0x1F00 );
memset( ram + 0x5F00, 0, sizeof ram - 0x5F00 );
ram [hi_page] = 0; // joypad reads back as 0
apu.reset();
for ( int i = 0; i < (int) sizeof sound_data; i++ )
apu.write_register( 0, i + apu.start_addr, sound_data [i] );
unsigned load_addr = get_le16( header_.load_addr );
rom.set_addr( load_addr );
cpu::rst_base = load_addr;
cpu::reset( rom.unmapped() );
cpu::map_code( ram_addr, 0x10000 - ram_addr, ram );
cpu::map_code( 0, bank_size, rom.at_addr( 0 ) );
set_bank( rom.size() > bank_size );
ram [hi_page + 6] = header_.timer_modulo;
ram [hi_page + 7] = header_.timer_mode;
update_timer();
next_play = play_period;
cpu::r.a = track;
cpu::r.pc = idle_addr;
cpu::r.sp = get_le16( header_.stack_ptr );
cpu_time = 0;
cpu_jsr( get_le16( header_.init_addr ) );
return 0;
}
blargg_err_t Gbs_Emu::run_clocks( blip_time_t& duration, int )
{
cpu_time = 0;
while ( cpu_time < duration )
{
long count = duration - cpu_time;
cpu_time = duration;
bool result = cpu::run( count );
cpu_time -= cpu::remain();
if ( result )
{
if ( cpu::r.pc == idle_addr )
{
if ( next_play > duration )
{
cpu_time = duration;
break;
}
if ( cpu_time < next_play )
cpu_time = next_play;
next_play += play_period;
cpu_jsr( get_le16( header_.play_addr ) );
GME_FRAME_HOOK( this );
// TODO: handle timer rates different than 60 Hz
}
else if ( cpu::r.pc > 0xFFFF )
{
debug_printf( "PC wrapped around\n" );
cpu::r.pc &= 0xFFFF;
}
else
{
set_warning( "Emulation error (illegal/unsupported instruction)" );
debug_printf( "Bad opcode $%.2x at $%.4x\n",
(int) *cpu::get_code( cpu::r.pc ), (int) cpu::r.pc );
cpu::r.pc = (cpu::r.pc + 1) & 0xFFFF;
cpu_time += 6;
}
}
}
duration = cpu_time;
next_play -= cpu_time;
if ( next_play < 0 ) // could go negative if routine is taking too long to return
next_play = 0;
apu.end_frame( cpu_time );
return 0;
}

View file

@ -1,63 +1,88 @@
// Nintendo Game Boy GBS music file emulator // Nintendo Game Boy GBS music file emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 "Gb_Apu.h"
#include "Gb_Cpu.h"
class Gbs_Emu : public Classic_Emu {
public: class Gbs_Emu : private Gb_Cpu, public Classic_Emu {
// Equalizer profiles for Game Boy speaker and headphones typedef Gb_Cpu cpu;
static equalizer_t const handheld_eq; public:
static equalizer_t const headphones_eq; // Equalizer profiles for Game Boy Color speaker and headphones
static equalizer_t const cgb_eq; // Game Boy Color headphones have less bass static equalizer_t const handheld_eq;
static equalizer_t const headphones_eq;
// GBS file header (see Gbs_Core.h)
typedef Gbs_Core::header_t header_t; // GBS file header
enum { header_size = 112 };
// Header for currently loaded file struct header_t
header_t const& header() const { return core_.header(); } {
char tag [3];
// Selects which sound hardware to use. AGB hardware is cleaner than the byte vers;
// others. Doesn't take effect until next start_track(). byte track_count;
enum sound_t { byte first_track;
sound_dmg = Gb_Apu::mode_dmg, // Game Boy monochrome byte load_addr [2];
sound_cgb = Gb_Apu::mode_cgb, // Game Boy Color byte init_addr [2];
sound_agb = Gb_Apu::mode_agb, // Game Boy Advance byte play_addr [2];
sound_gbs // Use DMG/CGB based on GBS (default) byte stack_ptr [2];
}; byte timer_modulo;
void set_sound( sound_t s ) { sound_hardware = s; } byte timer_mode;
char game [32];
// If true, makes APU more accurate, which results in more clicking. char author [32];
void enable_clicking( bool enable = true ) { core_.apu().reduce_clicks( !enable ); } char copyright [32];
};
static gme_type_t static_type() { return gme_gbs_type; }
// Header for currently loaded file
Gbs_Core& core() { return core_; } header_t const& header() const { return header_; }
blargg_err_t hash_( Hash_Function& ) const; static gme_type_t static_type() { return gme_gbs_type; }
// Internal public:
public: // deprecated
Gbs_Emu(); using Music_Emu::load;
~Gbs_Emu(); blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
protected:
// Overrides public:
virtual blargg_err_t track_info_( track_info_t*, int track ) const; Gbs_Emu();
virtual blargg_err_t load_( Data_Reader& ); ~Gbs_Emu();
virtual blargg_err_t start_track_( int ); protected:
virtual blargg_err_t run_clocks( blip_time_t&, int ); blargg_err_t track_info_( track_info_t*, int track ) const;
virtual void set_tempo_( double ); blargg_err_t load_( Data_Reader& );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); blargg_err_t start_track_( int );
virtual void update_eq( blip_eq_t const& ); blargg_err_t run_clocks( blip_time_t&, int );
virtual void unload(); void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
private: void update_eq( blip_eq_t const& );
sound_t sound_hardware; void unload();
Gbs_Core core_; private:
}; // rom
enum { bank_size = 0x4000 };
#endif Rom_Data<bank_size> rom;
void set_bank( int );
// timer
blip_time_t cpu_time;
blip_time_t play_period;
blip_time_t next_play;
void update_timer();
header_t header_;
void cpu_jsr( gb_addr_t );
public: private: friend class Gb_Cpu;
blip_time_t clock() const { return cpu_time - cpu::remain(); }
enum { joypad_addr = 0xFF00 };
enum { ram_addr = 0xA000 };
enum { hi_page = 0xFF00 - ram_addr };
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
Gb_Apu apu;
int cpu_read( gb_addr_t );
void cpu_write( gb_addr_t, int );
};
#endif

View file

@ -1,183 +1,234 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Gme_File.h" #include "Gme_File.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include "blargg_endian.h"
can redistribute it and/or modify it under the terms of the GNU Lesser #include <string.h>
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 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
module is distributed in the hope that it will be useful, but WITHOUT ANY can redistribute it and/or modify it under the terms of the GNU Lesser
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS General Public License as published by the Free Software Foundation; either
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more version 2.1 of the License, or (at your option) any later version. This
details. You should have received a copy of the GNU Lesser General Public module is distributed in the hope that it will be useful, but WITHOUT ANY
License along with this module; if not, write to the Free Software Foundation, WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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
#include "blargg_source.h" License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
void Gme_File::unload()
{ #include "blargg_source.h"
clear_playlist(); // BEFORE clearing track count
track_count_ = 0; const char* const gme_wrong_file_type = "Wrong file type for this emulator";
raw_track_count_ = 0;
Gme_Loader::unload(); void Gme_File::clear_playlist()
} {
playlist.clear();
Gme_File::Gme_File() clear_playlist_();
{ track_count_ = raw_track_count_;
type_ = NULL; }
user_data_ = NULL;
user_cleanup_ = NULL; void Gme_File::unload()
Gme_File::unload(); // clears fields {
} clear_playlist(); // *before* clearing track count
track_count_ = 0;
Gme_File::~Gme_File() raw_track_count_ = 0;
{ file_data.clear();
if ( user_cleanup_ ) }
user_cleanup_( user_data_ );
} Gme_File::Gme_File()
{
blargg_err_t Gme_File::post_load() type_ = 0;
{ user_data_ = 0;
if ( !track_count() ) user_cleanup_ = 0;
set_track_count( type()->track_count ); unload(); // clears fields
return Gme_Loader::post_load(); blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
} }
void Gme_File::clear_playlist() Gme_File::~Gme_File()
{ {
playlist.clear(); if ( user_cleanup_ )
clear_playlist_(); user_cleanup_( user_data_ );
track_count_ = raw_track_count_; }
}
blargg_err_t Gme_File::load_mem_( byte const* data, long size )
void Gme_File::copy_field_( char out [], const char* in, int in_size ) {
{ require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
if ( !in || !*in ) Mem_File_Reader in( data, size );
return; return load_( in );
}
// remove spaces/junk from beginning
while ( in_size && unsigned (*in - 1) <= ' ' - 1 ) blargg_err_t Gme_File::load_( Data_Reader& in )
{ {
in++; RETURN_ERR( file_data.resize( in.remain() ) );
in_size--; RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
} return load_mem_( file_data.begin(), file_data.size() );
}
// truncate
if ( in_size > max_field_ ) // public load functions call this at beginning
in_size = max_field_; void Gme_File::pre_load() { unload(); }
// find terminator void Gme_File::post_load_() { }
int len = 0;
while ( len < in_size && in [len] ) // public load functions call this at end
len++; blargg_err_t Gme_File::post_load( blargg_err_t err )
{
// remove spaces/junk from end if ( !track_count() )
while ( len && unsigned (in [len - 1]) <= ' ' ) set_track_count( type()->track_count );
len--; if ( !err )
post_load_();
// copy else
out [len] = 0; unload();
memcpy( out, in, len );
return err;
// strip out stupid fields that should have been left blank }
if ( !strcmp( out, "?" ) || !strcmp( out, "<?>" ) || !strcmp( out, "< ? >" ) )
out [0] = 0; // Public load functions
}
blargg_err_t Gme_File::load_mem( void const* in, long size )
void Gme_File::copy_field_( char out [], const char* in ) {
{ pre_load();
copy_field_( out, in, max_field_ ); return post_load( load_mem_( (byte const*) in, size ) );
} }
blargg_err_t Gme_File::remap_track_( int* track_io ) const blargg_err_t Gme_File::load( Data_Reader& in )
{ {
if ( (unsigned) *track_io >= (unsigned) track_count() ) pre_load();
return BLARGG_ERR( BLARGG_ERR_CALLER, "invalid track" ); return post_load( load_( in ) );
}
if ( (unsigned) *track_io < (unsigned) playlist.size() )
{ blargg_err_t Gme_File::load_file( const char* path )
M3u_Playlist::entry_t const& e = playlist [*track_io]; {
*track_io = 0; pre_load();
if ( e.track >= 0 ) GME_FILE_READER in;
{ RETURN_ERR( in.open( path ) );
*track_io = e.track; return post_load( load_( in ) );
// TODO: really needs to be removed? }
if ( !(type_->flags_ & 0x02) )
*track_io -= e.decimal_track; blargg_err_t Gme_File::load_remaining_( void const* h, long s, Data_Reader& in )
} {
if ( *track_io >= raw_track_count_ ) Remaining_Reader rem( h, s, &in );
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "invalid track in m3u playlist" ); return load( rem );
} }
else
{ // Track info
check( !playlist.size() );
} void Gme_File::copy_field_( char* out, const char* in, int in_size )
return blargg_ok; {
} if ( !in || !*in )
return;
blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
{ // remove spaces/junk from beginning
out->track_count = track_count(); while ( in_size && unsigned (*in - 1) <= ' ' - 1 )
out->length = -1; {
out->loop_length = -1; in++;
out->intro_length = -1; in_size--;
out->fade_length = -1; }
out->play_length = -1;
out->repeat_count = -1; // truncate
out->song [0] = 0; if ( in_size > max_field_ )
out->game [0] = 0; in_size = max_field_;
out->author [0] = 0;
out->composer [0] = 0; // find terminator
out->engineer [0] = 0; int len = 0;
out->sequencer [0] = 0; while ( len < in_size && in [len] )
out->tagger [0] = 0; len++;
out->copyright [0] = 0;
out->date [0] = 0; // remove spaces/junk from end
out->comment [0] = 0; while ( len && unsigned (in [len - 1]) <= ' ' )
out->dumper [0] = 0; len--;
out->system [0] = 0;
out->disc [0] = 0; // copy
out->track [0] = 0; out [len] = 0;
out->ost [0] = 0; memcpy( out, in, len );
copy_field_( out->system, type()->system ); // strip out stupid fields that should have been left blank
if ( !strcmp( out, "?" ) || !strcmp( out, "<?>" ) || !strcmp( out, "< ? >" ) )
int remapped = track; out [0] = 0;
RETURN_ERR( remap_track_( &remapped ) ); }
RETURN_ERR( track_info_( out, remapped ) );
void Gme_File::copy_field_( char* out, const char* in )
// override with m3u info {
if ( playlist.size() ) copy_field_( out, in, max_field_ );
{ }
M3u_Playlist::info_t const& i = playlist.info();
copy_field_( out->game , i.title ); blargg_err_t Gme_File::remap_track_( int* track_io ) const
copy_field_( out->author , i.artist ); {
copy_field_( out->engineer , i.engineer ); if ( (unsigned) *track_io >= (unsigned) track_count() )
copy_field_( out->composer , i.composer ); return "Invalid track";
copy_field_( out->sequencer, i.sequencer );
copy_field_( out->copyright, i.copyright ); if ( (unsigned) *track_io < (unsigned) playlist.size() )
copy_field_( out->dumper , i.ripping ); {
copy_field_( out->tagger , i.tagging ); M3u_Playlist::entry_t const& e = playlist [*track_io];
copy_field_( out->date , i.date ); *track_io = 0;
if ( e.track >= 0 )
M3u_Playlist::entry_t const& e = playlist [track]; {
if ( e.length >= 0 ) out->length = e.length; *track_io = e.track;
if ( e.intro >= 0 ) out->intro_length = e.intro; if ( !(type_->flags_ & 0x02) )
if ( e.loop >= 0 ) out->loop_length = e.loop; *track_io -= e.decimal_track;
if ( e.fade >= 0 ) out->fade_length = e.fade; }
if ( e.repeat >= 0 ) out->repeat_count = e.repeat; if ( *track_io >= raw_track_count_ )
copy_field_( out->song, e.name ); return "Invalid track in m3u playlist";
} }
else
// play_length {
out->play_length = out->length; check( !playlist.size() );
if ( out->play_length <= 0 ) }
{ return 0;
out->play_length = out->intro_length + 2 * out->loop_length; // intro + 2 loops }
if ( out->play_length <= 0 )
out->play_length = 150 * 1000; // 2.5 minutes blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
} {
out->track_count = track_count();
return blargg_ok; out->length = -1;
} out->loop_length = -1;
out->intro_length = -1;
out->fade_length = -1;
out->play_length = -1;
out->repeat_count = -1;
out->song [0] = 0;
out->game [0] = 0;
out->author [0] = 0;
out->composer [0] = 0;
out->engineer [0] = 0;
out->sequencer [0] = 0;
out->tagger [0] = 0;
out->copyright [0] = 0;
out->date [0] = 0;
out->comment [0] = 0;
out->dumper [0] = 0;
out->system [0] = 0;
out->disc [0] = 0;
out->track [0] = 0;
out->ost [0] = 0;
copy_field_( out->system, type()->system );
int remapped = track;
RETURN_ERR( remap_track_( &remapped ) );
RETURN_ERR( track_info_( out, remapped ) );
// override with m3u info
if ( playlist.size() )
{
M3u_Playlist::info_t const& i = playlist.info();
copy_field_( out->game , i.title );
copy_field_( out->author, i.artist );
copy_field_( out->engineer, i.engineer );
copy_field_( out->composer, i.composer );
copy_field_( out->sequencer, i.sequencer );
copy_field_( out->copyright, i.copyright );
copy_field_( out->dumper, i.ripping );
copy_field_( out->tagger, i.tagging );
copy_field_( out->date, i.date );
M3u_Playlist::entry_t const& e = playlist [track];
copy_field_( out->song, e.name );
if ( e.length >= 0 ) out->length = e.length;
if ( e.intro >= 0 ) out->intro_length = e.intro;
if ( e.loop >= 0 ) out->loop_length = e.loop;
if ( e.fade >= 0 ) out->fade_length = e.fade;
if ( e.repeat >= 0 ) out->repeat_count = e.repeat;
}
return 0;
}

View file

@ -1,153 +1,190 @@
// Common interface for track information // Common interface to game music file loading and information
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef GME_FILE_H #ifndef GME_FILE_H
#define GME_FILE_H #define GME_FILE_H
#include "gme.h" #include "gme.h"
#include "Gme_Loader.h" #include "blargg_common.h"
#include "M3u_Playlist.h" #include "Data_Reader.h"
#include "M3u_Playlist.h"
struct track_info_t
{ // Error returned if file is wrong type
int track_count; //extern const char gme_wrong_file_type []; // declared in gme.h
/* times in milliseconds; -1 if unknown */ struct gme_type_t_
int length; /* total length, if file specifies it */ {
int intro_length; /* length of song up to looping section */ const char* system; /* name of system this music file type is generally for */
int loop_length; /* length of looping section */ int track_count; /* non-zero for formats with a fixed number of tracks */
int fade_length; Music_Emu* (*new_emu)(); /* Create new emulator for this type (useful in C++ only) */
int repeat_count; Music_Emu* (*new_info)(); /* Create new info reader for this type */
/* Length if available, otherwise intro_length+loop_length*2 if available, /* internal */
otherwise a default of 150000 (2.5 minutes). */ const char* extension_;
int play_length; int flags_;
};
/* empty string if not available */
char system [256]; struct track_info_t
char game [256]; {
char song [256]; long track_count;
char author [256];
char composer [256]; /* times in milliseconds; -1 if unknown */
char engineer [256]; long length;
char sequencer [256]; long intro_length;
char tagger [256]; long loop_length;
char copyright [256]; long fade_length;
char date [256]; long repeat_count;
char comment [256];
char dumper [256]; /* Length if available, otherwise intro_length+loop_length*2 if available,
char disc [256]; * otherwise a default of 150000 (2.5 minutes) */
char track [256]; long play_length;
char ost [256];
}; /* empty string if not available */
enum { gme_max_field = 255 }; char system [256];
char game [256];
class Gme_File : public Gme_Loader { char song [256];
public: char author [256];
// Type of emulator. For example if this returns gme_nsfe_type, this object char composer [256];
// is an NSFE emulator, and you can downcast to an Nsfe_Emu* if necessary. char engineer [256];
gme_type_t type() const; char sequencer [256];
char tagger [256];
// Loads an m3u playlist. Must be done AFTER loading main music file. char copyright [256];
blargg_err_t load_m3u( const char path [] ); char date [256];
blargg_err_t load_m3u( Data_Reader& in ); char comment [256];
char dumper [256];
// Clears any loaded m3u playlist and any internal playlist that the music char disc [256];
// format supports (NSFE for example). char track [256];
void clear_playlist(); char ost [256];
};
// Number of tracks or 0 if no file has been loaded enum { gme_max_field = 255 };
int track_count() const;
struct Gme_File {
// Gets information for a track (length, name, author, etc.) public:
// See gme.h for definition of struct track_info_t. // File loading
blargg_err_t track_info( track_info_t* out, int track ) const;
// Each loads game music data from a file and returns an error if
// User data/cleanup // file is wrong type or is seriously corrupt. They also set warning
// string for minor problems.
// Sets/gets pointer to data you want to associate with this emulator.
// You can use this for whatever you want. // Load from file on disk
void set_user_data( void* p ) { user_data_ = p; } blargg_err_t load_file( const char* path );
void* user_data() const { return user_data_; }
// Load from custom data source (see Data_Reader.h)
// Registers cleanup function to be called when deleting emulator, or NULL to blargg_err_t load( Data_Reader& );
// clear it. Passes user_data to cleanup function.
void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; } // Load from file already read into memory. Keeps pointer to data, so you
// must not free it until you're done with the file.
public: blargg_err_t load_mem( void const* data, long size );
Gme_File();
~Gme_File(); // Load an m3u playlist. Must be done after loading main music file.
blargg_err_t load_m3u( const char* path );
protected: blargg_err_t load_m3u( Data_Reader& in );
// Services
void set_type( gme_type_t t ) { type_ = t; } // Clears any loaded m3u playlist and any internal playlist that the music
void set_track_count( int n ) { track_count_ = raw_track_count_ = n; } // format supports (NSFE for example).
void clear_playlist();
// Must be overridden
virtual blargg_err_t track_info_( track_info_t* out, int track ) const BLARGG_PURE( ; ) // Informational
// Optionally overridden // Type of emulator. For example if this returns gme_nsfe_type, this object
virtual void clear_playlist_() { } // is an NSFE emulator, and you can cast to an Nsfe_Emu* if necessary.
gme_type_t type() const;
protected: // Gme_Loader overrides
virtual void unload(); // Most recent warning string, or NULL if none. Clears current warning after
virtual blargg_err_t post_load(); // returning.
const char* warning();
protected:
blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu // Number of tracks or 0 if no file has been loaded
private: int track_count() const;
gme_type_t type_;
void* user_data_; // Get information for a track (length, name, author, etc.)
gme_user_cleanup_t user_cleanup_; // See gme.h for definition of struct track_info_t.
int track_count_; blargg_err_t track_info( track_info_t* out, int track ) const;
int raw_track_count_;
M3u_Playlist playlist; // User data/cleanup
char playlist_warning [64];
// Set/get pointer to data you want to associate with this emulator.
blargg_err_t load_m3u_( blargg_err_t ); // You can use this for whatever you want.
void set_user_data( void* p ) { user_data_ = p; }
public: void* user_data() const { return user_data_; }
// track_info field copying
enum { max_field_ = 255 }; // Register cleanup function to be called when deleting emulator, or NULL to
static void copy_field_( char out [], const char* in ); // clear it. Passes user_data to cleanup function.
static void copy_field_( char out [], const char* in, int len ); void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; }
};
bool is_archive = false;
struct gme_type_t_ virtual blargg_err_t load_archive( const char* ) { return gme_wrong_file_type; }
{
const char* system; /* name of system this music file type is generally for */ public:
int track_count; /* non-zero for formats with a fixed number of tracks */ // deprecated
Music_Emu* (*new_emu)(); /* Create new emulator for this type (C++ only) */ int error_count() const; // use warning()
Music_Emu* (*new_info)();/* Create new info reader for this type (C++ only) */ public:
Gme_File();
/* internal */ virtual ~Gme_File();
const char* extension_; BLARGG_DISABLE_NOTHROW
int flags_; typedef uint8_t byte;
}; protected:
// Services
/* Emulator type constants for each supported file type */ void set_track_count( int n ) { track_count_ = raw_track_count_ = n; }
extern const gme_type_t_ void set_warning( const char* s ) { warning_ = s; }
gme_ay_type [1], void set_type( gme_type_t t ) { type_ = t; }
gme_gbs_type [1], blargg_err_t load_remaining_( void const* header, long header_size, Data_Reader& remaining );
gme_gym_type [1],
gme_hes_type [1], // Overridable
gme_kss_type [1], virtual void unload(); // called before loading file and if loading fails
gme_nsf_type [1], virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
gme_nsfe_type [1], virtual blargg_err_t load_mem_( byte const* data, long size ); // use data in memory
gme_sap_type [1], virtual blargg_err_t track_info_( track_info_t* out, int track ) const = 0;
gme_sfm_type [1], virtual void pre_load();
gme_sgc_type [1], virtual void post_load_();
gme_spc_type [1], virtual void clear_playlist_() { }
gme_vgm_type [1],
gme_vgz_type [1]; public:
blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu
#define GME_COPY_FIELD( in, out, name ) \ private:
{ Gme_File::copy_field_( out->name, in.name, sizeof in.name ); } // noncopyable
Gme_File( const Gme_File& );
inline gme_type_t Gme_File::type() const { return type_; } Gme_File& operator = ( const Gme_File& );
inline int Gme_File::track_count() const { return track_count_; } gme_type_t type_;
int track_count_;
inline blargg_err_t Gme_File::track_info_( track_info_t*, int ) const { return blargg_ok; } int raw_track_count_;
const char* warning_;
#endif void* user_data_;
gme_user_cleanup_t user_cleanup_;
M3u_Playlist playlist;
char playlist_warning [64];
blargg_vector<byte> file_data; // only if loaded into memory using default load
blargg_err_t load_m3u_( blargg_err_t );
blargg_err_t post_load( blargg_err_t err );
public:
// track_info field copying
enum { max_field_ = 255 };
static void copy_field_( char* out, const char* in );
static void copy_field_( char* out, const char* in, int len );
};
Music_Emu* gme_new_( Music_Emu*, long sample_rate );
#define GME_COPY_FIELD( in, out, name ) \
{ Gme_File::copy_field_( out->name, in.name, sizeof in.name ); }
#ifndef GME_FILE_READER
#define GME_FILE_READER Std_File_Reader
#elif defined (GME_FILE_READER_INCLUDE)
#include GME_FILE_READER_INCLUDE
#endif
inline gme_type_t Gme_File::type() const { return type_; }
inline int Gme_File::error_count() const { return warning_ != 0; }
inline int Gme_File::track_count() const { return track_count_; }
inline const char* Gme_File::warning()
{
const char* s = warning_;
warning_ = 0;
return s;
}
#endif

View file

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

View file

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

View file

@ -1,428 +1,380 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Gym_Emu.h" #include "Gym_Emu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
General Public License as published by the Free Software Foundation; either can redistribute it and/or modify it under the terms of the GNU Lesser
version 2.1 of the License, or (at your option) any later version. This General Public License as published by the Free Software Foundation; either
module is distributed in the hope that it will be useful, but WITHOUT ANY version 2.1 of the License, or (at your option) any later version. This
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS module is distributed in the hope that it will be useful, but WITHOUT ANY
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
details. You should have received a copy of the GNU Lesser General Public FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
License along with this module; if not, write to the Free Software Foundation, details. You should have received a copy of the GNU Lesser General Public
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#include "blargg_source.h"
double const min_tempo = 0.25;
double const oversample = 5 / 3.0; double const min_tempo = 0.25;
double const fm_gain = 3.0; double const oversample_factor = 5 / 3.0;
double const fm_gain = 3.0;
int const base_clock = 53700300;
int const clock_rate = base_clock / 15; const long base_clock = 53700300;
const long clock_rate = base_clock / 15;
Gym_Emu::Gym_Emu()
{ Gym_Emu::Gym_Emu()
resampler.set_callback( play_frame_, this ); {
pos = NULL; data = 0;
disable_oversampling_ = false; pos = 0;
set_type( gme_gym_type ); set_type( gme_gym_type );
set_silence_lookahead( 1 ); // tracks should already be trimmed
pcm_buf = stereo_buf.center(); static const char* const names [] = {
} "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
};
Gym_Emu::~Gym_Emu() { } set_voice_names( names );
set_silence_lookahead( 1 ); // tracks should already be trimmed
// Track info }
static void get_gym_info( Gym_Emu::header_t const& h, int length, track_info_t* out ) Gym_Emu::~Gym_Emu() { }
{
if ( 0 != memcmp( h.tag, "GYMX", 4 ) ) // Track info
return;
static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out )
length = length * 50 / 3; // 1000 / 60 {
int loop = get_le32( h.loop_start ); if ( !memcmp( h.tag, "GYMX", 4 ) )
if ( loop ) {
{ length = length * 50 / 3; // 1000 / 60
out->intro_length = loop * 50 / 3; long loop = get_le32( h.loop_start );
out->loop_length = length - out->intro_length; if ( loop )
} {
else out->intro_length = loop * 50 / 3;
{ out->loop_length = length - out->intro_length;
out->length = length; }
out->intro_length = length; // make it clear that track is no longer than length else
out->loop_length = 0; {
} out->length = length;
out->intro_length = length; // make it clear that track is no longer than length
// more stupidity where the field should have been left blank out->loop_length = 0;
if ( strcmp( h.song, "Unknown Song" ) ) }
GME_COPY_FIELD( h, out, song );
// more stupidity where the field should have been left
if ( strcmp( h.game, "Unknown Game" ) ) if ( strcmp( h.song, "Unknown Song" ) )
GME_COPY_FIELD( h, out, game ); GME_COPY_FIELD( h, out, song );
if ( strcmp( h.copyright, "Unknown Publisher" ) ) if ( strcmp( h.game, "Unknown Game" ) )
GME_COPY_FIELD( h, out, copyright ); GME_COPY_FIELD( h, out, game );
if ( strcmp( h.dumper, "Unknown Person" ) ) if ( strcmp( h.copyright, "Unknown Publisher" ) )
GME_COPY_FIELD( h, out, dumper ); GME_COPY_FIELD( h, out, copyright );
if ( strcmp( h.comment, "Header added by YMAMP" ) ) if ( strcmp( h.dumper, "Unknown Person" ) )
GME_COPY_FIELD( h, out, comment ); GME_COPY_FIELD( h, out, dumper );
}
if ( strcmp( h.comment, "Header added by YMAMP" ) )
static void hash_gym_file( Gym_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) GME_COPY_FIELD( h, out, comment );
{ }
out.hash_( &h.loop_start[0], sizeof(h.loop_start) ); }
out.hash_( &h.packed[0], sizeof(h.packed) );
out.hash_( data, data_size ); blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const
} {
get_gym_info( header_, track_length(), out );
static int gym_track_length( byte const p [], byte const* end ) return 0;
{ }
int time = 0;
while ( p < end ) static long gym_track_length( byte const* p, byte const* end )
{ {
switch ( *p++ ) long time = 0;
{ while ( p < end )
case 0: {
time++; switch ( *p++ )
break; {
case 0:
case 1: time++;
case 2: break;
p += 2;
break; case 1:
case 2:
case 3: p += 2;
p += 1; break;
break;
} case 3:
} p += 1;
return time; break;
} }
}
blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const return time;
{ }
get_gym_info( header_, gym_track_length( log_begin(), file_end() ), out );
return blargg_ok; long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); }
}
static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 )
static blargg_err_t check_header( byte const in [], int size, int* data_offset = NULL ) {
{ if ( size < 4 )
if ( size < 4 ) return gme_wrong_file_type;
return blargg_err_file_type;
if ( memcmp( in, "GYMX", 4 ) == 0 )
if ( memcmp( in, "GYMX", 4 ) == 0 ) {
{ if ( size < Gym_Emu::header_size + 1 )
if ( size < Gym_Emu::header_t::size + 1 ) return gme_wrong_file_type;
return blargg_err_file_type;
if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 )
if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 ) return "Packed GYM file not supported";
return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "packed GYM file" );
if ( data_offset )
if ( data_offset ) *data_offset = Gym_Emu::header_size;
*data_offset = Gym_Emu::header_t::size; }
} else if ( *in > 3 )
else if ( *in > 3 ) {
{ return gme_wrong_file_type;
return blargg_err_file_type; }
}
return 0;
return blargg_ok; }
}
struct Gym_File : Gme_Info_
struct Gym_File : Gme_Info_ {
{ byte const* file_begin;
int data_offset; byte const* file_end;
int data_offset;
Gym_File() { set_type( gme_gym_type ); }
Gym_File() { set_type( gme_gym_type ); }
blargg_err_t load_mem_( byte const in [], int size )
{ blargg_err_t load_mem_( byte const* in, long size )
data_offset = 0; {
return check_header( in, size, &data_offset ); file_begin = in;
} file_end = in + size;
data_offset = 0;
blargg_err_t track_info_( track_info_t* out, int ) const return check_header( in, size, &data_offset );
{ }
int length = gym_track_length( &file_begin() [data_offset], file_end() );
get_gym_info( *(Gym_Emu::header_t const*) file_begin(), length, out ); blargg_err_t track_info_( track_info_t* out, int ) const
return blargg_ok; {
} long length = gym_track_length( &file_begin [data_offset], file_end );
get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out );
blargg_err_t hash_( Hash_Function& out ) const return 0;
{ }
Gym_Emu::header_t const* h = ( Gym_Emu::header_t const* ) file_begin(); };
byte const* data = &file_begin() [data_offset];
static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
hash_gym_file( *h, data, file_end() - data, out ); static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; }
return blargg_ok; static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 };
} extern gme_type_t const gme_gym_type = &gme_gym_type_;
};
// Setup
static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; } blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate )
{
gme_type_t_ const gme_gym_type [1] = {{ "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 }}; blip_eq_t eq( -32, 8000, sample_rate );
apu.treble_eq( eq );
// Setup dac_synth.treble_eq( eq );
apu.volume( 0.135 * fm_gain * gain() );
blargg_err_t Gym_Emu::set_sample_rate_( int sample_rate ) dac_synth.volume( 0.125 / 256 * fm_gain * gain() );
{ double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() );
blip_eq_t eq( -32, 8000, sample_rate ); fm_sample_rate = sample_rate * factor;
apu.treble_eq( eq );
pcm_synth.treble_eq( eq ); RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) );
blip_buf.clock_rate( clock_rate );
apu.volume( 0.135 * fm_gain * gain() );
RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) );
double factor = oversample; RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) );
if ( disable_oversampling_ )
factor = (double) base_clock / 7 / 144 / sample_rate; return 0;
RETURN_ERR( resampler.setup( factor, 0.990, fm_gain * gain() ) ); }
factor = resampler.rate();
double fm_rate = sample_rate * factor; void Gym_Emu::set_tempo_( double t )
{
RETURN_ERR( stereo_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) ); if ( t < min_tempo )
stereo_buf.clock_rate( clock_rate ); {
set_tempo( min_tempo );
RETURN_ERR( fm.set_rate( fm_rate, base_clock / 7.0 ) ); return;
RETURN_ERR( resampler.reset( (int) (1.0 / 60 / min_tempo * sample_rate) ) ); }
return blargg_ok; if ( blip_buf.sample_rate() )
} {
clocks_per_frame = long (clock_rate / 60 / tempo());
void Gym_Emu::set_tempo_( double t ) Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) );
{ }
if ( t < min_tempo ) }
{
set_tempo( min_tempo ); void Gym_Emu::mute_voices_( int mask )
return; {
} Music_Emu::mute_voices_( mask );
fm.mute_voices( mask );
if ( stereo_buf.sample_rate() ) dac_muted = (mask & 0x40) != 0;
{ apu.output( (mask & 0x80) ? 0 : &blip_buf );
double denom = tempo() * 60; }
clocks_per_frame = (int) (clock_rate / denom);
resampler.resize( (int) (sample_rate() / denom) ); blargg_err_t Gym_Emu::load_mem_( byte const* in, long size )
} {
} assert( offsetof (header_t,packed [4]) == header_size );
int offset = 0;
void Gym_Emu::mute_voices_( int mask ) RETURN_ERR( check_header( in, size, &offset ) );
{ set_voice_count( 8 );
Music_Emu::mute_voices_( mask );
fm.mute_voices( mask ); data = in + offset;
apu.set_output( (mask & 0x80) ? 0 : stereo_buf.center() ); data_end = in + size;
pcm_synth.volume( (mask & 0x40) ? 0.0 : 0.125 / 256 * fm_gain * gain() ); loop_begin = 0;
}
if ( offset )
blargg_err_t Gym_Emu::load_mem_( byte const in [], int size ) header_ = *(header_t const*) in;
{ else
assert( offsetof (header_t,packed [4]) == header_t::size ); memset( &header_, 0, sizeof header_ );
log_offset = 0;
RETURN_ERR( check_header( in, size, &log_offset ) ); return 0;
}
loop_begin = NULL;
// Emulation
static const char* const names [] = {
"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG" blargg_err_t Gym_Emu::start_track_( int track )
}; {
set_voice_names( names ); RETURN_ERR( Music_Emu::start_track_( track ) );
set_voice_count( 8 ); pos = data;
loop_remain = get_le32( header_.loop_start );
if ( log_offset )
header_ = *(header_t const*) in; prev_dac_count = 0;
else dac_enabled = false;
memset( &header_, 0, sizeof header_ ); dac_amp = -1;
return blargg_ok; fm.reset();
} apu.reset();
blip_buf.clear();
// Emulation Dual_Resampler::clear();
return 0;
blargg_err_t Gym_Emu::start_track_( int track ) }
{
RETURN_ERR( Music_Emu::start_track_( track ) ); void Gym_Emu::run_dac( int dac_count )
{
pos = log_begin(); // Guess beginning and end of sample and adjust rate and buffer position accordingly.
loop_remain = get_le32( header_.loop_start );
// count dac samples in next frame
prev_pcm_count = 0; int next_dac_count = 0;
pcm_enabled = 0; const byte* p = this->pos;
pcm_amp = -1; int cmd;
while ( (cmd = *p++) != 0 )
fm.reset(); {
apu.reset(); int data = *p++;
stereo_buf.clear(); if ( cmd <= 2 )
resampler.clear(); ++p;
pcm_buf = stereo_buf.center(); if ( cmd == 1 && data == 0x2A )
return blargg_ok; next_dac_count++;
} }
void Gym_Emu::run_pcm( byte const pcm_in [], int pcm_count ) // detect beginning and end of sample
{ int rate_count = dac_count;
// Guess beginning and end of sample and adjust rate and buffer position accordingly. int start = 0;
if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count )
// count dac samples in next frame {
int next_pcm_count = 0; rate_count = next_dac_count;
const byte* p = this->pos; start = next_dac_count - dac_count;
int cmd; }
while ( (cmd = *p++) != 0 ) else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count )
{ {
int data = *p++; rate_count = prev_dac_count;
if ( cmd <= 2 ) }
++p;
if ( cmd == 1 && data == 0x2A ) // Evenly space samples within buffer section being used
next_pcm_count++; blip_resampled_time_t period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count;
}
blip_resampled_time_t time = blip_buf.resampled_time( 0 ) +
// detect beginning and end of sample period * start + (period >> 1);
int rate_count = pcm_count;
int start = 0; int dac_amp = this->dac_amp;
if ( !prev_pcm_count && next_pcm_count && pcm_count < next_pcm_count ) if ( dac_amp < 0 )
{ dac_amp = dac_buf [0];
rate_count = next_pcm_count;
start = next_pcm_count - pcm_count; for ( int i = 0; i < dac_count; i++ )
} {
else if ( prev_pcm_count && !next_pcm_count && pcm_count < prev_pcm_count ) int delta = dac_buf [i] - dac_amp;
{ dac_amp += delta;
rate_count = prev_pcm_count; dac_synth.offset_resampled( time, delta, &blip_buf );
} time += period;
}
// Evenly space samples within buffer section being used this->dac_amp = dac_amp;
blip_resampled_time_t period = pcm_buf->resampled_duration( clocks_per_frame ) / rate_count; }
blip_resampled_time_t time = pcm_buf->resampled_time( 0 ) + period * start + (unsigned) period / 2; void Gym_Emu::parse_frame()
{
int pcm_amp = this->pcm_amp; int dac_count = 0;
if ( pcm_amp < 0 ) const byte* pos = this->pos;
pcm_amp = pcm_in [0];
if ( loop_remain && !--loop_remain )
for ( int i = 0; i < pcm_count; i++ ) loop_begin = pos; // find loop on first time through sequence
{
int delta = pcm_in [i] - pcm_amp; int cmd;
pcm_amp += delta; while ( (cmd = *pos++) != 0 )
pcm_synth.offset_resampled( time, delta, pcm_buf ); {
time += period; int data = *pos++;
} if ( cmd == 1 )
this->pcm_amp = pcm_amp; {
pcm_buf->set_modified(); int data2 = *pos++;
} if ( data != 0x2A )
{
void Gym_Emu::parse_frame() if ( data == 0x2B )
{ dac_enabled = (data2 & 0x80) != 0;
byte pcm [1024]; // all PCM writes for frame
int pcm_size = 0; fm.write0( data, data2 );
const byte* pos = this->pos; }
else if ( dac_count < (int) sizeof dac_buf )
if ( loop_remain && !--loop_remain ) {
loop_begin = pos; // find loop on first time through sequence dac_buf [dac_count] = data2;
dac_count += dac_enabled;
int cmd; }
while ( (cmd = *pos++) != 0 ) }
{ else if ( cmd == 2 )
int data = *pos++; {
if ( cmd == 1 ) fm.write1( data, *pos++ );
{ }
int data2 = *pos++; else if ( cmd == 3 )
if ( data == 0x2A ) {
{ apu.write_data( 0, data );
pcm [pcm_size] = data2; }
if ( pcm_size < (int) sizeof pcm - 1 ) else
pcm_size += pcm_enabled; {
} // to do: many GYM streams are full of errors, and error count should
else // reflect cases where music is really having problems
{ //log_error();
if ( data == 0x2B ) --pos; // put data back
pcm_enabled = data2 >> 7 & 1; }
}
fm.write0( data, data2 );
} // loop
} if ( pos >= data_end )
else if ( cmd == 2 ) {
{ check( pos == data_end );
int data2 = *pos++;
if ( data == 0xB6 ) if ( loop_begin )
{ pos = loop_begin;
Blip_Buffer * pcm_buf = NULL; else
switch ( data2 >> 6 ) set_track_ended();
{ }
case 0: pcm_buf = NULL; break; this->pos = pos;
case 1: pcm_buf = stereo_buf.right(); break;
case 2: pcm_buf = stereo_buf.left(); break; // dac
case 3: pcm_buf = stereo_buf.center(); break; if ( dac_count && !dac_muted )
} run_dac( dac_count );
/*if ( this->pcm_buf != pcm_buf ) prev_dac_count = dac_count;
{ }
if ( this->pcm_buf ) pcm_synth.offset_inline( 0, -pcm_amp, this->pcm_buf );
if ( pcm_buf ) pcm_synth.offset_inline( 0, pcm_amp, pcm_buf ); int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf )
}*/ {
this->pcm_buf = pcm_buf; if ( !track_ended() )
} parse_frame();
fm.write1( data, data2 );
} apu.end_frame( blip_time );
else if ( cmd == 3 )
{ memset( buf, 0, sample_count * sizeof *buf );
apu.write_data( 0, data ); fm.run( sample_count >> 1, buf );
}
else return sample_count;
{ }
// to do: many GYM streams are full of errors, and error count should
// reflect cases where music is really having problems blargg_err_t Gym_Emu::play_( long count, sample_t* out )
//log_error(); {
--pos; // put data back Dual_Resampler::dual_play( count, out, blip_buf );
} return 0;
} }
if ( pos >= file_end() )
{
// Reached end
check( pos == file_end() );
if ( loop_begin )
pos = loop_begin;
else
set_track_ended();
}
this->pos = pos;
// PCM
if ( pcm_buf && pcm_size )
run_pcm( pcm, pcm_size );
prev_pcm_count = pcm_size;
}
inline int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t buf [] )
{
if ( !track_ended() )
parse_frame();
apu.end_frame( blip_time );
memset( buf, 0, sample_count * sizeof *buf );
fm.run( sample_count >> 1, buf );
return sample_count;
}
int Gym_Emu::play_frame_( void* p, blip_time_t a, int b, sample_t c [] )
{
return STATIC_CAST(Gym_Emu*,p)->play_frame( a, b, c );
}
blargg_err_t Gym_Emu::play_( int count, sample_t out [] )
{
resampler.dual_play( count, out, stereo_buf );
return blargg_ok;
}
blargg_err_t Gym_Emu::hash_( Hash_Function& out ) const
{
hash_gym_file( header(), log_begin(), file_end() - log_begin(), out );
return blargg_ok;
}

View file

@ -1,88 +1,82 @@
// Sega Genesis/Mega Drive GYM music file emulator // Sega Genesis/Mega Drive GYM music file emulator
// Performs PCM timing recovery to improve sample quality. // Includes with PCM timing recovery to improve sample quality.
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef GYM_EMU_H #ifndef GYM_EMU_H
#define GYM_EMU_H #define GYM_EMU_H
#include "Dual_Resampler.h" #include "Dual_Resampler.h"
#include "Ym2612_Emu.h" #include "Ym2612_Emu.h"
#include "Music_Emu.h" #include "Music_Emu.h"
#include "Sms_Apu.h" #include "Sms_Apu.h"
class Gym_Emu : public Music_Emu { class Gym_Emu : public Music_Emu, private Dual_Resampler {
public: public:
// GYM file header
// GYM file header (optional; many files have NO header at all) enum { header_size = 428 };
struct header_t struct header_t
{ {
enum { size = 428 }; char tag [4];
char song [32];
char tag [ 4]; char game [32];
char song [ 32]; char copyright [32];
char game [ 32]; char emulator [32];
char copyright [ 32]; char dumper [32];
char emulator [ 32]; char comment [256];
char dumper [ 32]; byte loop_start [4]; // in 1/60 seconds, 0 if not looped
char comment [256]; byte packed [4];
byte loop_start [ 4]; // in 1/60 seconds, 0 if not looped };
byte packed [ 4];
}; // Header for currently loaded file
header_t const& header() const { return header_; }
// Header for currently loaded file
header_t const& header() const { return header_; } static gme_type_t static_type() { return gme_gym_type; }
static gme_type_t static_type() { return gme_gym_type; } public:
// deprecated
// Disables running FM chips at higher than normal rate. Will result in slightly using Music_Emu::load;
// more aliasing of high notes. blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; } { return load_remaining_( &h, sizeof h, in ); }
enum { gym_rate = 60 };
blargg_err_t hash_( Hash_Function& ) const; long track_length() const; // use track_info()
// Implementation public:
public: Gym_Emu();
Gym_Emu(); ~Gym_Emu();
~Gym_Emu(); protected:
blargg_err_t load_mem_( byte const*, long );
protected: blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t load_mem_( byte const [], int ); blargg_err_t set_sample_rate_( long sample_rate );
virtual blargg_err_t track_info_( track_info_t*, int track ) const; blargg_err_t start_track_( int );
virtual blargg_err_t set_sample_rate_( int sample_rate ); blargg_err_t play_( long count, sample_t* );
virtual blargg_err_t start_track_( int ); void mute_voices_( int );
virtual blargg_err_t play_( int count, sample_t [] ); void set_tempo_( double );
virtual void mute_voices_( int ); int play_frame( blip_time_t blip_time, int sample_count, sample_t* buf );
virtual void set_tempo_( double ); private:
// sequence data begin, loop begin, current position, end
private: const byte* data;
// Log const byte* loop_begin;
byte const* pos; // current position const byte* pos;
byte const* loop_begin; const byte* data_end;
int log_offset; // size of header (0 or header_t::size) blargg_long loop_remain; // frames remaining until loop beginning has been located
int loop_remain; // frames remaining until loop_begin has been located header_t header_;
int clocks_per_frame; double fm_sample_rate;
blargg_long clocks_per_frame;
bool disable_oversampling_; void parse_frame();
// PCM // dac (pcm)
int pcm_amp; int dac_amp;
int prev_pcm_count; // for detecting beginning/end of group of samples int prev_dac_count;
int pcm_enabled; bool dac_enabled;
bool dac_muted;
// large objects void run_dac( int );
Dual_Resampler resampler;
Stereo_Buffer stereo_buf; // sound
Blip_Buffer * pcm_buf; Blip_Buffer blip_buf;
Ym2612_Emu fm; Ym2612_Emu fm;
Sms_Apu apu; Blip_Synth<blip_med_quality,1> dac_synth;
Blip_Synth_Fast pcm_synth; Sms_Apu apu;
header_t header_; byte dac_buf [1024];
};
byte const* log_begin() const { return file_begin() + log_offset; }
void parse_frame(); #endif
void run_pcm( byte const in [], int count );
int play_frame( blip_time_t blip_time, int sample_count, sample_t buf [] );
static int play_frame_( void*, blip_time_t, int, sample_t [] );
};
#endif

View file

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

View file

@ -1,87 +1,66 @@
// 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 https://bitbucket.org/mpyne/game-music-emu/
#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 { struct Hes_Osc
public: {
// Basics unsigned char wave [32];
short volume [2];
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, int last_amp [2];
// output is mono. int delay;
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); int period;
unsigned char noise;
// Emulates to time t, then writes data to addr unsigned char phase;
void write_data( blip_time_t t, int addr, int data ); unsigned char balance;
unsigned char dac;
// Emulates to time t, then subtracts t from the current time. blip_time_t last_time;
// OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); Blip_Buffer* outputs [2];
Blip_Buffer* chans [3];
// More features unsigned noise_lfsr;
unsigned char control;
// Resets sound chip
void reset(); enum { amp_range = 0x8000 };
typedef Blip_Synth<blip_med_quality,1> synth_t;
// Same as set_output(), but for a particular channel
enum { osc_count = 6 }; // 0 <= chan < osc_count void run_until( synth_t& synth, blip_time_t );
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); };
// Sets treble equalization class Hes_Apu {
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } public:
void treble_eq( blip_eq_t const& );
// Sets overall volume, where 1.0 is normal void volume( double );
void volume( double v ) { synth.volume( 1.8 / osc_count / amp_range * v ); }
enum { osc_count = 6 };
// Registers are at io_addr to io_addr+io_size-1 void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
enum { io_addr = 0x0800 };
enum { io_size = 10 }; void reset();
// Implementation enum { start_addr = 0x0800 };
public: enum { end_addr = 0x0809 };
Hes_Apu(); void write_data( blip_time_t, int addr, int data );
typedef BOOST::uint8_t byte;
void end_frame( blip_time_t );
private:
enum { amp_range = 0x8000 }; public:
struct Osc Hes_Apu();
{ private:
byte wave [32]; Hes_Osc oscs [osc_count];
int delay; int latch;
int period; int balance;
int phase; Hes_Osc::synth_t synth;
int noise_delay; void balance_changed( Hes_Osc& );
byte noise; void recalc_chans();
unsigned lfsr; };
byte control; inline void Hes_Apu::volume( double v ) { synth.volume( 1.8 / osc_count / Hes_Osc::amp_range * v ); }
byte balance;
byte dac; inline void Hes_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
short volume [2];
int last_amp [2]; #endif
blip_time_t last_time;
Blip_Buffer* output [2];
Blip_Buffer* outputs [3];
};
Osc oscs [osc_count];
int latch;
int balance;
Blip_Synth_Fast synth;
void balance_changed( Osc& );
static void run_osc( Blip_Synth_Fast&, Osc&, blip_time_t );
};
inline void Hes_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
for ( int i = osc_count; --i >= 0; )
set_output( i, c, l, r );
}
#endif

View file

@ -1,309 +0,0 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Hes_Apu_Adpcm.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Hes_Apu_Adpcm::Hes_Apu_Adpcm()
{
output = NULL;
memset( &state, 0, sizeof( state ) );
reset();
}
void Hes_Apu_Adpcm::reset()
{
last_time = 0;
next_timer = 0;
last_amp = 0;
memset( &state.pcmbuf, 0, sizeof(state.pcmbuf) );
memset( &state.port, 0, sizeof(state.port) );
state.ad_sample = 0;
state.ad_ref_index = 0;
state.addr = 0;
state.freq = 0;
state.writeptr = 0;
state.readptr = 0;
state.playflag = 0;
state.repeatflag = 0;
state.length = 0;
state.volume = 0xFF;
state.fadetimer = 0;
state.fadecount = 0;
}
void Hes_Apu_Adpcm::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
require( !center || (center && !left && !right) || (center && left && right) );
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
if ( !center || !left || !right )
{
left = center;
right = center;
}
output = center;
}
void Hes_Apu_Adpcm::run_until( blip_time_t end_time )
{
int volume = state.volume;
int fadetimer = state.fadetimer;
int fadecount = state.fadecount;
int last_time = this->last_time;
double next_timer = this->next_timer;
int last_amp = this->last_amp;
Blip_Buffer* output = this->output; // cache often-used values
while ( state.playflag && last_time < end_time )
{
while ( last_time >= next_timer )
{
if ( fadetimer )
{
if ( fadecount > 0 )
{
fadecount--;
volume = 0xFF * fadecount / fadetimer;
}
else if ( fadecount < 0 )
{
fadecount++;
volume = 0xFF - ( 0xFF * fadecount / fadetimer );
}
}
next_timer += 7159.091;
}
int amp;
if ( state.ad_low_nibble )
{
amp = adpcm_decode( state.pcmbuf[ state.playptr ] & 0x0F );
state.ad_low_nibble = false;
state.playptr++;
state.playedsamplecount++;
if ( state.playedsamplecount == state.playlength )
{
state.playflag = 0;
}
}
else
{
amp = adpcm_decode( state.pcmbuf[ state.playptr ] >> 4 );
state.ad_low_nibble = true;
}
amp = amp * volume / 0xFF;
int delta = amp - last_amp;
if ( output && delta )
{
last_amp = amp;
synth.offset_inline( last_time, delta, output );
}
last_time += state.freq;
}
if ( !state.playflag )
{
while ( next_timer <= end_time ) next_timer += 7159.091;
last_time = end_time;
}
this->last_time = last_time;
this->next_timer = next_timer;
this->last_amp = last_amp;
state.volume = volume;
state.fadetimer = fadetimer;
state.fadecount = fadecount;
}
void Hes_Apu_Adpcm::write_data( blip_time_t time, int addr, int data )
{
if ( time > last_time ) run_until( time );
data &= 0xFF;
state.port[ addr & 15 ] = data;
switch ( addr & 15 )
{
case 8:
state.addr &= 0xFF00;
state.addr |= data;
break;
case 9:
state.addr &= 0xFF;
state.addr |= data << 8;
break;
case 10:
state.pcmbuf[ state.writeptr++ ] = data;
state.playlength ++;
break;
case 11:
dprintf("ADPCM DMA 0x%02X", data);
break;
case 13:
if ( data & 0x80 )
{
state.addr = 0;
state.freq = 0;
state.writeptr = 0;
state.readptr = 0;
state.playflag = 0;
state.repeatflag = 0;
state.length = 0;
state.volume = 0xFF;
}
if ( ( data & 3 ) == 3 )
{
state.writeptr = state.addr;
}
if ( data & 8 )
{
state.readptr = state.addr ? state.addr - 1 : state.addr;
}
if ( data & 0x10 )
{
state.length = state.addr;
}
state.repeatflag = data & 0x20;
state.playflag = data & 0x40;
if ( state.playflag )
{
state.playptr = state.readptr;
state.playlength = state.length + 1;
state.playedsamplecount = 0;
state.ad_sample = 0;
state.ad_low_nibble = false;
}
break;
case 14:
state.freq = 7159091 / ( 32000 / ( 16 - ( data & 15 ) ) );
break;
case 15:
switch ( data & 15 )
{
case 0:
case 8:
case 12:
state.fadetimer = -100;
state.fadecount = state.fadetimer;
break;
case 10:
state.fadetimer = 5000;
state.fadecount = state.fadetimer;
break;
case 14:
state.fadetimer = 1500;
state.fadecount = state.fadetimer;
break;
}
break;
}
}
int Hes_Apu_Adpcm::read_data( blip_time_t time, int addr )
{
if ( time > last_time ) run_until( time );
switch ( addr & 15 )
{
case 10:
return state.pcmbuf [state.readptr++];
case 11:
return state.port [11] & ~1;
case 12:
if (!state.playflag)
{
state.port [12] |= 1;
state.port [12] &= ~8;
}
else
{
state.port [12] &= ~1;
state.port [12] |= 8;
}
return state.port [12];
case 13:
return state.port [13];
}
return 0xFF;
}
void Hes_Apu_Adpcm::end_frame( blip_time_t end_time )
{
run_until( end_time );
last_time -= end_time;
next_timer -= (double)end_time;
check( last_time >= 0 );
if ( output )
output->set_modified();
}
static short stepsize[49] = {
16, 17, 19, 21, 23, 25, 28,
31, 34, 37, 41, 45, 50, 55,
60, 66, 73, 80, 88, 97, 107,
118, 130, 143, 157, 173, 190, 209,
230, 253, 279, 307, 337, 371, 408,
449, 494, 544, 598, 658, 724, 796,
876, 963,1060,1166,1282,1411,1552
};
int Hes_Apu_Adpcm::adpcm_decode( int code )
{
int step = stepsize[state.ad_ref_index];
int delta;
int c = code & 7;
#if 1
delta = 0;
if ( c & 4 ) delta += step;
step >>= 1;
if ( c & 2 ) delta += step;
step >>= 1;
if ( c & 1 ) delta += step;
step >>= 1;
delta += step;
#else
delta = ( ( c + c + 1 ) * step ) / 8; // maybe faster, but introduces rounding
#endif
if ( c != code )
{
state.ad_sample -= delta;
if ( state.ad_sample < -2048 )
state.ad_sample = -2048;
}
else
{
state.ad_sample += delta;
if ( state.ad_sample > 2047 )
state.ad_sample = 2047;
}
static int const steps [8] = {
-1, -1, -1, -1, 2, 4, 6, 8
};
state.ad_ref_index += steps [c];
if ( state.ad_ref_index < 0 )
state.ad_ref_index = 0;
else if ( state.ad_ref_index > 48 )
state.ad_ref_index = 48;
return state.ad_sample;
}

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,139 +1,122 @@
// PC Engine CPU emulator for use with HES music files // PC Engine CPU emulator for use with HES music files
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 { typedef blargg_long hes_time_t; // clock cycle count
public: typedef unsigned hes_addr_t; // 16-bit address
typedef BOOST::uint8_t byte; enum { future_hes_time = INT_MAX / 2 + 1 };
typedef int time_t;
typedef int addr_t; class Hes_Cpu {
enum { future_time = INT_MAX/2 + 1 }; public:
void reset();
void reset();
enum { page_size = 0x2000 };
enum { page_bits = 13 }; enum { page_shift = 13 };
enum { page_size = 1 << page_bits }; enum { page_count = 8 };
enum { page_count = 0x10000 / page_size }; void set_mmr( int reg, int bank );
void set_mmr( int reg, int bank, void const* code );
uint8_t const* get_code( hes_addr_t );
byte const* get_code( addr_t );
uint8_t ram [page_size];
// NOT kept updated during emulation.
struct registers_t { // not kept updated during a call to run()
BOOST::uint16_t pc; struct registers_t {
byte a; uint16_t pc;
byte x; uint8_t a;
byte y; uint8_t x;
byte flags; uint8_t y;
byte sp; uint8_t status;
}; uint8_t sp;
registers_t r; };
registers_t r;
// page mapping registers
byte mmr [page_count + 1]; // page mapping registers
uint8_t mmr [page_count + 1];
// Time of beginning of next instruction to be executed
time_t time() const { return cpu_state->time + cpu_state->base; } // Set end_time and run CPU from current time. Returns true if any illegal
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; } // instructions were encountered.
void adjust_time( int delta ) { cpu_state->time += delta; } bool run( hes_time_t end_time );
// Clocks past end (negative if before) // Time of beginning of next instruction to be executed
int time_past_end() const { return cpu_state->time; } hes_time_t time() const { return state->time + state->base; }
void set_time( hes_time_t t ) { state->time = t - state->base; }
// Time of next IRQ void adjust_time( int delta ) { state->time += delta; }
time_t irq_time() const { return irq_time_; }
void set_irq_time( time_t ); hes_time_t irq_time() const { return irq_time_; }
void set_irq_time( hes_time_t );
// Emulation stops once time >= end_time
time_t end_time() const { return end_time_; } hes_time_t end_time() const { return end_time_; }
void set_end_time( time_t ); void set_end_time( hes_time_t );
// Subtracts t from all times void end_frame( hes_time_t );
void end_frame( time_t t );
// Attempt to execute instruction here results in CPU advancing time to
// Can read this many bytes past end of a page // lesser of irq_time() and end_time() (or end_time() if IRQs are
enum { cpu_padding = 8 }; // disabled)
enum { idle_addr = 0x1FFF };
private:
// noncopyable // Can read this many bytes past end of a page
Hes_Cpu( const Hes_Cpu& ); enum { cpu_padding = 8 };
Hes_Cpu& operator = ( const Hes_Cpu& );
public:
Hes_Cpu() { state = &state_; }
// Implementation enum { irq_inhibit = 0x04 };
public: private:
Hes_Cpu() { cpu_state = &cpu_state_; } // noncopyable
enum { irq_inhibit_mask = 0x04 }; Hes_Cpu( const Hes_Cpu& );
Hes_Cpu& operator = ( const Hes_Cpu& );
struct cpu_state_t {
byte const* code_map [page_count + 1]; struct state_t {
time_t base; uint8_t const* code_map [page_count + 1];
int time; hes_time_t base;
}; blargg_long time;
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy };
cpu_state_t cpu_state_; state_t* state; // points to state_ or a local copy within run()
time_t irq_time_; state_t state_;
time_t end_time_; hes_time_t irq_time_;
hes_time_t end_time_;
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 int update_end_time( hes_time_t end, hes_time_t irq );
}; };
#define HES_CPU_PAGE( addr ) ((unsigned) (addr) >> Hes_Cpu::page_bits) inline uint8_t const* Hes_Cpu::get_code( hes_addr_t addr )
{
#if BLARGG_NONPORTABLE return state->code_map [addr >> page_shift] + addr
#define HES_CPU_OFFSET( addr ) (addr) #if !BLARGG_NONPORTABLE
#else % (unsigned) page_size
#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 int Hes_Cpu::update_end_time( hes_time_t t, hes_time_t irq )
return cpu_state_.code_map [HES_CPU_PAGE( addr )] + HES_CPU_OFFSET( addr ); {
} if ( irq < t && !(r.status & irq_inhibit) ) t = irq;
int delta = state->base - t;
inline void Hes_Cpu::update_end_time( time_t end, time_t irq ) state->base = t;
{ return delta;
if ( end > irq && !(r.flags & irq_inhibit_mask) ) }
end = irq;
inline void Hes_Cpu::set_irq_time( hes_time_t t )
cpu_state->time += cpu_state->base - end; {
cpu_state->base = end; state->time += update_end_time( end_time_, (irq_time_ = t) );
} }
inline void Hes_Cpu::set_irq_time( time_t t ) inline void Hes_Cpu::set_end_time( hes_time_t t )
{ {
irq_time_ = t; state->time += update_end_time( (end_time_ = t), irq_time_ );
update_end_time( end_time_, t ); }
}
inline void Hes_Cpu::end_frame( hes_time_t t )
inline void Hes_Cpu::set_end_time( time_t t ) {
{ assert( state == &state_ );
end_time_ = t; state_.base -= t;
update_end_time( t, irq_time_ ); if ( irq_time_ < future_hes_time ) irq_time_ -= t;
} if ( end_time_ < future_hes_time ) end_time_ -= t;
}
inline void Hes_Cpu::end_frame( time_t t )
{ #endif
assert( cpu_state == &cpu_state_ );
cpu_state_.base -= t;
if ( irq_time_ < future_time ) irq_time_ -= t;
if ( end_time_ < future_time ) end_time_ -= t;
}
inline void Hes_Cpu::set_mmr( int reg, int bank, void const* code )
{
assert( (unsigned) reg <= page_count ); // allow page past end to be set
assert( (unsigned) bank < 0x100 );
mmr [reg] = bank;
byte const* p = STATIC_CAST(byte const*,code) - HES_CPU_OFFSET( reg << page_bits );
cpu_state->code_map [reg] = p;
cpu_state_.code_map [reg] = p;
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,192 +1,535 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Hes_Emu.h" #include "Hes_Emu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you #include <algorithm>
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 /* Copyright (C) 2006 Shay Green. This module is free software; you
version 2.1 of the License, or (at your option) any later version. This can redistribute it and/or modify it under the terms of the GNU Lesser
module is distributed in the hope that it will be useful, but WITHOUT ANY General Public License as published by the Free Software Foundation; either
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS version 2.1 of the License, or (at your option) any later version. This
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more module is distributed in the hope that it will be useful, but WITHOUT ANY
details. You should have received a copy of the GNU Lesser General Public WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
License along with this module; if not, write to the Free Software Foundation, FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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,
#include "blargg_source.h" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
Hes_Emu::Hes_Emu() #include "blargg_source.h"
{
set_type( gme_hes_type ); int const timer_mask = 0x04;
set_silence_lookahead( 6 ); int const vdp_mask = 0x02;
set_gain( 1.11 ); int const i_flag_mask = 0x04;
} int const unmapped = 0xFF;
Hes_Emu::~Hes_Emu() { } long const period_60hz = 262 * 455L; // scanlines * clocks per scanline
void Hes_Emu::unload() using std::min;
{ using std::max;
core.unload();
Music_Emu::unload(); Hes_Emu::Hes_Emu()
} {
timer.raw_load = 0;
static byte const* copy_field( byte const in [], char* out ) set_type( gme_hes_type );
{
if ( in ) static const char* const names [Hes_Apu::osc_count] = {
{ "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2"
int len = 0x20; };
if ( in [0x1F] && !in [0x2F] ) set_voice_names( names );
len = 0x30; // fields are sometimes 16 bytes longer (ugh)
static int const types [Hes_Apu::osc_count] = {
// since text fields are where any data could be, detect non-text wave_type | 0, wave_type | 1, wave_type | 2, wave_type | 3,
// and fields with data after zero byte terminator mixed_type | 0, mixed_type | 1
};
int i = 0; set_voice_types( types );
for ( ; i < len && in [i]; i++ ) set_silence_lookahead( 6 );
if ( (unsigned) (in [i] - ' ') >= 0xFF - ' ' ) // also treat 0xFF as non-text set_gain( 1.11 );
return 0; // non-ASCII found }
for ( ; i < len; i++ ) Hes_Emu::~Hes_Emu() { }
if ( in [i] )
return 0; // data after terminator void Hes_Emu::unload()
{
Gme_File::copy_field_( out, (char const*) in, len ); rom.clear();
in += len; Music_Emu::unload();
} }
return in;
} // Track info
static byte const* copy_hes_fields( byte const in [], track_info_t* out ) static byte const* copy_field( byte const* in, char* out )
{ {
byte const* in_offset = in; if ( in )
if ( *in_offset >= ' ' ) {
{ int len = 0x20;
in_offset = copy_field( in_offset, out->game ); if ( in [0x1F] && !in [0x2F] )
in_offset = copy_field( in_offset, out->author ); len = 0x30; // fields are sometimes 16 bytes longer (ugh)
in_offset = copy_field( in_offset, out->copyright );
} // since text fields are where any data could be, detect non-text
return in_offset ? in_offset : in; // and fields with data after zero byte terminator
}
int i = 0;
static void hash_hes_file( Hes_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) for ( i = 0; i < len && in [i]; i++ )
{ if ( ((in [i] + 1) & 0xFF) < ' ' + 1 ) // also treat 0xFF as non-text
out.hash_( &h.vers, sizeof(h.vers) ); return 0; // non-ASCII found
out.hash_( &h.first_track, sizeof(h.first_track) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) ); for ( ; i < len; i++ )
out.hash_( &h.banks[0], sizeof(h.banks) ); if ( in [i] )
out.hash_( &h.data_size[0], sizeof(h.data_size) ); return 0; // data after terminator
out.hash_( &h.addr[0], sizeof(h.addr) );
out.hash_( &h.unused[0], sizeof(h.unused) ); Gme_File::copy_field_( out, (char const*) in, len );
out.hash_( data, Hes_Core::info_offset ); in += len;
}
track_info_t temp; // GCC whines about passing a pointer to a temporary here return in;
byte const* more_data = copy_hes_fields( data + Hes_Core::info_offset, &temp ); }
out.hash_( more_data, data_size - ( more_data - data ) );
} static void copy_hes_fields( byte const* in, track_info_t* out )
{
blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const if ( *in >= ' ' )
{ {
copy_hes_fields( core.data() + core.info_offset, out ); in = copy_field( in, out->game );
return blargg_ok; in = copy_field( in, out->author );
} in = copy_field( in, out->copyright );
}
struct Hes_File : Gme_Info_ }
{
enum { fields_offset = Hes_Core::header_t::size + Hes_Core::info_offset }; blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const
{
union header_t { copy_hes_fields( rom.begin() + 0x20, out );
Hes_Core::header_t header; return 0;
byte data [fields_offset + 0x30 * 3]; }
} const* h;
static blargg_err_t check_hes_header( void const* header )
Hes_File() {
{ if ( memcmp( header, "HESM", 4 ) )
set_type( gme_hes_type ); return gme_wrong_file_type;
} return 0;
}
blargg_err_t load_mem_( byte const begin [], int size )
{ struct Hes_File : Gme_Info_
h = ( header_t const* ) begin; {
struct header_t {
if ( !h->header.valid_tag() ) char header [Hes_Emu::header_size];
return blargg_err_file_type; char unused [0x20];
byte fields [0x30 * 3];
return blargg_ok; } h;
}
Hes_File() { set_type( gme_hes_type ); }
blargg_err_t track_info_( track_info_t* out, int ) const
{ blargg_err_t load_( Data_Reader& in )
copy_hes_fields( h->data + fields_offset, out ); {
return blargg_ok; assert( offsetof (header_t,fields) == Hes_Emu::header_size + 0x20 );
} blargg_err_t err = in.read( &h, sizeof h );
if ( err )
blargg_err_t hash_( Hash_Function& out ) const return (err == in.eof_error ? gme_wrong_file_type : err);
{ return check_hes_header( &h );
hash_hes_file( h->header, file_begin() + h->header.size, file_end() - file_begin() - h->header.size, out ); }
return blargg_ok;
} blargg_err_t track_info_( track_info_t* out, int ) const
}; {
copy_hes_fields( h.fields, out );
static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; } return 0;
static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; } }
};
gme_type_t_ const gme_hes_type [1] = {{ "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 }};
static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; }
blargg_err_t Hes_Emu::load_( Data_Reader& in ) static Music_Emu* new_hes_file() { return BLARGG_NEW Hes_File; }
{
RETURN_ERR( core.load( in ) ); static gme_type_t_ const gme_hes_type_ = { "PC Engine", 256, &new_hes_emu, &new_hes_file, "HES", 1 };
extern gme_type_t const gme_hes_type = &gme_hes_type_;
static const char* const names [Hes_Apu::osc_count + Hes_Apu_Adpcm::osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Multi 1", "Multi 2", "ADPCM"
}; // Setup
set_voice_names( names );
blargg_err_t Hes_Emu::load_( Data_Reader& in )
static int const types [Hes_Apu::osc_count + Hes_Apu_Adpcm::osc_count] = { {
wave_type+0, wave_type+1, wave_type+2, wave_type+3, mixed_type+0, mixed_type+1, mixed_type+2 assert( offsetof (header_t,unused [4]) == header_size );
}; RETURN_ERR( rom.load( in, header_size, &header_, unmapped ) );
set_voice_types( types );
RETURN_ERR( check_hes_header( header_.tag ) );
set_voice_count( core.apu().osc_count + core.adpcm().osc_count );
core.apu().volume( gain() ); if ( header_.vers != 0 )
core.adpcm().volume( gain() ); set_warning( "Unknown file version" );
return setup_buffer( 7159091 ); if ( memcmp( header_.data_tag, "DATA", 4 ) )
} set_warning( "Data header missing" );
void Hes_Emu::update_eq( blip_eq_t const& eq ) if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
{ set_warning( "Unknown header data" );
core.apu().treble_eq( eq );
core.adpcm().treble_eq( eq ); // File spec supports multiple blocks, but I haven't found any, and
} // many files have bad sizes in the only block, so it's simpler to
// just try to load the damn data as best as possible.
void Hes_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{ long addr = get_le32( header_.addr );
if ( i < core.apu().osc_count ) long size = get_le32( header_.size );
core.apu().set_output( i, c, l, r ); long const rom_max = 0x100000;
else if ( i == core.apu().osc_count ) if ( addr & ~(rom_max - 1) )
core.adpcm().set_output( 0, c, l, r ); {
} set_warning( "Invalid address" );
addr &= rom_max - 1;
void Hes_Emu::set_tempo_( double t ) }
{ if ( (unsigned long) (addr + size) > (unsigned long) rom_max )
core.set_tempo( t ); set_warning( "Invalid size" );
}
if ( size != rom.file_size() )
blargg_err_t Hes_Emu::start_track_( int track ) {
{ if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
RETURN_ERR( Classic_Emu::start_track_( track ) ); set_warning( "Multiple DATA not supported" );
return core.start_track( track ); else if ( size < rom.file_size() )
} set_warning( "Extra file data" );
else
blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int ) set_warning( "Missing file data" );
{ }
return core.end_frame( duration_ );
} rom.set_addr( addr );
blargg_err_t Hes_Emu::hash_( Hash_Function& out ) const set_voice_count( apu.osc_count );
{
hash_hes_file( header(), core.data(), core.data_size(), out ); apu.volume( gain() );
return blargg_ok;
} return setup_buffer( 7159091 );
}
void Hes_Emu::update_eq( blip_eq_t const& eq )
{
apu.treble_eq( eq );
}
void Hes_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
apu.osc_output( i, center, left, right );
}
// Emulation
void Hes_Emu::recalc_timer_load()
{
timer.load = timer.raw_load * timer_base + 1;
}
void Hes_Emu::set_tempo_( double t )
{
play_period = hes_time_t (period_60hz / t);
timer_base = int (1024 / t);
recalc_timer_load();
}
blargg_err_t Hes_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
memset( sgx, 0, sizeof sgx );
apu.reset();
cpu::reset();
for ( unsigned i = 0; i < sizeof header_.banks; i++ )
set_mmr( i, header_.banks [i] );
set_mmr( page_count, 0xFF ); // unmapped beyond end of address space
irq.disables = timer_mask | vdp_mask;
irq.timer = future_hes_time;
irq.vdp = future_hes_time;
timer.enabled = false;
timer.raw_load= 0x80;
timer.count = timer.load;
timer.fired = false;
timer.last_time = 0;
vdp.latch = 0;
vdp.control = 0;
vdp.next_vbl = 0;
ram [0x1FF] = (idle_addr - 1) >> 8;
ram [0x1FE] = (idle_addr - 1) & 0xFF;
r.sp = 0xFD;
r.pc = get_le16( header_.init_addr );
r.a = track;
recalc_timer_load();
last_frame_hook = 0;
return 0;
}
// Hardware
void Hes_Emu::cpu_write_vdp( int addr, int data )
{
switch ( addr )
{
case 0:
vdp.latch = data & 0x1F;
break;
case 2:
if ( vdp.latch == 5 )
{
if ( data & 0x04 )
set_warning( "Scanline interrupt unsupported" );
run_until( time() );
vdp.control = data;
irq_changed();
}
else
{
debug_printf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
}
break;
case 3:
debug_printf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
break;
}
}
void Hes_Emu::cpu_write_( hes_addr_t addr, int data )
{
if ( unsigned (addr - apu.start_addr) <= apu.end_addr - apu.start_addr )
{
GME_APU_HOOK( this, addr - apu.start_addr, data );
// avoid going way past end when a long block xfer is writing to I/O space
hes_time_t t = min( time(), end_time() + 8 );
apu.write_data( t, addr, data );
return;
}
hes_time_t time = this->time();
switch ( addr )
{
case 0x0000:
case 0x0002:
case 0x0003:
cpu_write_vdp( addr, data );
return;
case 0x0C00: {
run_until( time );
timer.raw_load = (data & 0x7F) + 1;
recalc_timer_load();
timer.count = timer.load;
break;
}
case 0x0C01:
data &= 1;
if ( timer.enabled == data )
return;
run_until( time );
timer.enabled = data;
if ( data )
timer.count = timer.load;
break;
case 0x1402:
run_until( time );
irq.disables = data;
if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
debug_printf( "Int mask: $%02X\n", data );
break;
case 0x1403:
run_until( time );
if ( timer.enabled )
timer.count = timer.load;
timer.fired = false;
break;
#ifndef NDEBUG
case 0x1000: // I/O port
case 0x0402: // palette
case 0x0403:
case 0x0404:
case 0x0405:
return;
default:
debug_printf( "unmapped write $%04X <- $%02X\n", addr, data );
return;
#endif
}
irq_changed();
}
int Hes_Emu::cpu_read_( hes_addr_t addr )
{
hes_time_t time = this->time();
addr &= page_size - 1;
switch ( addr )
{
case 0x0000:
if ( irq.vdp > time )
return 0;
irq.vdp = future_hes_time;
run_until( time );
irq_changed();
return 0x20;
case 0x0002:
case 0x0003:
debug_printf( "VDP read not supported: %d\n", addr );
return 0;
case 0x0C01:
//return timer.enabled; // TODO: remove?
case 0x0C00:
run_until( time );
debug_printf( "Timer count read\n" );
return (unsigned) (timer.count - 1) / timer_base;
case 0x1402:
return irq.disables;
case 0x1403:
{
int status = 0;
if ( irq.timer <= time ) status |= timer_mask;
if ( irq.vdp <= time ) status |= vdp_mask;
return status;
}
#ifndef NDEBUG
case 0x1000: // I/O port
case 0x180C: // CD-ROM
case 0x180D:
break;
default:
debug_printf( "unmapped read $%04X\n", addr );
#endif
}
return unmapped;
}
// see hes_cpu_io.h for core read/write functions
// Emulation
void Hes_Emu::run_until( hes_time_t present )
{
while ( vdp.next_vbl < present )
vdp.next_vbl += play_period;
hes_time_t elapsed = present - timer.last_time;
if ( elapsed > 0 )
{
if ( timer.enabled )
{
timer.count -= elapsed;
if ( timer.count <= 0 )
timer.count += timer.load;
}
timer.last_time = present;
}
}
void Hes_Emu::irq_changed()
{
hes_time_t present = time();
if ( irq.timer > present )
{
irq.timer = future_hes_time;
if ( timer.enabled && !timer.fired )
irq.timer = present + timer.count;
}
if ( irq.vdp > present )
{
irq.vdp = future_hes_time;
if ( vdp.control & 0x08 )
irq.vdp = vdp.next_vbl;
}
hes_time_t time = future_hes_time;
if ( !(irq.disables & timer_mask) ) time = irq.timer;
if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp );
set_irq_time( time );
}
int Hes_Emu::cpu_done()
{
check( time() >= end_time() ||
(!(r.status & i_flag_mask) && time() >= irq_time()) );
if ( !(r.status & i_flag_mask) )
{
hes_time_t present = time();
if ( irq.timer <= present && !(irq.disables & timer_mask) )
{
timer.fired = true;
irq.timer = future_hes_time;
irq_changed(); // overkill, but not worth writing custom code
#if GME_FRAME_HOOK_DEFINED
{
unsigned const threshold = period_60hz / 30;
unsigned long elapsed = present - last_frame_hook;
if ( elapsed - period_60hz + threshold / 2 < threshold )
{
last_frame_hook = present;
GME_FRAME_HOOK( this );
}
}
#endif
return 0x0A;
}
if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
{
// work around for bugs with music not acknowledging VDP
//run_until( present );
//irq.vdp = future_hes_time;
//irq_changed();
#if GME_FRAME_HOOK_DEFINED
last_frame_hook = present;
GME_FRAME_HOOK( this );
#endif
return 0x08;
}
}
return 0;
}
static void adjust_time( blargg_long& time, hes_time_t delta )
{
if ( time < future_hes_time )
{
time -= delta;
if ( time < 0 )
time = 0;
}
}
blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int )
{
blip_time_t const duration = duration_; // cache
if ( cpu::run( duration ) )
set_warning( "Emulation error (illegal instruction)" );
check( time() >= duration );
//check( time() - duration < 20 ); // Txx instruction could cause going way over
run_until( duration );
// end time frame
timer.last_time -= duration;
vdp.next_vbl -= duration;
#if GME_FRAME_HOOK_DEFINED
last_frame_hook -= duration;
#endif
cpu::end_frame( duration );
::adjust_time( irq.timer, duration );
::adjust_time( irq.vdp, duration );
apu.end_frame( duration );
return 0;
}

View file

@ -1,42 +1,94 @@
// TurboGrafx-16/PC Engine HES music file emulator // TurboGrafx-16/PC Engine HES music file emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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_Apu.h"
#include "Hes_Cpu.h"
class Hes_Emu : public Classic_Emu {
public: class Hes_Emu : private Hes_Cpu, public Classic_Emu {
typedef Hes_Cpu cpu;
static gme_type_t static_type() { return gme_hes_type; } public:
// HES file header
// HES file header (see Hes_Core.h) enum { header_size = 0x20 };
typedef Hes_Core::header_t header_t; struct header_t
{
// Header for currently loaded file byte tag [4];
header_t const& header() const { return core.header(); } byte vers;
byte first_track;
blargg_err_t hash_( Hash_Function& ) const; byte init_addr [2];
byte banks [8];
// Implementation byte data_tag [4];
public: byte size [4];
Hes_Emu(); byte addr [4];
~Hes_Emu(); byte unused [4];
virtual void unload(); };
protected: // Header for currently loaded file
virtual blargg_err_t track_info_( track_info_t*, int track ) const; header_t const& header() const { return header_; }
virtual blargg_err_t load_( Data_Reader& );
virtual blargg_err_t start_track_( int ); static gme_type_t static_type() { return gme_hes_type; }
virtual blargg_err_t run_clocks( blip_time_t&, int );
virtual void set_tempo_( double ); public:
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); Hes_Emu();
virtual void update_eq( blip_eq_t const& ); ~Hes_Emu();
protected:
private: blargg_err_t track_info_( track_info_t*, int track ) const;
Hes_Core core; blargg_err_t load_( Data_Reader& );
}; blargg_err_t start_track_( int );
blargg_err_t run_clocks( blip_time_t&, int );
#endif void set_tempo_( double );
void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
void update_eq( blip_eq_t const& );
void unload();
public: private: friend class Hes_Cpu;
byte* write_pages [page_count + 1]; // 0 if unmapped or I/O space
int cpu_read_( hes_addr_t );
int cpu_read( hes_addr_t );
void cpu_write_( hes_addr_t, int data );
void cpu_write( hes_addr_t, int );
void cpu_write_vdp( int addr, int data );
byte const* cpu_set_mmr( int page, int bank );
int cpu_done();
private:
Rom_Data<page_size> rom;
header_t header_;
hes_time_t play_period;
hes_time_t last_frame_hook;
int timer_base;
struct {
hes_time_t last_time;
blargg_long count;
blargg_long load;
int raw_load;
byte enabled;
byte fired;
} timer;
struct {
hes_time_t next_vbl;
byte latch;
byte control;
} vdp;
struct {
hes_time_t timer;
hes_time_t vdp;
byte disables;
} irq;
void recalc_timer_load();
// large items
Hes_Apu apu;
byte sgx [3 * page_size + cpu_padding];
void irq_changed();
void run_until( hes_time_t );
};
#endif

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,120 @@
// Z80 CPU emulator
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef KSS_CPU_H
#define KSS_CPU_H
#include "blargg_endian.h"
typedef blargg_long cpu_time_t;
// must be defined by caller
void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data );
int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr );
void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data );
class Kss_Cpu {
public:
// Clear registers and map all pages to unmapped
void reset( void* unmapped_write, void const* unmapped_read );
// Map memory. Start and size must be multiple of page_size.
enum { page_size = 0x2000 };
void map_mem( unsigned addr, blargg_ulong size, void* write, void const* read );
// Map address to page
uint8_t* write( unsigned addr );
uint8_t const* read( unsigned addr );
// Run until specified time is reached. Returns true if suspicious/unsupported
// instruction was encountered at any point during run.
bool run( cpu_time_t end_time );
// Time of beginning of next instruction
cpu_time_t time() const { return state->time + state->base; }
// Alter current time. Not supported during run() call.
void set_time( cpu_time_t t ) { state->time = t - state->base; }
void adjust_time( int delta ) { state->time += delta; }
#if BLARGG_BIG_ENDIAN
struct regs_t { uint8_t b, c, d, e, h, l, flags, a; };
#else
struct regs_t { uint8_t c, b, e, d, l, h, a, flags; };
#endif
static_assert( sizeof (regs_t) == 8, "Invalid registers size, padding issue?" );
struct pairs_t { uint16_t bc, de, hl, fa; };
// Registers are not updated until run() returns
struct registers_t {
uint16_t pc;
uint16_t sp;
uint16_t ix;
uint16_t iy;
union {
regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a
pairs_t w; // w.bc, w.de, w.hl. w.fa
};
union {
regs_t b;
pairs_t w;
} alt;
uint8_t iff1;
uint8_t iff2;
uint8_t r;
uint8_t i;
uint8_t im;
};
//registers_t r; (below for efficiency)
enum { idle_addr = 0xFFFF };
// can read this far past end of a page
enum { cpu_padding = 0x100 };
public:
Kss_Cpu();
enum { page_shift = 13 };
enum { page_count = 0x10000 >> page_shift };
private:
uint8_t szpc [0x200];
cpu_time_t end_time_;
struct state_t {
uint8_t const* read [page_count + 1];
uint8_t * write [page_count + 1];
cpu_time_t base;
cpu_time_t time;
};
state_t* state; // points to state_ or a local copy within run()
state_t state_;
void set_end_time( cpu_time_t t );
void set_page( int i, void* write, void const* read );
public:
registers_t r;
};
#if BLARGG_NONPORTABLE
#define KSS_CPU_PAGE_OFFSET( addr ) (addr)
#else
#define KSS_CPU_PAGE_OFFSET( addr ) ((addr) & (page_size - 1))
#endif
inline uint8_t* Kss_Cpu::write( unsigned addr )
{
return state->write [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr );
}
inline uint8_t const* Kss_Cpu::read( unsigned addr )
{
return state->read [addr >> page_shift] + KSS_CPU_PAGE_OFFSET( addr );
}
inline void Kss_Cpu::set_end_time( cpu_time_t t )
{
cpu_time_t delta = state->base - t;
state->base = t;
state->time += delta;
}
#endif

View file

@ -1,493 +1,420 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Kss_Emu.h" #include "Kss_Emu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you #include <algorithm>
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 /* Copyright (C) 2006 Shay Green. This module is free software; you
version 2.1 of the License, or (at your option) any later version. This can redistribute it and/or modify it under the terms of the GNU Lesser
module is distributed in the hope that it will be useful, but WITHOUT ANY General Public License as published by the Free Software Foundation; either
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS version 2.1 of the License, or (at your option) any later version. This
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more module is distributed in the hope that it will be useful, but WITHOUT ANY
details. You should have received a copy of the GNU Lesser General Public WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
License along with this module; if not, write to the Free Software Foundation, FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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,
#include "blargg_source.h" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#define IF_PTR( ptr ) if ( ptr ) (ptr) #include "blargg_source.h"
int const clock_rate = 3579545; long const clock_rate = 3579545;
int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count;
#define FOR_EACH_APU( macro )\
{\ using std::min;
macro( sms.psg );\ using std::max;
macro( sms.fm );\
macro( msx.psg );\ Kss_Emu::Kss_Emu()
macro( msx.scc );\ {
macro( msx.music );\ sn = 0;
macro( msx.audio );\ set_type( gme_kss_type );
} set_silence_lookahead( 6 );
static const char* const names [osc_count] = {
Kss_Emu::Kss_Emu() : "Square 1", "Square 2", "Square 3",
core( this ) "Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5"
{ };
#define ACTION( apu ) { core.apu = NULL; } set_voice_names( names );
FOR_EACH_APU( ACTION );
#undef ACTION static int const types [osc_count] = {
wave_type | 0, wave_type | 1, wave_type | 2,
set_type( gme_kss_type ); wave_type | 3, wave_type | 4, wave_type | 5, wave_type | 6, wave_type | 7
} };
set_voice_types( types );
Kss_Emu::~Kss_Emu()
{ memset( unmapped_read, 0xFF, sizeof unmapped_read );
unload(); }
}
Kss_Emu::~Kss_Emu() { unload(); }
inline void Kss_Emu::Core::unload()
{ void Kss_Emu::unload()
#define ACTION( ptr ) { delete (ptr); (ptr) = 0; } {
FOR_EACH_APU( ACTION ); delete sn;
#undef ACTION sn = 0;
} Classic_Emu::unload();
}
void Kss_Emu::unload()
{ // Track info
core.unload();
Classic_Emu::unload(); static void copy_kss_fields( Kss_Emu::header_t const& h, track_info_t* out )
} {
const char* system = "MSX";
// Track info if ( h.device_flags & 0x02 )
{
static void copy_kss_fields( Kss_Core::header_t const& h, track_info_t* out ) system = "Sega Master System";
{ if ( h.device_flags & 0x04 )
const char* system = "MSX"; system = "Game Gear";
}
if ( h.device_flags & 0x02 ) Gme_File::copy_field_( out->system, system );
{ }
system = "Sega Master System";
if ( h.device_flags & 0x04 ) blargg_err_t Kss_Emu::track_info_( track_info_t* out, int ) const
system = "Game Gear"; {
copy_kss_fields( header_, out );
if ( h.device_flags & 0x01 ) return 0;
system = "Sega Mark III"; }
}
else static blargg_err_t check_kss_header( void const* header )
{ {
if ( h.device_flags & 0x09 ) if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
system = "MSX + FM Sound"; return gme_wrong_file_type;
} return 0;
Gme_File::copy_field_( out->system, system ); }
}
struct Kss_File : Gme_Info_
static void hash_kss_file( Kss_Core::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) {
{ Kss_Emu::header_t header_;
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
out.hash_( &h.load_size[0], sizeof(h.load_size) ); Kss_File() { set_type( gme_kss_type ); }
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) ); blargg_err_t load_( Data_Reader& in )
out.hash_( &h.first_bank, sizeof(h.first_bank) ); {
out.hash_( &h.bank_mode, sizeof(h.bank_mode) ); blargg_err_t err = in.read( &header_, Kss_Emu::header_size );
out.hash_( &h.extra_header, sizeof(h.extra_header) ); if ( err )
out.hash_( &h.device_flags, sizeof(h.device_flags) ); return (err == in.eof_error ? gme_wrong_file_type : err);
return check_kss_header( &header_ );
out.hash_( data, data_size ); }
}
blargg_err_t 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 ); return 0;
// TODO: remove }
//if ( msx.music ) strcpy( out->system, "msxmusic" ); };
//if ( msx.audio ) strcpy( out->system, "msxaudio" );
//if ( sms.fm ) strcpy( out->system, "fmunit" ); static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; }
return blargg_ok; static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; }
}
static gme_type_t_ const gme_kss_type_ = { "MSX", 256, &new_kss_emu, &new_kss_file, "KSS", 0x03 };
static blargg_err_t check_kss_header( void const* header ) extern gme_type_t const gme_kss_type = &gme_kss_type_;
{
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
return blargg_err_file_type; // Setup
return blargg_ok; void Kss_Emu::update_gain()
} {
double g = gain() * 1.4;
struct Kss_File : Gme_Info_ if ( scc_accessed )
{ g *= 1.5;
Kss_Emu::header_t const* header_; ay.volume( g );
scc.volume( g );
Kss_File() { set_type( gme_kss_type ); } if ( sn )
sn->volume( g );
blargg_err_t load_mem_( byte const begin [], int size ) }
{
header_ = ( Kss_Emu::header_t const* ) begin; blargg_err_t Kss_Emu::load_( Data_Reader& in )
{
if ( header_->tag [3] == 'X' && header_->extra_header == 0x10 ) memset( &header_, 0, sizeof header_ );
set_track_count( get_le16( header_->last_track ) + 1 ); assert( offsetof (header_t,device_flags) == header_size - 1 );
assert( offsetof (ext_header_t,msx_audio_vol) == ext_header_size - 1 );
return check_kss_header( header_ ); RETURN_ERR( rom.load( in, header_size, STATIC_CAST(header_t*,&header_), 0 ) );
}
RETURN_ERR( check_kss_header( header_.tag ) );
blargg_err_t track_info_( track_info_t* out, int ) const
{ if ( header_.tag [3] == 'C' )
copy_kss_fields( *header_, out ); {
return blargg_ok; if ( header_.extra_header )
} {
header_.extra_header = 0;
blargg_err_t hash_( Hash_Function& out ) const set_warning( "Unknown data in header" );
{ }
hash_kss_file( *header_, file_begin() + Kss_Core::header_t::base_size, file_end() - file_begin() - Kss_Core::header_t::base_size, out ); if ( header_.device_flags & ~0x0F )
return blargg_ok; {
} header_.device_flags &= 0x0F;
}; set_warning( "Unknown data in header" );
}
static Music_Emu* new_kss_emu () { return BLARGG_NEW Kss_Emu ; } }
static Music_Emu* new_kss_file() { return BLARGG_NEW Kss_File; } else
{
gme_type_t_ const gme_kss_type [1] = {{ ext_header_t& ext = header_;
"MSX", memcpy( &ext, rom.begin(), min( (int) ext_header_size, (int) header_.extra_header ) );
256, if ( header_.extra_header > 0x10 )
&new_kss_emu, set_warning( "Unknown data in header" );
&new_kss_file, }
"KSS",
0x03 if ( header_.device_flags & 0x09 )
}}; set_warning( "FM sound not supported" );
// Setup scc_enabled = 0xC000;
if ( header_.device_flags & 0x04 )
void Kss_Emu::Core::update_gain_() scc_enabled = 0;
{
double g = emu.gain(); if ( header_.device_flags & 0x02 && !sn )
if ( msx.music || msx.audio || sms.fm ) CHECK_ALLOC( sn = BLARGG_NEW( Sms_Apu ) );
{
g *= 0.3; set_voice_count( osc_count );
}
else return setup_buffer( ::clock_rate );
{ }
g *= 1.2;
if ( scc_accessed ) void Kss_Emu::update_eq( blip_eq_t const& eq )
g *= 1.4; {
} ay.treble_eq( eq );
scc.treble_eq( eq );
#define ACTION( apu ) IF_PTR( apu )->volume( g ) if ( sn )
FOR_EACH_APU( ACTION ); sn->treble_eq( eq );
#undef ACTION }
}
void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
static blargg_err_t new_opl_apu( Opl_Apu::type_t type, Opl_Apu** out ) {
{ int i2 = i - ay.osc_count;
check( !*out ); if ( i2 >= 0 )
CHECK_ALLOC( *out = BLARGG_NEW( Opl_Apu ) ); scc.osc_output( i2, center );
blip_time_t const period = 72; else
int const rate = clock_rate / period; ay.osc_output( i, center );
return (*out)->init( rate * period, rate, period, type ); if ( sn && i < sn->osc_count )
} sn->osc_output( i, center, left, right );
}
blargg_err_t Kss_Emu::load_( Data_Reader& in )
{ // Emulation
RETURN_ERR( core.load( in ) );
set_warning( core.warning() ); void Kss_Emu::set_tempo_( double t )
{
set_track_count( get_le16( header().last_track ) + 1 ); blip_time_t period =
(header_.device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60);
core.scc_enabled = false; play_period = blip_time_t (period / t);
if ( header().device_flags & 0x02 ) // Sega Master System }
{
int const osc_count = Sms_Apu::osc_count + Opl_Apu::osc_count; blargg_err_t Kss_Emu::start_track_( int track )
static const char* const names [osc_count] = { {
"Square 1", "Square 2", "Square 3", "Noise", "FM" RETURN_ERR( Classic_Emu::start_track_( track ) );
};
set_voice_names( names ); memset( ram, 0xC9, 0x4000 );
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
static int const types [osc_count] = {
wave_type+1, wave_type+3, wave_type+2, mixed_type+1, wave_type+0 // copy driver code to lo RAM
}; static byte const bios [] = {
set_voice_types( types ); 0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
// sms.psg };
set_voice_count( Sms_Apu::osc_count ); static byte const vectors [] = {
check( !core.sms.psg ); 0xC3, 0x01, 0x00, // $0093: WRTPSG vector
CHECK_ALLOC( core.sms.psg = BLARGG_NEW Sms_Apu ); 0xC3, 0x09, 0x00, // $0096: RDPSG vector
};
// sms.fm memcpy( ram + 0x01, bios, sizeof bios );
if ( header().device_flags & 0x01 ) memcpy( ram + 0x93, vectors, sizeof vectors );
{
set_voice_count( osc_count ); // copy non-banked data into RAM
RETURN_ERR( new_opl_apu( Opl_Apu::type_smsfmunit, &core.sms.fm ) ); unsigned load_addr = get_le16( header_.load_addr );
} long orig_load_size = get_le16( header_.load_size );
long load_size = min( orig_load_size, rom.file_size() );
} load_size = min( load_size, long (mem_size - load_addr) );
else // MSX if ( load_size != orig_load_size )
{ set_warning( "Excessive data size" );
int const osc_count = Ay_Apu::osc_count + Opl_Apu::osc_count; memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
static const char* const names [osc_count] = {
"Square 1", "Square 2", "Square 3", "FM" rom.set_addr( -load_size - header_.extra_header );
};
set_voice_names( names ); // check available bank data
blargg_long const bank_size = this->bank_size();
static int const types [osc_count] = { int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
wave_type+1, wave_type+3, wave_type+2, wave_type+0 bank_count = header_.bank_mode & 0x7F;
}; if ( bank_count > max_banks )
set_voice_types( types ); {
bank_count = max_banks;
// msx.psg set_warning( "Bank data missing" );
set_voice_count( Ay_Apu::osc_count ); }
check( !core.msx.psg ); //debug_printf( "load_size : $%X\n", load_size );
CHECK_ALLOC( core.msx.psg = BLARGG_NEW Ay_Apu ); //debug_printf( "bank_size : $%X\n", bank_size );
//debug_printf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
if ( header().device_flags & 0x10 )
set_warning( "MSX stereo not supported" ); ram [idle_addr] = 0xFF;
cpu::reset( unmapped_write, unmapped_read );
// msx.music cpu::map_mem( 0, mem_size, ram, ram );
if ( header().device_flags & 0x01 )
{ ay.reset();
set_voice_count( osc_count ); scc.reset();
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxmusic, &core.msx.music ) ); if ( sn )
} sn->reset();
r.sp = 0xF380;
// msx.audio ram [--r.sp] = idle_addr >> 8;
if ( header().device_flags & 0x08 ) ram [--r.sp] = idle_addr & 0xFF;
{ r.b.a = track;
set_voice_count( osc_count ); r.pc = get_le16( header_.init_addr );
RETURN_ERR( new_opl_apu( Opl_Apu::type_msxaudio, &core.msx.audio ) ); next_play = play_period;
} scc_accessed = false;
gain_updated = false;
if ( !(header().device_flags & 0x80) ) update_gain();
{ ay_latch = 0;
if ( !(header().device_flags & 0x84) )
core.scc_enabled = core.scc_enabled_true; return 0;
}
// msx.scc
check( !core.msx.scc ); void Kss_Emu::set_bank( int logical, int physical )
CHECK_ALLOC( core.msx.scc = BLARGG_NEW Scc_Apu ); {
unsigned const bank_size = this->bank_size();
int const osc_count = Ay_Apu::osc_count + Scc_Apu::osc_count;
static const char* const names [osc_count] = { unsigned addr = 0x8000;
"Square 1", "Square 2", "Square 3", if ( logical && bank_size == 8 * 1024 )
"Wave 1", "Wave 2", "Wave 3", "Wave 4", "Wave 5" addr = 0xA000;
};
set_voice_names( names ); physical -= header_.first_bank;
if ( (unsigned) physical >= (unsigned) bank_count )
static int const types [osc_count] = { {
wave_type+1, wave_type+3, wave_type+2, byte* data = ram + addr;
wave_type+0, wave_type+4, wave_type+5, wave_type+6, wave_type+7, cpu::map_mem( addr, bank_size, data, data );
}; }
set_voice_types( types ); else
{
set_voice_count( osc_count ); long phys = physical * (blargg_long) bank_size;
} for ( unsigned offset = 0; offset < bank_size; offset += page_size )
} cpu::map_mem( addr + offset, page_size,
unmapped_write, rom.at_addr( phys + offset ) );
set_silence_lookahead( 6 ); }
if ( core.sms.fm || core.msx.music || core.msx.audio ) }
{
if ( !Opl_Apu::supported() ) void Kss_Emu::cpu_write( unsigned addr, int data )
set_warning( "FM sound not supported" ); {
else data &= 0xFF;
set_silence_lookahead( 3 ); // Opl_Apu is really slow switch ( addr )
} {
case 0x9000:
return setup_buffer( ::clock_rate ); set_bank( 0, data );
} return;
void Kss_Emu::update_eq( blip_eq_t const& eq ) case 0xB000:
{ set_bank( 1, data );
#define ACTION( apu ) IF_PTR( core.apu )->treble_eq( eq ) return;
FOR_EACH_APU( ACTION ); }
#undef ACTION
} int scc_addr = (addr & 0xDFFF) ^ 0x9800;
if ( scc_addr < scc.reg_count )
void Kss_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) {
{ scc_accessed = true;
if ( core.sms.psg ) // Sega Master System scc.write( time(), scc_addr, data );
{ return;
i -= core.sms.psg->osc_count; }
if ( i < 0 )
{ debug_printf( "LD ($%04X),$%02X\n", addr, data );
core.sms.psg->set_output( i + core.sms.psg->osc_count, center, left, right ); }
return;
} void kss_cpu_write( Kss_Cpu* cpu, unsigned addr, int data )
{
if ( core.sms.fm && i < core.sms.fm->osc_count ) *cpu->write( addr ) = data;
core.sms.fm->set_output( i, center, NULL, NULL ); if ( (addr & STATIC_CAST(Kss_Emu&,*cpu).scc_enabled) == 0x8000 )
} STATIC_CAST(Kss_Emu&,*cpu).cpu_write( addr, data );
else if ( core.msx.psg ) // MSX }
{
i -= core.msx.psg->osc_count; void kss_cpu_out( Kss_Cpu* cpu, cpu_time_t time, unsigned addr, int data )
if ( i < 0 ) {
{ data &= 0xFF;
core.msx.psg->set_output( i + core.msx.psg->osc_count, center ); Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu);
return; switch ( addr & 0xFF )
} {
case 0xA0:
if ( core.msx.scc && i < core.msx.scc->osc_count ) core.msx.scc ->set_output( i, center ); emu.ay_latch = data & 0x0F;
if ( core.msx.music && i < core.msx.music->osc_count ) core.msx.music->set_output( i, center, NULL, NULL ); return;
if ( core.msx.audio && i < core.msx.audio->osc_count ) core.msx.audio->set_output( i, center, NULL, NULL );
} case 0xA1:
} GME_APU_HOOK( &emu, emu.ay_latch, data );
emu.ay.write( time, emu.ay_latch, data );
void Kss_Emu::set_tempo_( double t ) return;
{
int period = (header().device_flags & 0x40 ? ::clock_rate / 50 : ::clock_rate / 60); case 0x06:
core.set_play_period( (Kss_Core::time_t) (period / t) ); if ( emu.sn && (emu.header_.device_flags & 0x04) )
} {
emu.sn->write_ggstereo( time, data );
blargg_err_t Kss_Emu::start_track_( int track ) return;
{ }
RETURN_ERR( Classic_Emu::start_track_( track ) ); break;
#define ACTION( apu ) IF_PTR( core.apu )->reset() case 0x7E:
FOR_EACH_APU( ACTION ); case 0x7F:
#undef ACTION if ( emu.sn )
{
core.scc_accessed = false; GME_APU_HOOK( &emu, 16, data );
core.update_gain_(); emu.sn->write_data( time, data );
return;
return core.start_track( track ); }
} break;
void Kss_Emu::Core::cpu_write_( addr_t addr, int data ) case 0xFE:
{ emu.set_bank( 0, data );
// TODO: SCC+ support return;
data &= 0xFF; #ifndef NDEBUG
switch ( addr ) case 0xF1: // FM data
{ if ( data )
case 0x9000: break; // trap non-zero data
set_bank( 0, data ); case 0xF0: // FM addr
return; case 0xA8: // PPI
return;
case 0xB000: #endif
set_bank( 1, data ); }
return;
debug_printf( "OUT $%04X,$%02X\n", addr, data );
case 0xBFFE: // selects between mapping areas (we just always enable both) }
if ( data == 0 || data == 0x20 )
return; int kss_cpu_in( Kss_Cpu*, cpu_time_t, unsigned addr )
} {
//Kss_Emu& emu = STATIC_CAST(Kss_Emu&,*cpu);
int scc_addr = (addr & 0xDFFF) - 0x9800; //switch ( addr & 0xFF )
if ( (unsigned) scc_addr < 0xB0 && msx.scc ) //{
{ //}
scc_accessed = true;
//if ( (unsigned) (scc_addr - 0x90) < 0x10 ) debug_printf( "IN $%04X\n", addr );
// scc_addr -= 0x10; // 0x90-0x9F mirrors to 0x80-0x8F return 0;
if ( scc_addr < Scc_Apu::reg_count ) }
msx.scc->write( cpu.time(), addr, data );
return; // Emulation
}
blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int )
dprintf( "LD ($%04X),$%02X\n", addr, data ); {
} while ( time() < duration )
{
void Kss_Emu::Core::cpu_write( addr_t addr, int data ) blip_time_t end = min( duration, next_play );
{ cpu::run( min( duration, next_play ) );
*cpu.write( addr ) = data; if ( r.pc == idle_addr )
if ( (addr & scc_enabled) == 0x8000 ) set_time( end );
cpu_write_( addr, data );
} if ( time() >= next_play )
{
void Kss_Emu::Core::cpu_out( time_t time, addr_t addr, int data ) next_play += play_period;
{ if ( r.pc == idle_addr )
data &= 0xFF; {
switch ( addr & 0xFF ) if ( !gain_updated )
{ {
case 0xA0: gain_updated = true;
if ( msx.psg ) if ( scc_accessed )
msx.psg->write_addr( data ); update_gain();
return; }
case 0xA1: ram [--r.sp] = idle_addr >> 8;
if ( msx.psg ) ram [--r.sp] = idle_addr & 0xFF;
msx.psg->write_data( time, data ); r.pc = get_le16( header_.play_addr );
return; GME_FRAME_HOOK( this );
}
case 0x06: }
if ( sms.psg && (header().device_flags & 0x04) ) }
{
sms.psg->write_ggstereo( time, data ); duration = time();
return; next_play -= duration;
} check( next_play >= 0 );
break; adjust_time( -duration );
ay.end_frame( duration );
case 0x7E: scc.end_frame( duration );
case 0x7F: if ( sn )
if ( sms.psg ) sn->end_frame( duration );
{
sms.psg->write_data( time, data ); return 0;
return; }
}
break;
#define OPL_WRITE_HANDLER( base, opl )\
case base : if ( opl ) { opl->write_addr( data ); return; } break;\
case base+1: if ( opl ) { opl->write_data( time, data ); return; } break;
OPL_WRITE_HANDLER( 0x7C, msx.music )
OPL_WRITE_HANDLER( 0xC0, msx.audio )
OPL_WRITE_HANDLER( 0xF0, sms.fm )
case 0xFE:
set_bank( 0, data );
return;
#ifndef NDEBUG
case 0xA8: // PPI
return;
#endif
}
Kss_Core::cpu_out( time, addr, data );
}
int Kss_Emu::Core::cpu_in( time_t time, addr_t addr )
{
switch ( addr & 0xFF )
{
case 0xC0:
case 0xC1:
if ( msx.audio )
return msx.audio->read( time, addr & 1 );
break;
case 0xA2:
if ( msx.psg )
return msx.psg->read();
break;
#ifndef NDEBUG
case 0xA8: // PPI
return 0;
#endif
}
return Kss_Core::cpu_in( time, addr );
}
void Kss_Emu::Core::update_gain()
{
if ( scc_accessed )
{
dprintf( "SCC accessed\n" );
update_gain_();
}
}
blargg_err_t Kss_Emu::run_clocks( blip_time_t& duration, int )
{
RETURN_ERR( core.end_frame( duration ) );
#define ACTION( apu ) IF_PTR( core.apu )->end_frame( duration )
FOR_EACH_APU( ACTION );
#undef ACTION
return blargg_ok;
}
blargg_err_t Kss_Emu::hash_( Hash_Function& out ) const
{
hash_kss_file( header(), core.rom_().begin(), core.rom_().file_size(), out );
return blargg_ok;
}

View file

@ -1,79 +1,95 @@
// MSX computer KSS music file emulator // MSX computer KSS music file emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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_Scc_Apu.h"
#include "Kss_Scc_Apu.h" #include "Kss_Cpu.h"
#include "Sms_Apu.h" #include "Sms_Apu.h"
#include "Ay_Apu.h" #include "Ay_Apu.h"
#include "Opl_Apu.h"
class Kss_Emu : private Kss_Cpu, public Classic_Emu {
class Kss_Emu : public Classic_Emu { typedef Kss_Cpu cpu;
public: public:
// KSS file header (see Kss_Core.h) // KSS file header
typedef Kss_Core::header_t header_t; enum { header_size = 0x10 };
struct header_t
// Header for currently loaded file {
header_t const& header() const { return core.header(); } byte tag [4];
byte load_addr [2];
blargg_err_t hash_( Hash_Function& ) const; byte load_size [2];
byte init_addr [2];
static gme_type_t static_type() { return gme_kss_type; } byte play_addr [2];
byte first_bank;
// Implementation byte bank_mode;
public: byte extra_header;
Kss_Emu(); byte device_flags;
~Kss_Emu(); };
protected: enum { ext_header_size = 0x10 };
virtual blargg_err_t track_info_( track_info_t*, int track ) const; struct ext_header_t
virtual blargg_err_t load_( Data_Reader& ); {
virtual blargg_err_t start_track_( int ); byte data_size [4];
virtual blargg_err_t run_clocks( blip_time_t&, int ); byte unused [4];
virtual void set_tempo_( double ); byte first_track [2];
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); byte last_tack [2];
virtual void update_eq( blip_eq_t const& ); byte psg_vol;
virtual void unload(); byte scc_vol;
byte msx_music_vol;
private: byte msx_audio_vol;
struct Core; };
friend struct Core;
struct Core : Kss_Core { struct composite_header_t : header_t, ext_header_t { };
Kss_Emu& emu;
// Header for currently loaded file
// detection of tunes that use SCC so they can be made louder composite_header_t const& header() const { return header_; }
bool scc_accessed;
static gme_type_t static_type() { return gme_kss_type; }
enum { scc_enabled_true = 0xC000 }; public:
unsigned scc_enabled; // 0 or 0xC000 Kss_Emu();
int ay_latch; ~Kss_Emu();
protected:
struct { blargg_err_t track_info_( track_info_t*, int track ) const;
Sms_Apu* psg; blargg_err_t load_( Data_Reader& );
Opl_Apu* fm; blargg_err_t start_track_( int );
} sms; blargg_err_t run_clocks( blip_time_t&, int );
void set_tempo_( double );
struct { void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
Ay_Apu* psg; void update_eq( blip_eq_t const& );
Scc_Apu* scc; void unload();
Opl_Apu* music; private:
Opl_Apu* audio; Rom_Data<page_size> rom;
} msx; composite_header_t header_;
Core( Kss_Emu* e ) : emu( *e ) { } bool scc_accessed;
bool gain_updated;
virtual void cpu_write( addr_t, int ); void update_gain();
virtual int cpu_in( time_t, addr_t );
virtual void cpu_out( time_t, addr_t, int ); unsigned scc_enabled; // 0 or 0xC000
virtual void update_gain(); int bank_count;
void set_bank( int logical, int physical );
void cpu_write_( addr_t addr, int data ); blargg_long bank_size() const { return (16 * 1024L) >> (header_.bank_mode >> 7 & 1); }
void update_gain_();
void unload(); blip_time_t play_period;
} core; blip_time_t next_play;
}; int ay_latch;
#endif friend void kss_cpu_out( class Kss_Cpu*, cpu_time_t, unsigned addr, int data );
friend int kss_cpu_in( class Kss_Cpu*, cpu_time_t, unsigned addr );
void cpu_write( unsigned addr, int data );
friend void kss_cpu_write( class Kss_Cpu*, unsigned addr, int data );
// large items
enum { mem_size = 0x10000 };
byte ram [mem_size + cpu_padding];
Ay_Apu ay;
Scc_Apu scc;
Sms_Apu* sn;
byte unmapped_read [0x100];
byte unmapped_write [page_size];
};
#endif

View file

@ -1,124 +1,97 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 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; unsigned 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::run_until( blip_time_t end_time )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int index = 0; index < osc_count; index++ )
set_output( i, buf ); {
} osc_t& osc = oscs [index];
void Scc_Apu::volume( double v ) Blip_Buffer* const output = osc.output;
{ if ( !output )
synth.volume( 0.43 / osc_count / amp_range * v ); continue;
} output->set_modified();
void Scc_Apu::reset() blip_time_t period = (regs [0x80 + index * 2 + 1] & 0x0F) * 0x100 +
{ regs [0x80 + index * 2] + 1;
last_time = 0; int volume = 0;
if ( regs [0x8F] & (1 << index) )
for ( int i = osc_count; --i >= 0; ) {
memset( &oscs [i], 0, offsetof (osc_t,output) ); blip_time_t inaudible_period = (blargg_ulong) (output->clock_rate() +
inaudible_freq * 32) / (inaudible_freq * 16);
memset( regs, 0, sizeof regs ); if ( period > inaudible_period )
} volume = (regs [0x8A + index] & 0x0F) * (amp_range / 256 / 15);
}
Scc_Apu::Scc_Apu()
{ int8_t const* wave = (int8_t*) regs + index * wave_size;
set_output( NULL ); if ( index == osc_count - 1 )
volume( 1.0 ); wave -= wave_size; // last two oscs share wave
reset(); {
} int amp = wave [osc.phase] * volume;
int delta = amp - osc.last_amp;
void Scc_Apu::run_until( blip_time_t end_time ) if ( delta )
{ {
for ( int index = 0; index < osc_count; index++ ) osc.last_amp = amp;
{ synth.offset( last_time, delta, output );
osc_t& osc = oscs [index]; }
}
Blip_Buffer* const output = osc.output;
if ( !output ) blip_time_t time = last_time + osc.delay;
continue; if ( time < end_time )
{
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 + if ( !volume )
regs [0xA0 + index * 2] + 1; {
int volume = 0; // maintain phase
if ( regs [0xAF] & (1 << index) ) blargg_long count = (end_time - time + period - 1) / period;
{ osc.phase = (osc.phase + count) & (wave_size - 1);
blip_time_t inaudible_period = (unsigned) (output->clock_rate() + time += count * period;
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16); }
if ( period > inaudible_period ) else
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15); {
}
int phase = osc.phase;
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size; int last_wave = wave [phase];
/*if ( index == osc_count - 1 ) phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
wave -= wave_size; // last two oscs share same wave RAM*/
do
{ {
int delta = wave [osc.phase] * volume - osc.last_amp; int amp = wave [phase];
if ( delta ) phase = (phase + 1) & (wave_size - 1);
{ int delta = amp - last_wave;
osc.last_amp += delta; if ( delta )
output->set_modified(); {
synth.offset( last_time, delta, output ); last_wave = amp;
} synth.offset( time, delta * volume, output );
} }
time += period;
blip_time_t time = last_time + osc.delay; }
if ( time < end_time ) while ( time < end_time );
{
int phase = osc.phase; osc.phase = phase = (phase - 1) & (wave_size - 1); // undo pre-advance
if ( !volume ) osc.last_amp = wave [phase] * volume;
{ }
// maintain phase }
int count = (end_time - time + period - 1) / period; osc.delay = time - end_time;
phase += count; // will be masked below }
time += count * period; last_time = end_time;
} }
else
{
int last_wave = wave [phase];
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
do
{
int delta = wave [phase] - last_wave;
phase = (phase + 1) & (wave_size - 1);
if ( delta )
{
last_wave += delta;
synth.offset_inline( time, delta * volume, output );
}
time += period;
}
while ( time < end_time );
osc.last_amp = last_wave * volume;
output->set_modified();
phase--; // undo pre-advance
}
osc.phase = phase & (wave_size - 1);
}
osc.delay = time - end_time;
}
last_time = end_time;
}

View file

@ -1,111 +1,106 @@
// Konami SCC sound chip emulator // Konami SCC sound chip emulator
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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"
#include <string.h>
class Scc_Apu {
public: class Scc_Apu {
// Basics public:
// Set buffer to generate all sound into, or disable sound if NULL
// Sets buffer to generate sound into, or 0 to mute. void output( Blip_Buffer* );
void set_output( Blip_Buffer* );
// Reset sound chip
// Emulates to time t, then writes data to reg void reset();
enum { reg_count = 0xB0 }; // 0 <= reg < reg_count
void write( blip_time_t t, int reg, int data ); // Write to register at specified time
enum { reg_count = 0x90 };
// Emulates to time t, then subtracts t from the current time. void write( blip_time_t time, int reg, int data );
// OK if previous write call had time slightly after t.
void end_frame( blip_time_t t ); // Run sound to specified time, end current time frame, then start a new
// time frame at time 0. Time frames have no effect on emulation and each
// More features // can be whatever length is convenient.
void end_frame( blip_time_t length );
// Resets sound chip
void reset(); // Additional features
// Same as set_output(), but for a particular channel // Set sound output of specific oscillator to buffer, where index is
enum { osc_count = 5 }; // 0 to 4. If buffer is NULL, the specified oscillator is muted.
void set_output( int chan, Blip_Buffer* ); enum { osc_count = 5 };
void osc_output( int index, Blip_Buffer* );
// Set overall volume, where 1.0 is normal
void volume( double ); // Set overall volume (default is 1.0)
void volume( double );
// Set treble equalization
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } // Set treble equalization (see documentation)
void treble_eq( blip_eq_t const& );
private:
// noncopyable public:
Scc_Apu( const Scc_Apu& ); Scc_Apu();
Scc_Apu& operator = ( const Scc_Apu& ); private:
enum { amp_range = 0x8000 };
struct osc_t
// Implementation {
public: int delay;
Scc_Apu(); int phase;
BLARGG_DISABLE_NOTHROW int last_amp;
Blip_Buffer* output;
private: };
enum { amp_range = 0x8000 }; osc_t oscs [osc_count];
struct osc_t blip_time_t last_time;
{ unsigned char regs [reg_count];
int delay; Blip_Synth<blip_med_quality,1> synth;
int phase;
int last_amp; void run_until( blip_time_t );
Blip_Buffer* output; };
};
osc_t oscs [osc_count]; inline void Scc_Apu::volume( double v ) { synth.volume( 0.43 / osc_count / amp_range * v ); }
blip_time_t last_time;
unsigned char regs [reg_count]; inline void Scc_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
Blip_Synth_Fast synth;
inline void Scc_Apu::osc_output( int index, Blip_Buffer* b )
void run_until( blip_time_t ); {
}; assert( (unsigned) index < osc_count );
oscs [index].output = b;
inline void Scc_Apu::set_output( int index, Blip_Buffer* b ) }
{
assert( (unsigned) index < osc_count ); inline void Scc_Apu::write( blip_time_t time, int addr, int data )
oscs [index].output = b; {
} assert( (unsigned) addr < reg_count );
run_until( time );
inline void Scc_Apu::write( blip_time_t time, int addr, int data ) regs [addr] = data;
{ }
//assert( (unsigned) addr < reg_count );
assert( ( addr >= 0x9800 && addr <= 0x988F ) || ( addr >= 0xB800 && addr <= 0xB8AF ) ); inline void Scc_Apu::end_frame( blip_time_t end_time )
run_until( time ); {
if ( end_time > last_time )
addr -= 0x9800; run_until( end_time );
if ( ( unsigned ) addr < 0x90 ) last_time -= end_time;
{ assert( last_time >= 0 );
if ( ( unsigned ) addr < 0x60 ) }
regs [addr] = data;
else if ( ( unsigned ) addr < 0x80 ) inline void Scc_Apu::output( Blip_Buffer* buf )
{ {
regs [addr] = regs[addr + 0x20] = data; for ( int i = 0; i < osc_count; i++ )
} oscs [i].output = buf;
else if ( ( unsigned ) addr < 0x90 ) }
{
regs [addr + 0x20] = data; inline Scc_Apu::Scc_Apu()
} {
} output( 0 );
else }
{
addr -= 0xB800 - 0x9800; inline void Scc_Apu::reset()
if ( ( unsigned ) addr < 0xB0 ) {
regs [addr] = data; last_time = 0;
}
} for ( int i = 0; i < osc_count; i++ )
memset( &oscs [i], 0, offsetof (osc_t,output) );
inline void Scc_Apu::end_frame( blip_time_t end_time )
{ memset( regs, 0, sizeof regs );
if ( end_time > last_time ) }
run_until( end_time );
#endif
last_time -= end_time;
assert( last_time >= 0 );
}
#endif

View file

@ -1,476 +1,500 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "M3u_Playlist.h" #include "M3u_Playlist.h"
#include "Music_Emu.h" #include "Music_Emu.h"
/* Copyright (C) 2006 Shay Green. This module is free software; you #include <string.h>
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 /* Copyright (C) 2006 Shay Green. This module is free software; you
version 2.1 of the License, or (at your option) any later version. This can redistribute it and/or modify it under the terms of the GNU Lesser
module is distributed in the hope that it will be useful, but WITHOUT ANY General Public License as published by the Free Software Foundation; either
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS version 2.1 of the License, or (at your option) any later version. This
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more module is distributed in the hope that it will be useful, but WITHOUT ANY
details. You should have received a copy of the GNU Lesser General Public WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
License along with this module; if not, write to the Free Software Foundation, FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ 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,
#include "blargg_source.h" Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
// gme functions defined here to avoid linking in m3u code unless it's used #include "blargg_source.h"
blargg_err_t Gme_File::load_m3u_( blargg_err_t err ) // gme functions defined here to avoid linking in m3u code unless it's used
{
if ( !err ) blargg_err_t Gme_File::load_m3u_( blargg_err_t err )
{ {
require( raw_track_count_ ); // file must be loaded first require( raw_track_count_ ); // file must be loaded first
if ( playlist.size() )
track_count_ = playlist.size(); if ( !err )
{
int line = playlist.first_error(); if ( playlist.size() )
if ( line ) track_count_ = playlist.size();
{
// avoid using bloated printf() int line = playlist.first_error();
char* out = &playlist_warning [sizeof playlist_warning]; if ( line )
*--out = 0; {
do { // avoid using bloated printf()
*--out = line % 10 + '0'; char* out = &playlist_warning [sizeof playlist_warning];
} while ( (line /= 10) > 0 ); *--out = 0;
do {
static const char str [] = "Problem in m3u at line "; *--out = line % 10 + '0';
out -= sizeof str - 1; } while ( (line /= 10) > 0 );
memcpy( out, str, sizeof str - 1 );
set_warning( out ); static const char str [] = "Problem in m3u at line ";
} out -= sizeof str - 1;
} memcpy( out, str, sizeof str - 1 );
return err; set_warning( out );
} }
}
blargg_err_t Gme_File::load_m3u( const char path [] ) { return load_m3u_( playlist.load( path ) ); } return err;
}
blargg_err_t Gme_File::load_m3u( Data_Reader& in ) { return load_m3u_( playlist.load( in ) ); }
blargg_err_t Gme_File::load_m3u( const char* path ) { return load_m3u_( playlist.load( path ) ); }
gme_err_t gme_load_m3u( Music_Emu* me, const char path [] ) { return me->load_m3u( path ); }
blargg_err_t Gme_File::load_m3u( Data_Reader& in ) { return load_m3u_( playlist.load( in ) ); }
gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
{ gme_err_t gme_load_m3u( Music_Emu* me, const char* path ) { return me->load_m3u( path ); }
Mem_File_Reader in( data, size );
return me->load_m3u( in ); gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
} {
Mem_File_Reader in( data, size );
static char* skip_white( char* in ) return me->load_m3u( in );
{ }
while ( unsigned (*in - 1) <= ' ' - 1 )
in++;
return in;
} static char* skip_white( char* in )
{
inline unsigned from_dec( unsigned n ) { return n - '0'; } while ( *in == ' ' )
in++;
static char* parse_filename( char* in, M3u_Playlist::entry_t& entry ) return in;
{ }
entry.file = in;
entry.type = ""; inline unsigned from_dec( unsigned n ) { return n - '0'; }
char* out = in;
while ( 1 ) static char* parse_filename( char* in, M3u_Playlist::entry_t& entry )
{ {
int c = *in; entry.file = in;
if ( !c ) break; entry.type = "";
in++; char* out = in;
while ( 1 )
if ( c == ',' ) // commas in filename {
{ int c = *in;
char* p = skip_white( in ); if ( !c ) break;
if ( *p == '$' || from_dec( *p ) <= 9 ) in++;
{
in = p; if ( c == ',' ) // commas in filename
break; {
} char* p = skip_white( in );
} if ( *p == '$' || from_dec( *p ) <= 9 )
{
if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix in = p;
{ break;
entry.type = ++in; }
while ( (c = *in) != 0 && c != ',' ) }
in++;
if ( c == ',' ) if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix
{ {
*in++ = 0; // terminate type entry.type = ++in;
in = skip_white( in ); while ( (c = *in) != 0 && c != ',' )
} in++;
break; if ( c == ',' )
} {
*in++ = 0; // terminate type
if ( c == '\\' ) // \ prefix for special characters in = skip_white( in );
{ }
c = *in; break;
if ( !c ) break; }
in++;
} if ( c == '\\' ) // \ prefix for special characters
*out++ = (char) c; {
} c = *in;
*out = 0; // terminate string if ( !c ) break;
return in; in++;
} }
*out++ = (char) c;
static char* next_field( char* in, int* result ) }
{ *out = 0; // terminate string
while ( 1 ) return in;
{ }
in = skip_white( in );
static char* next_field( char* in, int* result )
if ( !*in ) {
break; while ( 1 )
{
if ( *in == ',' ) in = skip_white( in );
{
in++; if ( !*in )
break; break;
}
if ( *in == ',' )
*result = 1; {
in++; in++;
} break;
return skip_white( in ); }
}
*result = 1;
static char* parse_int_( char* in, int* out ) in++;
{ }
int n = 0; return skip_white( in );
while ( 1 ) }
{
unsigned d = from_dec( *in ); static char* parse_int_( char* in, int* out )
if ( d > 9 ) {
break; int n = 0;
in++; while ( 1 )
n = n * 10 + d; {
*out = n; unsigned d = from_dec( *in );
} if ( d > 9 )
return in; break;
} in++;
n = n * 10 + d;
static char* parse_int( char* in, int* out, int* result ) *out = n;
{ }
return next_field( parse_int_( in, out ), result ); return in;
} }
// Returns 16 or greater if not hex static char* parse_mil_( char* in, int* out )
inline int from_hex_char( int h ) {
{ int n = 0;
h -= 0x30; int x = 100;
if ( (unsigned) h > 9 ) while ( 1 )
h = ((h - 0x11) & 0xDF) + 10; {
return h; unsigned d = from_dec( *in );
} if ( d > 9 )
break;
static char* parse_track( char* in, M3u_Playlist::entry_t& entry, int* result ) in++;
{ n += d * x;
if ( *in == '$' ) x /= 10;
{ *out = n;
in++; }
int n = 0; return in;
while ( 1 ) }
{
int h = from_hex_char( *in ); static char* parse_int( char* in, int* out, int* result )
if ( h > 15 ) {
break; return next_field( parse_int_( in, out ), result );
in++; }
n = n * 16 + h;
entry.track = n; // Returns 16 or greater if not hex
} inline int from_hex_char( int h )
} {
else h -= 0x30;
{ if ( (unsigned) h > 9 )
in = parse_int_( in, &entry.track ); h = ((h - 0x11) & 0xDF) + 10;
if ( entry.track >= 0 ) return h;
entry.decimal_track = 1; }
}
return next_field( in, result ); static char* parse_track( char* in, M3u_Playlist::entry_t& entry, int* result )
} {
if ( *in == '$' )
static char* parse_time_( char* in, int* out ) {
{ in++;
*out = -1; int n = 0;
int n = -1; while ( 1 )
in = parse_int_( in, &n ); {
if ( n >= 0 ) int h = from_hex_char( *in );
{ if ( h > 15 )
*out = n; break;
while ( *in == ':' ) in++;
{ n = n * 16 + h;
n = -1; entry.track = n;
in = parse_int_( in + 1, &n ); }
if ( n >= 0 ) }
*out = *out * 60 + n; else
} {
*out *= 1000; in = parse_int_( in, &entry.track );
if ( *in == '.' ) if ( entry.track >= 0 )
{ entry.decimal_track = 1;
n = -1; }
in = parse_int_( in + 1, &n ); return next_field( in, result );
if ( n >= 0 ) }
*out = *out + n;
} static char* parse_time_( char* in, int* out )
} {
return in; *out = -1;
} int n = -1;
in = parse_int_( in, &n );
static char* parse_time( char* in, int* out, int* result ) if ( n >= 0 )
{ {
return next_field( parse_time_( in, out ), result ); *out = n;
} while ( *in == ':' )
{
static char* parse_name( char* in ) n = -1;
{ in = parse_int_( in + 1, &n );
char* out = in; if ( n >= 0 )
while ( 1 ) *out = *out * 60 + n;
{ }
int c = *in; *out *= 1000;
if ( !c ) break;
in++; if ( *in == '.' )
{
if ( c == ',' ) // commas in string n = -1;
{ in = parse_mil_( in + 1, &n );
char* p = skip_white( in ); if ( n >= 0 )
if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 ) *out += n;
{ }
in = p; }
break; return in;
} }
}
static char* parse_time( char* in, int* out, int* result )
if ( c == '\\' ) // \ prefix for special characters {
{ return next_field( parse_time_( in, out ), result );
c = *in; }
if ( !c ) break;
in++; static char* parse_name( char* in )
} {
*out++ = (char) c; char* out = in;
} while ( 1 )
*out = 0; // terminate string {
return in; int c = *in;
} if ( !c ) break;
in++;
static int parse_line( char* in, M3u_Playlist::entry_t& entry )
{ if ( c == ',' ) // commas in string
int result = 0; {
char* p = skip_white( in );
// file if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 )
entry.file = in; {
entry.type = ""; in = p;
in = parse_filename( in, entry ); break;
}
// track }
entry.track = -1;
entry.decimal_track = 0; if ( c == '\\' ) // \ prefix for special characters
in = parse_track( in, entry, &result ); {
c = *in;
// name if ( !c ) break;
entry.name = in; in++;
in = parse_name( in ); }
*out++ = (char) c;
// time }
entry.length = -1; *out = 0; // terminate string
in = parse_time( in, &entry.length, &result ); return in;
}
// loop
entry.intro = -1; static int parse_line( char* in, M3u_Playlist::entry_t& entry )
entry.loop = -1; {
if ( *in == '-' ) int result = 0;
{
entry.loop = entry.length; // file
in++; entry.file = in;
} entry.type = "";
else in = parse_filename( in, entry );
{
in = parse_time_( in, &entry.loop ); // track
if ( entry.loop >= 0 ) entry.track = -1;
{ entry.decimal_track = 0;
entry.intro = entry.length - entry.loop; in = parse_track( in, entry, &result );
if ( *in == '-' ) // trailing '-' means that intro length was specified
{ // name
in++; entry.name = in;
entry.intro = entry.loop; in = parse_name( in );
entry.loop = entry.length - entry.intro;
} // time
} entry.length = -1;
} in = parse_time( in, &entry.length, &result );
in = next_field( in, &result );
// loop
// fade entry.intro = -1;
entry.fade = -1; entry.loop = -1;
in = parse_time( in, &entry.fade, &result ); if ( *in == '-' )
{
// repeat entry.loop = entry.length;
entry.repeat = -1; in++;
in = parse_int( in, &entry.repeat, &result ); }
else
return result; {
} in = parse_time_( in, &entry.loop );
if ( entry.loop >= 0 )
static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_comment_value, bool first ) {
{ entry.intro = 0;
in = skip_white( in + 1 ); if ( *in == '-' ) // trailing '-' means that intro length was specified
const char* field = in; {
if ( *field != '@' ) in++;
while ( *in && *in != ':' ) entry.intro = entry.loop;
in++; entry.loop = entry.length - entry.intro;
}
if ( *in == ':' ) }
{ }
const char* text = skip_white( in + 1 ); in = next_field( in, &result );
if ( *text )
{ // fade
*in = 0; entry.fade = -1;
if ( !strcmp( "Composer" , field ) ) info.composer = text; in = parse_time( in, &entry.fade, &result );
else if ( !strcmp( "Engineer" , field ) ) info.engineer = text;
else if ( !strcmp( "Ripping" , field ) ) info.ripping = text; // repeat
else if ( !strcmp( "Tagging" , field ) ) info.tagging = text; entry.repeat = -1;
else if ( !strcmp( "Game" , field ) ) info.title = text; in = parse_int( in, &entry.repeat, &result );
else if ( !strcmp( "Artist" , field ) ) info.artist = text;
else if ( !strcmp( "Copyright", field ) ) info.copyright = text; return result;
else }
text = 0;
if ( text ) static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_comment_value, bool first )
return; {
*in = ':'; in = skip_white( in + 1 );
} const char* field = in;
} if ( *field != '@' )
else if ( *field == '@' ) while ( *in && *in != ':' )
{ in++;
++field;
in = (char*)field; if ( *in == ':' )
while ( *in && *in > ' ' ) {
in++; const char* text = skip_white( in + 1 );
const char* text = skip_white( in ); if ( *text )
if ( *text ) {
{ *in = 0;
char saved = *in; if ( !strcmp( "Composer" , field ) ) info.composer = text;
*in = 0; else if ( !strcmp( "Engineer" , field ) ) info.engineer = text;
if ( !strcmp( "TITLE" , field ) ) info.title = text; else if ( !strcmp( "Ripping" , field ) ) info.ripping = text;
else if ( !strcmp( "ARTIST", field ) ) info.artist = text; else if ( !strcmp( "Tagging" , field ) ) info.tagging = text;
else if ( !strcmp( "DATE", field ) ) info.date = text; else if ( !strcmp( "Game" , field ) ) info.title = text;
else if ( !strcmp( "COMPOSER", field ) ) info.composer = text; else if ( !strcmp( "Artist" , field ) ) info.artist = text;
else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text; else if ( !strcmp( "Copyright", field ) ) info.copyright = text;
else if ( !strcmp( "ENGINEER", field ) ) info.engineer = text; else
else if ( !strcmp( "RIPPER", field ) ) info.ripping = text; text = 0;
else if ( !strcmp( "TAGGER", field ) ) info.tagging = text; if ( text )
else return;
text = 0; *in = ':';
if ( text ) }
{ }
last_comment_value = (char*)text; else if ( *field == '@' )
return; {
} ++field;
*in = saved; in = (char*)field;
} while ( *in && *in > ' ' )
} in++;
else if ( last_comment_value ) const char* text = skip_white( in );
{ if ( *text )
size_t len = strlen( last_comment_value ); {
last_comment_value[ len ] = ','; char saved = *in;
last_comment_value[ len + 1 ] = ' '; *in = 0;
size_t field_len = strlen( field ); if ( !strcmp( "TITLE" , field ) ) info.title = text;
memmove( last_comment_value + len + 2, field, field_len ); else if ( !strcmp( "ARTIST" , field ) ) info.artist = text;
last_comment_value[ len + 2 + field_len ] = 0; else if ( !strcmp( "DATE" , field ) ) info.date = text;
return; else if ( !strcmp( "COMPOSER" , field ) ) info.composer = text;
} else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text;
else if ( !strcmp( "ENGINEER" , field ) ) info.engineer = text;
if ( first ) else if ( !strcmp( "RIPPER" , field ) ) info.ripping = text;
info.title = field; else if ( !strcmp( "TAGGER" , field ) ) info.tagging = text;
} else
text = 0;
blargg_err_t M3u_Playlist::parse_() if ( text )
{ {
info_.title = ""; last_comment_value = (char*)text;
info_.artist = ""; return;
info_.date = ""; }
info_.composer = ""; }
info_.sequencer = ""; }
info_.engineer = ""; else if ( last_comment_value )
info_.ripping = ""; {
info_.tagging = ""; size_t len = strlen( last_comment_value );
info_.copyright = ""; last_comment_value[ len ] = ',';
last_comment_value[ len + 1 ] = ' ';
int const CR = 13; size_t field_len = strlen( field );
int const LF = 10; memmove( last_comment_value + len + 2, field, field_len );
last_comment_value[ len + 2 + field_len ] = 0;
data.end() [-1] = LF; // terminate input return;
}
first_error_ = 0;
bool first_comment = true; if ( first )
int line = 0; info.title = field;
int count = 0; }
char* in = data.begin();
char* last_comment_value = 0; blargg_err_t M3u_Playlist::parse_()
while ( in < data.end() ) {
{ info_.title = "";
// find end of line and terminate it info_.artist = "";
line++; info_.date = "";
char* begin = in; info_.composer = "";
while ( *in != CR && *in != LF ) info_.sequencer = "";
{ info_.engineer = "";
if ( !*in ) info_.ripping = "";
return blargg_err_file_type; info_.tagging = "";
in++; info_.copyright = "";
}
if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line int const CR = 13;
*in++ = 0; int const LF = 10;
*in++ = 0;
data.end() [-1] = LF; // terminate input
// parse line
if ( *begin == '#' ) first_error_ = 0;
{ bool first_comment = true;
parse_comment( begin, info_, last_comment_value, first_comment ); int line = 0;
first_comment = false; int count = 0;
} char* in = data.begin();
else if ( *begin ) char* last_comment_value = 0;
{ while ( in < data.end() )
if ( (int) entries.size() <= count ) {
RETURN_ERR( entries.resize( count * 2 + 64 ) ); // find end of line and terminate it
line++;
if ( !parse_line( begin, entries [count] ) ) char* begin = in;
count++; while ( *in != CR && *in != LF )
else if ( !first_error_ ) {
first_error_ = line; if ( !*in )
first_comment = false; return "Not an m3u playlist";
} in++;
else last_comment_value = 0; }
} if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line
if ( count <= 0 ) *in++ = 0;
return blargg_err_file_type; *in++ = 0;
// Treat first comment as title only if another field is also specified // parse line
if ( !(info_.artist [0] | info_.composer [0] | info_.date [0] | info_.engineer [0] | info_.ripping [0] | info_.sequencer [0] | info_.tagging [0] | info_.copyright[0]) ) if ( *begin == '#' )
info_.title = ""; {
parse_comment( begin, info_, last_comment_value, first_comment );
return entries.resize( count ); first_comment = false;
} }
else if ( *begin )
blargg_err_t M3u_Playlist::parse() {
{ if ( (int) entries.size() <= count )
blargg_err_t err = parse_(); RETURN_ERR( entries.resize( count * 2 + 64 ) );
if ( err )
clear_(); if ( !parse_line( begin, entries [count] ) )
return err; count++;
} else if ( !first_error_ )
first_error_ = line;
blargg_err_t M3u_Playlist::load( Data_Reader& in ) first_comment = false;
{ }
RETURN_ERR( data.resize( in.remain() + 1 ) ); else last_comment_value = 0;
RETURN_ERR( in.read( data.begin(), data.size() - 1 ) ); }
return parse(); if ( count <= 0 )
} return "Not an m3u playlist";
blargg_err_t M3u_Playlist::load( const char path [] ) if ( !(info_.composer [0] | info_.engineer [0] | info_.ripping [0] | info_.tagging [0]) )
{ info_.title = "";
GME_FILE_READER in;
RETURN_ERR( in.open( path ) ); return entries.resize( count );
return load( in ); }
}
blargg_err_t M3u_Playlist::parse()
blargg_err_t M3u_Playlist::load( void const* in, long size ) {
{ blargg_err_t err = parse_();
RETURN_ERR( data.resize( size + 1 ) ); if ( err )
memcpy( data.begin(), in, size ); {
return parse(); entries.clear();
} data.clear();
}
return err;
}
blargg_err_t M3u_Playlist::load( Data_Reader& in )
{
RETURN_ERR( data.resize( in.remain() + 1 ) );
RETURN_ERR( in.read( data.begin(), data.size() - 1 ) );
return parse();
}
blargg_err_t M3u_Playlist::load( const char* path )
{
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
return load( in );
}
blargg_err_t M3u_Playlist::load( void const* in, long size )
{
RETURN_ERR( data.resize( size + 1 ) );
memcpy( data.begin(), in, size );
return parse();
}

View file

@ -1,87 +1,71 @@
// M3U playlist file parser, with support for subtrack information // M3U playlist file parser, with support for subtrack information
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef M3U_PLAYLIST_H #ifndef M3U_PLAYLIST_H
#define M3U_PLAYLIST_H #define M3U_PLAYLIST_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Data_Reader.h" #include "Data_Reader.h"
class M3u_Playlist { class M3u_Playlist {
public: public:
// Load playlist data // Load playlist data
blargg_err_t load( const char* path ); blargg_err_t load( const char* path );
blargg_err_t load( Data_Reader& in ); blargg_err_t load( Data_Reader& in );
blargg_err_t load( void const* data, long size ); blargg_err_t load( void const* data, long size );
// Line number of first parse error, 0 if no error. Any lines with parse // Line number of first parse error, 0 if no error. Any lines with parse
// errors are ignored. // errors are ignored.
int first_error() const { return first_error_; } int first_error() const { return first_error_; }
// All string pointers point to valid string, or "" if not available struct info_t
struct info_t {
{ const char* title;
const char* title; const char* artist;
const char* artist; const char* date;
const char* date; const char* composer;
const char* composer; const char* sequencer;
const char* sequencer; const char* engineer;
const char* engineer; const char* ripping;
const char* ripping; const char* tagging;
const char* tagging; const char* copyright;
const char* copyright; };
}; info_t const& info() const { return info_; }
info_t const& info() const { return info_; }
struct entry_t
struct entry_t {
{ const char* file; // filename without stupid ::TYPE suffix
const char* file; // filename without stupid ::TYPE suffix const char* type; // if filename has ::TYPE suffix, this will be "TYPE". "" if none.
const char* type; // if filename has ::TYPE suffix, this is "TYPE", otherwise "" const char* name;
const char* name; bool decimal_track; // true if track was specified in hex
bool decimal_track; // true if track was specified in decimal // integers are -1 if not present
// integers are -1 if not present int track; // 1-based
int track; int length; // milliseconds
int length; // milliseconds int intro;
int intro; int loop;
int loop; int fade;
int fade; int repeat; // count
int repeat; // count };
}; entry_t const& operator [] ( int i ) const { return entries [i]; }
entry_t const& operator [] ( int i ) const { return entries [i]; } int size() const { return entries.size(); }
int size() const { return entries.size(); }
void clear();
void clear();
private:
private: blargg_vector<entry_t> entries;
blargg_vector<entry_t> entries; blargg_vector<char> data;
blargg_vector<char> data; int first_error_;
int first_error_; info_t info_;
info_t info_;
blargg_err_t parse();
blargg_err_t parse(); blargg_err_t parse_();
blargg_err_t parse_(); };
void clear_();
}; inline void M3u_Playlist::clear()
{
inline void M3u_Playlist::clear_() first_error_ = 0;
{ entries.clear();
info_.title = ""; data.clear();
info_.artist = ""; }
info_.date = "";
info_.composer = ""; #endif
info_.sequencer = "";
info_.engineer = "";
info_.ripping = "";
info_.tagging = "";
info_.copyright = "";
entries.clear();
data.clear();
}
inline void M3u_Playlist::clear()
{
first_error_ = 0;
clear_();
}
#endif

View file

@ -1,290 +1,232 @@
// Blip_Buffer $vers. http://www.slack.net/~ant/ // Blip_Buffer 0.4.1. http://www.slack.net/~ant/
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
/* Copyright (C) 2003-2008 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"
Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) #ifdef BLARGG_ENABLE_OPTIMIZER
{ #include BLARGG_ENABLE_OPTIMIZER
length_ = 0; #endif
sample_rate_ = 0;
channels_changed_count_ = 1; Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf )
channel_types_ = NULL; {
channel_count_ = 0; length_ = 0;
immediate_removal_ = true; sample_rate_ = 0;
} channels_changed_count_ = 1;
}
Multi_Buffer::channel_t Multi_Buffer::channel( int /*index*/ )
{ blargg_err_t Multi_Buffer::set_channel_count( int ) { return 0; }
channel_t ch;
ch.center = ch.left = ch.right = NULL; // Silent_Buffer
return ch;
} Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse
{
// Silent_Buffer // TODO: better to use empty Blip_Buffer so caller never has to check for NULL?
chan.left = 0;
Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse chan.center = 0;
{ chan.right = 0;
// TODO: better to use empty Blip_Buffer so caller never has to check for NULL? }
chan.left = NULL;
chan.center = NULL; // Mono_Buffer
chan.right = NULL;
} Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 )
{
// Mono_Buffer chan.center = &buf;
chan.left = &buf;
Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) chan.right = &buf;
{ }
chan.center = &buf;
chan.left = &buf; Mono_Buffer::~Mono_Buffer() { }
chan.right = &buf;
} blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec )
{
Mono_Buffer::~Mono_Buffer() { } RETURN_ERR( buf.set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() );
blargg_err_t Mono_Buffer::set_sample_rate( int rate, int msec ) }
{
RETURN_ERR( buf.set_sample_rate( rate, msec ) ); // Stereo_Buffer
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() );
} Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 )
{
chan.center = &bufs [0];
// Tracked_Blip_Buffer chan.left = &bufs [1];
chan.right = &bufs [2];
int const blip_buffer_extra = 32; // TODO: explain why this value }
Tracked_Blip_Buffer::Tracked_Blip_Buffer() Stereo_Buffer::~Stereo_Buffer() { }
{
last_non_silence = 0; blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec )
} {
for ( int i = 0; i < buf_count; i++ )
void Tracked_Blip_Buffer::clear() RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
{ return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
last_non_silence = 0; }
Blip_Buffer::clear();
} void Stereo_Buffer::clock_rate( long rate )
{
void Tracked_Blip_Buffer::end_frame( blip_time_t t ) for ( int i = 0; i < buf_count; i++ )
{ bufs [i].clock_rate( rate );
Blip_Buffer::end_frame( t ); }
if ( modified() )
{ void Stereo_Buffer::bass_freq( int bass )
clear_modified(); {
last_non_silence = samples_avail() + blip_buffer_extra; for ( int i = 0; i < buf_count; i++ )
} bufs [i].bass_freq( bass );
} }
unsigned Tracked_Blip_Buffer::non_silent() const void Stereo_Buffer::clear()
{ {
return last_non_silence | unsettled(); stereo_added = 0;
} was_stereo = false;
for ( int i = 0; i < buf_count; i++ )
inline void Tracked_Blip_Buffer::remove_( int n ) bufs [i].clear();
{ }
if ( (last_non_silence -= n) < 0 )
last_non_silence = 0; void Stereo_Buffer::end_frame( blip_time_t clock_count )
} {
stereo_added = 0;
void Tracked_Blip_Buffer::remove_silence( int n ) for ( int i = 0; i < buf_count; i++ )
{ {
remove_( n ); stereo_added |= bufs [i].clear_modified() << i;
Blip_Buffer::remove_silence( n ); bufs [i].end_frame( clock_count );
} }
}
void Tracked_Blip_Buffer::remove_samples( int n )
{ long Stereo_Buffer::read_samples( blip_sample_t* out, long count )
remove_( n ); {
Blip_Buffer::remove_samples( n ); require( !(count & 1) ); // count must be even
} count = (unsigned) count / 2;
void Tracked_Blip_Buffer::remove_all_samples() long avail = bufs [0].samples_avail();
{ if ( count > avail )
int avail = samples_avail(); count = avail;
if ( !non_silent() ) if ( count )
remove_silence( avail ); {
else int bufs_used = stereo_added | was_stereo;
remove_samples( avail ); //debug_printf( "%X\n", bufs_used );
} if ( bufs_used <= 1 )
{
int Tracked_Blip_Buffer::read_samples( blip_sample_t out [], int count ) mix_mono( out, count );
{ bufs [0].remove_samples( count );
count = Blip_Buffer::read_samples( out, count ); bufs [1].remove_silence( count );
remove_( count ); bufs [2].remove_silence( count );
return count; }
} else if ( bufs_used & 1 )
{
// Stereo_Buffer mix_stereo( out, count );
bufs [0].remove_samples( count );
int const stereo = 2; bufs [1].remove_samples( count );
bufs [2].remove_samples( count );
Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) }
{ else
chan.center = mixer.bufs [2] = &bufs [2]; {
chan.left = mixer.bufs [0] = &bufs [0]; mix_stereo_no_center( out, count );
chan.right = mixer.bufs [1] = &bufs [1]; bufs [0].remove_silence( count );
mixer.samples_read = 0; bufs [1].remove_samples( count );
} bufs [2].remove_samples( count );
}
Stereo_Buffer::~Stereo_Buffer() { }
// to do: this might miss opportunities for optimization
blargg_err_t Stereo_Buffer::set_sample_rate( int rate, int msec ) if ( !bufs [0].samples_avail() )
{ {
mixer.samples_read = 0; was_stereo = stereo_added;
for ( int i = bufs_size; --i >= 0; ) stereo_added = 0;
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); }
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); }
}
return count * 2;
void Stereo_Buffer::clock_rate( int rate ) }
{
for ( int i = bufs_size; --i >= 0; ) void Stereo_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count )
bufs [i].clock_rate( rate ); {
} blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [1] );
void Stereo_Buffer::bass_freq( int bass ) BLIP_READER_BEGIN( left, bufs [1] );
{ BLIP_READER_BEGIN( right, bufs [2] );
for ( int i = bufs_size; --i >= 0; ) BLIP_READER_BEGIN( center, bufs [0] );
bufs [i].bass_freq( bass );
} for ( ; count; --count )
{
void Stereo_Buffer::clear() int c = BLIP_READER_READ( center );
{ blargg_long l = c + BLIP_READER_READ( left );
mixer.samples_read = 0; blargg_long r = c + BLIP_READER_READ( right );
for ( int i = bufs_size; --i >= 0; ) if ( (int16_t) l != l )
bufs [i].clear(); l = 0x7FFF - (l >> 24);
}
BLIP_READER_NEXT( center, bass );
void Stereo_Buffer::end_frame( blip_time_t time ) if ( (int16_t) r != r )
{ r = 0x7FFF - (r >> 24);
for ( int i = bufs_size; --i >= 0; )
bufs [i].end_frame( time ); BLIP_READER_NEXT( left, bass );
} BLIP_READER_NEXT( right, bass );
int Stereo_Buffer::read_samples( blip_sample_t out [], int out_size ) out [0] = l;
{ out [1] = r;
require( (out_size & 1) == 0 ); // must read an even number of samples out += 2;
out_size = min( out_size, samples_avail() ); }
int pair_count = int (out_size >> 1); BLIP_READER_END( center, bufs [0] );
if ( pair_count ) BLIP_READER_END( right, bufs [2] );
{ BLIP_READER_END( left, bufs [1] );
mixer.read_pairs( out, pair_count ); }
if ( samples_avail() <= 0 || immediate_removal() ) void Stereo_Buffer::mix_stereo_no_center( blip_sample_t* out_, blargg_long count )
{ {
for ( int i = bufs_size; --i >= 0; ) blip_sample_t* BLIP_RESTRICT out = out_;
{ int const bass = BLIP_READER_BASS( bufs [1] );
buf_t& b = bufs [i]; BLIP_READER_BEGIN( left, bufs [1] );
// TODO: might miss non-silence settling since it checks END of last read BLIP_READER_BEGIN( right, bufs [2] );
if ( !b.non_silent() )
b.remove_silence( mixer.samples_read ); for ( ; count; --count )
else {
b.remove_samples( mixer.samples_read ); blargg_long l = BLIP_READER_READ( left );
} if ( (int16_t) l != l )
mixer.samples_read = 0; l = 0x7FFF - (l >> 24);
}
} blargg_long r = BLIP_READER_READ( right );
return out_size; if ( (int16_t) r != r )
} r = 0x7FFF - (r >> 24);
BLIP_READER_NEXT( left, bass );
// Stereo_Mixer BLIP_READER_NEXT( right, bass );
// mixers use a single index value to improve performance on register-challenged processors out [0] = l;
// offset goes from negative to zero out [1] = r;
out += 2;
void Stereo_Mixer::read_pairs( blip_sample_t out [], int count ) }
{
// TODO: if caller never marks buffers as modified, uses mono BLIP_READER_END( right, bufs [2] );
// except that buffer isn't cleared, so caller can encounter BLIP_READER_END( left, bufs [1] );
// subtle problems and not realize the cause. }
samples_read += count;
if ( bufs [0]->non_silent() | bufs [1]->non_silent() ) void Stereo_Buffer::mix_mono( blip_sample_t* out_, blargg_long count )
mix_stereo( out, count ); {
else blip_sample_t* BLIP_RESTRICT out = out_;
mix_mono( out, count ); int const bass = BLIP_READER_BASS( bufs [0] );
} BLIP_READER_BEGIN( center, bufs [0] );
void Stereo_Mixer::mix_mono( blip_sample_t out_ [], int count ) for ( ; count; --count )
{ {
int const bass = bufs [2]->highpass_shift(); blargg_long s = BLIP_READER_READ( center );
Blip_Buffer::delta_t const* center = bufs [2]->read_pos() + samples_read; if ( (int16_t) s != s )
int center_sum = bufs [2]->integrator(); s = 0x7FFF - (s >> 24);
typedef blip_sample_t stereo_blip_sample_t [stereo]; BLIP_READER_NEXT( center, bass );
stereo_blip_sample_t* BLARGG_RESTRICT out = (stereo_blip_sample_t*) out_ + count; out [0] = s;
int offset = -count; out [1] = s;
do out += 2;
{ }
int s = center_sum >> bufs [2]->delta_bits;
BLIP_READER_END( center, bufs [0] );
center_sum -= center_sum >> bass; }
center_sum += center [offset];
BLIP_CLAMP( s, s );
out [offset] [0] = (blip_sample_t) s;
out [offset] [1] = (blip_sample_t) s;
}
while ( ++offset );
bufs [2]->set_integrator( center_sum );
}
void Stereo_Mixer::mix_stereo( blip_sample_t out_ [], int count )
{
blip_sample_t* BLARGG_RESTRICT out = out_ + count * stereo;
// do left + center and right + center separately to reduce register load
Tracked_Blip_Buffer* const* buf = &bufs [2];
while ( true ) // loop runs twice
{
--buf;
--out;
int const bass = bufs [2]->highpass_shift();
Blip_Buffer::delta_t const* side = (*buf)->read_pos() + samples_read;
Blip_Buffer::delta_t const* center = bufs [2]->read_pos() + samples_read;
int side_sum = (*buf)->integrator();
int center_sum = bufs [2]->integrator();
int offset = -count;
do
{
int s = (center_sum + side_sum) >> Blip_Buffer::delta_bits;
side_sum -= side_sum >> bass;
center_sum -= center_sum >> bass;
side_sum += side [offset];
center_sum += center [offset];
BLIP_CLAMP( s, s );
++offset; // before write since out is decremented to slightly before end
out [offset * stereo] = (blip_sample_t) s;
}
while ( offset );
(*buf)->set_integrator( side_sum );
if ( buf != bufs )
continue;
// only end center once
bufs [2]->set_integrator( center_sum );
break;
}
}

View file

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

View file

@ -1,244 +1,455 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Music_Emu.h" #include "Music_Emu.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include "Multi_Buffer.h"
can redistribute it and/or modify it under the terms of the GNU Lesser #include <string.h>
General Public License as published by the Free Software Foundation; either #include <algorithm>
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 /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS can redistribute it and/or modify it under the terms of the GNU Lesser
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more General Public License as published by the Free Software Foundation; either
details. You should have received a copy of the GNU Lesser General Public version 2.1 of the License, or (at your option) any later version. This
License along with this module; if not, write to the Free Software Foundation, module is distributed in the hope that it will be useful, but WITHOUT ANY
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
#include "blargg_source.h" 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,
int const stereo = 2; // number of channels for stereo Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180, 0,0,0,0,0,0,0,0 }; #include "blargg_source.h"
void Music_Emu::clear_track_vars() int const silence_max = 6; // seconds
{ int const silence_threshold = 0x10;
current_track_ = -1; long const fade_block_size = 512;
warning(); // clear warning int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift)
track_filter.stop();
} using std::min;
using std::max;
void Music_Emu::unload()
{ Music_Emu::equalizer_t const Music_Emu::tv_eq =
voice_count_ = 0; Music_Emu::make_equalizer( -8.0, 180 );
clear_track_vars();
Gme_File::unload(); void Music_Emu::clear_track_vars()
} {
current_track_ = -1;
Music_Emu::gme_t() out_time = 0;
{ emu_time = 0;
effects_buffer_ = NULL; emu_track_ended_ = true;
sample_rate_ = 0; track_ended_ = true;
mute_mask_ = 0; fade_start = INT_MAX / 2 + 1;
tempo_ = 1.0; fade_step = 1;
gain_ = 1.0; silence_time = 0;
silence_count = 0;
fade_set = false; buf_remain = 0;
warning(); // clear warning
// defaults }
tfilter = track_filter.setup();
set_max_initial_silence( 15 ); void Music_Emu::unload()
set_silence_lookahead( 3 ); {
ignore_silence( false ); voice_count_ = 0;
clear_track_vars();
equalizer_.treble = -1.0; Gme_File::unload();
equalizer_.bass = 60; }
static const char* const names [] = { Music_Emu::Music_Emu()
"Voice 1", "Voice 2", "Voice 3", "Voice 4", {
"Voice 5", "Voice 6", "Voice 7", "Voice 8" effects_buffer = 0;
}; multi_channel_ = false;
set_voice_names( names ); sample_rate_ = 0;
Music_Emu::unload(); // clears fields mute_mask_ = 0;
} tempo_ = 1.0;
gain_ = 1.0;
Music_Emu::~gme_t()
{ // defaults
assert( !effects_buffer_ ); max_initial_silence = 2;
} silence_lookahead = 3;
ignore_silence_ = false;
blargg_err_t Music_Emu::set_sample_rate( int rate ) equalizer_.treble = -1.0;
{ equalizer_.bass = 60;
require( !sample_rate() ); // sample rate can't be changed once set
RETURN_ERR( set_sample_rate_( rate ) ); emu_autoload_playback_limit_ = true;
RETURN_ERR( track_filter.init( this ) );
sample_rate_ = rate; static const char* const names [] = {
tfilter.max_silence = 6 * stereo * sample_rate(); "Voice 1", "Voice 2", "Voice 3", "Voice 4",
return blargg_ok; "Voice 5", "Voice 6", "Voice 7", "Voice 8"
} };
set_voice_names( names );
void Music_Emu::pre_load() Music_Emu::unload(); // non-virtual
{ }
require( sample_rate() ); // set_sample_rate() must be called before loading a file
Gme_File::pre_load(); Music_Emu::~Music_Emu() { delete effects_buffer; }
}
blargg_err_t Music_Emu::set_sample_rate( long rate )
void Music_Emu::set_equalizer( equalizer_t const& eq ) {
{ require( !sample_rate() ); // sample rate can't be changed once set
// TODO: why is GCC generating memcpy call here? RETURN_ERR( set_sample_rate_( rate ) );
// Without the 'if', valgrind flags it. RETURN_ERR( buf.resize( buf_size ) );
if ( &eq != &equalizer_ ) sample_rate_ = rate;
equalizer_ = eq; return 0;
set_equalizer_( eq ); }
}
void Music_Emu::pre_load()
void Music_Emu::mute_voice( int index, bool mute ) {
{ require( sample_rate() ); // set_sample_rate() must be called before loading a file
require( (unsigned) index < (unsigned) voice_count() ); Gme_File::pre_load();
int bit = 1 << index; }
int mask = mute_mask_ | bit;
if ( !mute ) void Music_Emu::set_equalizer( equalizer_t const& eq )
mask ^= bit; {
mute_voices( mask ); equalizer_ = eq;
} set_equalizer_( eq );
}
void Music_Emu::mute_voices( int mask )
{ bool Music_Emu::multi_channel() const
require( sample_rate() ); // sample rate must be set first {
mute_mask_ = mask; return this->multi_channel_;
mute_voices_( mask ); }
}
blargg_err_t Music_Emu::set_multi_channel( bool )
const char* Music_Emu::voice_name( int i ) const {
{ // by default not supported, derived may override this
if ( (unsigned) i < (unsigned) voice_count_ ) return "unsupported for this emulator type";
return voice_names_ [i]; }
//check( false ); // TODO: enable? blargg_err_t Music_Emu::set_multi_channel_( bool isEnabled )
return ""; {
} // multi channel support must be set at the very beginning
require( !sample_rate() );
void Music_Emu::set_tempo( double t ) multi_channel_ = isEnabled;
{ return 0;
require( sample_rate() ); // sample rate must be set first }
double const min = 0.02;
double const max = 4.00; void Music_Emu::mute_voice( int index, bool mute )
if ( t < min ) t = min; {
if ( t > max ) t = max; require( (unsigned) index < (unsigned) voice_count() );
tempo_ = t; int bit = 1 << index;
set_tempo_( t ); int mask = mute_mask_ | bit;
} if ( !mute )
mask ^= bit;
blargg_err_t Music_Emu::post_load() mute_voices( mask );
{ }
set_tempo( tempo_ );
remute_voices(); void Music_Emu::mute_voices( int mask )
return Gme_File::post_load(); {
} require( sample_rate() ); // sample rate must be set first
mute_mask_ = mask;
// Tell/Seek mute_voices_( mask );
}
int Music_Emu::msec_to_samples( int msec ) const
{ void Music_Emu::set_tempo( double t )
int sec = msec / 1000; {
msec -= sec * 1000; require( sample_rate() ); // sample rate must be set first
return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo; double const min = 0.02;
} double const max = 4.00;
if ( t < min ) t = min;
int Music_Emu::tell() const if ( t > max ) t = max;
{ tempo_ = t;
int rate = sample_rate() * stereo; set_tempo_( t );
int sec = track_filter.sample_count() / rate; }
return sec * 1000 + (track_filter.sample_count() - sec * rate) * 1000 / rate;
} void Music_Emu::post_load_()
{
blargg_err_t Music_Emu::seek( int msec ) set_tempo( tempo_ );
{ remute_voices();
int time = msec_to_samples( msec ); }
if ( time < track_filter.sample_count() )
{ blargg_err_t Music_Emu::start_track( int track )
RETURN_ERR( start_track( current_track_ ) ); {
if ( fade_set ) clear_track_vars();
set_fade( length_msec, fade_msec );
} int remapped = track;
return skip( time - track_filter.sample_count() ); RETURN_ERR( remap_track_( &remapped ) );
} current_track_ = track;
RETURN_ERR( start_track_( remapped ) );
blargg_err_t Music_Emu::skip( int count )
{ emu_track_ended_ = false;
require( current_track() >= 0 ); // start_track() must have been called already track_ended_ = false;
return track_filter.skip( count );
} if ( !ignore_silence_ )
{
blargg_err_t Music_Emu::skip_( int count ) // play until non-silence or end of track
{ for ( long end = max_initial_silence * out_channels() * sample_rate(); emu_time < end; )
// for long skip, mute sound {
const int threshold = 32768; fill_buf();
if ( count > threshold ) if ( buf_remain | (int) emu_track_ended_ )
{ break;
int saved_mute = mute_mask_; }
mute_voices( ~0 );
emu_time = buf_remain;
int n = count - threshold/2; out_time = 0;
n &= ~(2048-1); // round to multiple of 2048 silence_time = 0;
count -= n; silence_count = 0;
RETURN_ERR( track_filter.skip_( n ) ); }
return track_ended() ? warning() : 0;
mute_voices( saved_mute ); }
}
void Music_Emu::end_track_if_error( blargg_err_t err )
return track_filter.skip_( count ); {
} if ( err )
{
// Playback emu_track_ended_ = true;
set_warning( err );
blargg_err_t Music_Emu::start_track( int track ) }
{ }
clear_track_vars();
bool Music_Emu::autoload_playback_limit() const
int remapped = track; {
RETURN_ERR( remap_track_( &remapped ) ); return emu_autoload_playback_limit_;
current_track_ = track; }
blargg_err_t err = start_track_( remapped );
if ( err ) void Music_Emu::set_autoload_playback_limit( bool do_autoload_limit )
{ {
current_track_ = -1; emu_autoload_playback_limit_ = do_autoload_limit;
return err; }
}
// Tell/Seek
// convert filter times to samples
Track_Filter::setup_t s = tfilter; blargg_long Music_Emu::msec_to_samples( blargg_long msec ) const
s.max_initial *= sample_rate() * stereo; {
#if GME_DISABLE_SILENCE_LOOKAHEAD blargg_long sec = msec / 1000;
s.lookahead = 1; msec -= sec * 1000;
#endif return (sec * sample_rate() + msec * sample_rate() / 1000) * out_channels();
track_filter.setup( s ); }
return track_filter.start_track(); long Music_Emu::tell_samples() const
} {
return out_time;
void Music_Emu::set_fade( int start_msec, int length_msec ) }
{
fade_set = true; long Music_Emu::tell() const
this->length_msec = start_msec; {
this->fade_msec = length_msec; blargg_long rate = sample_rate() * out_channels();
track_filter.set_fade( start_msec < 0 ? Track_Filter::indefinite_count : msec_to_samples( start_msec ), blargg_long sec = out_time / rate;
length_msec * sample_rate() / (1000 / stereo) ); return sec * 1000 + (out_time - sec * rate) * 1000 / rate;
} }
blargg_err_t Music_Emu::play( int out_count, sample_t out [] ) blargg_err_t Music_Emu::seek_samples( long time )
{ {
require( current_track() >= 0 ); if ( time < out_time )
require( out_count % stereo == 0 ); RETURN_ERR( start_track( current_track_ ) );
return skip( time - out_time );
return track_filter.play( out_count, out ); }
}
blargg_err_t Music_Emu::seek( long msec )
// Gme_Info_ {
return seek_samples( msec_to_samples( msec ) );
blargg_err_t Gme_Info_::set_sample_rate_( int ) { return blargg_ok; } }
void Gme_Info_::pre_load() { Gme_File::pre_load(); } // skip Music_Emu
blargg_err_t Gme_Info_::post_load() { return Gme_File::post_load(); } // skip Music_Emu blargg_err_t Music_Emu::skip( long count )
void Gme_Info_::set_equalizer_( equalizer_t const& ){ check( false ); } {
void Gme_Info_::mute_voices_( int ) { check( false ); } require( current_track() >= 0 ); // start_track() must have been called already
void Gme_Info_::set_tempo_( double ) { } out_time += count;
blargg_err_t Gme_Info_::start_track_( int ) { return BLARGG_ERR( BLARGG_ERR_CALLER, "can't play file opened for info only" ); }
blargg_err_t Gme_Info_::play_( int, sample_t [] ) { return BLARGG_ERR( BLARGG_ERR_CALLER, "can't play file opened for info only" ); } // remove from silence and buf first
{
long n = min( count, silence_count );
silence_count -= n;
count -= n;
n = min( count, buf_remain );
buf_remain -= n;
count -= n;
}
if ( count && !emu_track_ended_ )
{
emu_time += count;
end_track_if_error( skip_( count ) );
}
if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended
track_ended_ |= emu_track_ended_;
return 0;
}
blargg_err_t Music_Emu::skip_( long count )
{
// for long skip, mute sound
const long threshold = 30000;
if ( count > threshold )
{
int saved_mute = mute_mask_;
mute_voices( ~0 );
while ( count > threshold / 2 && !emu_track_ended_ )
{
RETURN_ERR( play_( buf_size, buf.begin() ) );
count -= buf_size;
}
mute_voices( saved_mute );
}
while ( count && !emu_track_ended_ )
{
long n = buf_size;
if ( n > count )
n = count;
count -= n;
RETURN_ERR( play_( n, buf.begin() ) );
}
return 0;
}
// Fading
void Music_Emu::set_fade( long start_msec, long length_msec )
{
fade_step = sample_rate() * length_msec / (fade_block_size * fade_shift * 1000 / out_channels());
fade_start = msec_to_samples( start_msec );
}
// unit / pow( 2.0, (double) x / step )
static int int_log( blargg_long x, int step, int unit )
{
int shift = x / step;
int fraction = (x - shift * step) * unit / step;
return ((unit - fraction) + (fraction >> 1)) >> shift;
}
void Music_Emu::handle_fade( long out_count, sample_t* out )
{
for ( int i = 0; i < out_count; i += fade_block_size )
{
int const shift = 14;
int const unit = 1 << shift;
int gain = int_log( (out_time + i - fade_start) / fade_block_size,
fade_step, unit );
if ( gain < (unit >> fade_shift) )
track_ended_ = emu_track_ended_ = true;
sample_t* io = &out [i];
for ( int count = min( fade_block_size, out_count - i ); count; --count )
{
*io = sample_t ((*io * gain) >> shift);
++io;
}
}
}
// Silence detection
void Music_Emu::emu_play( long count, sample_t* out )
{
check( current_track_ >= 0 );
emu_time += count;
if ( current_track_ >= 0 && !emu_track_ended_ )
end_track_if_error( play_( count, out ) );
else
memset( out, 0, count * sizeof *out );
}
// number of consecutive silent samples at end
static long count_silence( Music_Emu::sample_t* begin, long size )
{
Music_Emu::sample_t first = *begin;
*begin = silence_threshold; // sentinel
Music_Emu::sample_t* p = begin + size;
while ( (unsigned) (*--p + silence_threshold / 2) <= (unsigned) silence_threshold ) { }
*begin = first;
return size - (p - begin);
}
// fill internal buffer and check it for silence
void Music_Emu::fill_buf()
{
assert( !buf_remain );
if ( !emu_track_ended_ )
{
emu_play( buf_size, buf.begin() );
long silence = count_silence( buf.begin(), buf_size );
if ( silence < buf_size )
{
silence_time = emu_time - silence;
buf_remain = buf_size;
return;
}
}
silence_count += buf_size;
}
blargg_err_t Music_Emu::play( long out_count, sample_t* out )
{
if ( track_ended_ )
{
memset( out, 0, out_count * sizeof *out );
}
else
{
require( current_track() >= 0 );
require( out_count % out_channels() == 0 );
assert( emu_time >= out_time );
// prints nifty graph of how far ahead we are when searching for silence
//debug_printf( "%*s \n", int ((emu_time - out_time) * 7 / sample_rate()), "*" );
long pos = 0;
if ( silence_count )
{
// during a run of silence, run emulator at >=2x speed so it gets ahead
long ahead_time = silence_lookahead * (out_time + out_count - silence_time) + silence_time;
while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) )
fill_buf();
// fill with silence
pos = min( silence_count, out_count );
memset( out, 0, pos * sizeof *out );
silence_count -= pos;
if ( emu_time - silence_time > silence_max * out_channels() * sample_rate() )
{
track_ended_ = emu_track_ended_ = true;
silence_count = 0;
buf_remain = 0;
}
}
if ( buf_remain )
{
// empty silence buf
long n = min( buf_remain, out_count - pos );
memcpy( &out [pos], buf.begin() + (buf_size - buf_remain), n * sizeof *out );
buf_remain -= n;
pos += n;
}
// generate remaining samples normally
long remain = out_count - pos;
if ( remain )
{
emu_play( remain, out + pos );
track_ended_ |= emu_track_ended_;
if ( !ignore_silence_ || out_time > fade_start )
{
// check end for a new run of silence
long silence = count_silence( out + pos, remain );
if ( silence < remain )
silence_time = emu_time - silence;
if ( emu_time - silence_time >= buf_size )
fill_buf(); // cause silence detection on next play()
}
}
if ( fade_start >= 0 && out_time > fade_start )
handle_fade( out_count, out );
}
out_time += out_count;
return 0;
}
// Gme_Info_
blargg_err_t Gme_Info_::set_sample_rate_( long ) { return 0; }
void Gme_Info_::pre_load() { Gme_File::pre_load(); } // skip Music_Emu
void Gme_Info_::post_load_() { Gme_File::post_load_(); } // skip Music_Emu
void Gme_Info_::set_equalizer_( equalizer_t const& ){ check( false ); }
void Gme_Info_::enable_accuracy_( bool ) { check( false ); }
void Gme_Info_::mute_voices_( int ) { check( false ); }
void Gme_Info_::set_tempo_( double ) { }
blargg_err_t Gme_Info_::start_track_( int ) { return "Use full emulator for playback"; }
blargg_err_t Gme_Info_::play_( long, sample_t* ) { return "Use full emulator for playback"; }

View file

@ -1,283 +1,252 @@
// Common interface to game music file emulators // Common interface to game music file emulators
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef MUSIC_EMU_H #ifndef MUSIC_EMU_H
#define MUSIC_EMU_H #define MUSIC_EMU_H
#include "Gme_File.h" #include "Gme_File.h"
#include "Track_Filter.h" class Multi_Buffer;
#include "blargg_errors.h"
class Multi_Buffer; struct Music_Emu : public Gme_File {
public:
struct gme_t : public Gme_File, private Track_Filter::callbacks_t { // Basic functionality (see Gme_File.h for file loading/track info functions)
public:
// Sets output sample rate. Must be called only once before loading file. // Set output sample rate. Must be called only once before loading file.
blargg_err_t set_sample_rate( int sample_rate ); blargg_err_t set_sample_rate( long sample_rate );
// Sample rate sound is generated at // specifies if all 8 voices get rendered to their own stereo channel
int sample_rate() const; // default implementation of Music_Emu always returns not supported error (i.e. no multichannel support by default)
// derived emus must override this if they support multichannel rendering
// File loading virtual blargg_err_t set_multi_channel( bool is_enabled );
// See Gme_Loader.h // Start a track, where 0 is the first track. Also clears warning string.
blargg_err_t start_track( int );
// Basic playback
// Generate 'count' samples info 'buf'. Output is in stereo. Any emulation
// Starts a track, where 0 is the first track. Also clears warning string. // errors set warning string, and major errors also end track.
blargg_err_t start_track( int ); typedef short sample_t;
blargg_err_t play( long count, sample_t* buf );
// Generates 'count' samples info 'buf'. Output is in stereo. Any emulation
// errors set warning string, and major errors also end track. // Informational
typedef short sample_t;
blargg_err_t play( int count, sample_t* buf ); // Sample rate sound is generated at
long sample_rate() const;
// Track information
// Index of current track or -1 if one hasn't been started
// See Gme_File.h int current_track() const;
// Index of current track or -1 if one hasn't been started // Number of voices used by currently loaded file
int current_track() const; int voice_count() const;
// Info for currently playing track // Names of voices
using Gme_File::track_info; const char** voice_names() const;
blargg_err_t track_info( track_info_t* out ) const;
blargg_err_t set_track_info( const track_info_t* in ); bool multi_channel() const;
blargg_err_t set_track_info( const track_info_t* in, int track_number );
// Track status/control
struct Hash_Function
{ // Number of milliseconds (1000 msec = 1 second) played since beginning of track
virtual void hash_( byte const* data, size_t size ) BLARGG_PURE( ; ) long tell() const;
};
virtual blargg_err_t hash_( Hash_Function& ) const BLARGG_PURE( ; ) // Number of samples generated since beginning of track
long tell_samples() const;
blargg_err_t save( gme_writer_t writer, void* your_data) const;
// Seek to new time in track. Seeking backwards or far forward can take a while.
// Track status/control blargg_err_t seek( long msec );
// Number of milliseconds played since beginning of track (1000 per second) // Equivalent to restarting track then skipping n samples
int tell() const; blargg_err_t seek_samples( long n );
// Seeks to new time in track. Seeking backwards or far forward can take a while. // Skip n samples
blargg_err_t seek( int msec ); blargg_err_t skip( long n );
// Skips n samples // True if a track has reached its end
blargg_err_t skip( int n ); bool track_ended() const;
// True if a track has reached its end // Set start time and length of track fade out. Once fade ends track_ended() returns
bool track_ended() const; // true. Fade time can be changed while track is playing.
void set_fade( long start_msec, long length_msec = 8000 );
// Sets start time and length of track fade out. Once fade ends track_ended() returns
// true. Fade time must be set after track has been started, and can be changed // Controls whether or not to automatically load and obey track length
// at any time. // metadata for supported emulators.
void set_fade( int start_msec, int length_msec = 8000 ); //
// @since 0.6.2.
// Disables automatic end-of-track detection and skipping of silence at beginning bool autoload_playback_limit() const;
void ignore_silence( bool disable = true ); void set_autoload_playback_limit( bool do_autoload_limit );
// Voices // Disable automatic end-of-track detection and skipping of silence at beginning
void ignore_silence( bool disable = true );
// Number of voices used by currently loaded file
int voice_count() const; // Info for current track
using Gme_File::track_info;
// Name of voice i, from 0 to voice_count()-1 blargg_err_t track_info( track_info_t* out ) const;
const char* voice_name( int i ) const;
// Sound customization
// Mutes/unmutes voice i, where voice 0 is first voice
void mute_voice( int index, bool mute = true ); // Adjust song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed.
// Track length as returned by track_info() assumes a tempo of 1.0.
// Sets muting state of all voices at once using a bit mask, where -1 mutes them all, void set_tempo( double );
// 0 unmutes them all, 0x01 mutes just the first voice, etc.
void mute_voices( int mask ); // Mute/unmute voice i, where voice 0 is first voice
void mute_voice( int index, bool mute = true );
// Sound customization
// Set muting state of all voices at once using a bit mask, where -1 mutes them all,
// Adjusts song tempo, where 1.0 = normal, 0.5 = half speed, 2.0 = double speed. // 0 unmutes them all, 0x01 mutes just the first voice, etc.
// Track length as returned by track_info() assumes a tempo of 1.0. void mute_voices( int mask );
void set_tempo( double );
// Change overall output amplitude, where 1.0 results in minimal clamping.
// Changes overall output amplitude, where 1.0 results in minimal clamping. // Must be called before set_sample_rate().
// Must be called before set_sample_rate(). void set_gain( double );
void set_gain( double );
// Request use of custom multichannel buffer. Only supported by "classic" emulators;
// Requests use of custom multichannel buffer. Only supported by "classic" emulators; // on others this has no effect. Should be called only once *before* set_sample_rate().
// on others this has no effect. Should be called only once *before* set_sample_rate(). virtual void set_buffer( Multi_Buffer* ) { }
virtual void set_buffer( class Multi_Buffer* ) { }
// Enables/disables accurate emulation options, if any are supported. Might change
// Mutes native effects of a given sound engine. Currently only applies to the SPC emulator. // equalizer settings.
virtual void mute_effects( bool mute ) { } void enable_accuracy( bool enable = true );
// Sound equalization (treble/bass) // Sound equalization (treble/bass)
// Frequency equalizer parameters (see gme.txt) // Frequency equalizer parameters (see gme.txt)
// See gme.h for definition of struct gme_equalizer_t. // See gme.h for definition of struct gme_equalizer_t.
typedef gme_equalizer_t equalizer_t; typedef gme_equalizer_t equalizer_t;
// Current frequency equalizater parameters // Current frequency equalizater parameters
equalizer_t const& equalizer() const; equalizer_t const& equalizer() const;
// Sets frequency equalizer parameters // Set frequency equalizer parameters
void set_equalizer( equalizer_t const& ); void set_equalizer( equalizer_t const& );
// Equalizer preset for a TV speaker // Construct equalizer of given treble/bass settings
static equalizer_t const tv_eq; static const equalizer_t make_equalizer( double treble, double bass )
{
// Derived interface const Music_Emu::equalizer_t e = { treble, bass,
protected: 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
// Cause any further generated samples to be silence, instead of calling play_() return e;
void set_track_ended() { track_filter.set_track_ended(); } }
// If more than secs of silence are encountered, track is ended // Equalizer settings for TV speaker
void set_max_initial_silence( int secs ) { tfilter.max_initial = secs; } static equalizer_t const tv_eq;
// Sets rate emulator is run at when scanning ahead for silence. 1=100%, 2=200% etc. public:
void set_silence_lookahead( int rate ) { tfilter.lookahead = rate; } Music_Emu();
~Music_Emu();
// Sets number of voices protected:
void set_voice_count( int n ) { voice_count_ = n; } void set_max_initial_silence( int n ) { max_initial_silence = n; }
void set_silence_lookahead( int n ) { silence_lookahead = n; }
// Sets names of voices void set_voice_count( int n ) { voice_count_ = n; }
void set_voice_names( const char* const names [] ); void set_voice_names( const char* const* names );
void set_track_ended() { emu_track_ended_ = true; }
// Current gain double gain() const { return gain_; }
double gain() const { return gain_; } double tempo() const { return tempo_; }
void remute_voices();
// Current tempo blargg_err_t set_multi_channel_( bool is_enabled );
double tempo() const { return tempo_; }
virtual blargg_err_t set_sample_rate_( long sample_rate ) = 0;
// Re-applies muting mask using mute_voices_() virtual void set_equalizer_( equalizer_t const& ) { }
void remute_voices(); virtual void enable_accuracy_( bool /* enable */ ) { }
virtual void mute_voices_( int mask ) = 0;
// Overrides should do the indicated task virtual void set_tempo_( double ) = 0;
virtual blargg_err_t start_track_( int ) = 0; // tempo is set before this
// Set sample rate as close as possible to sample_rate, then call virtual blargg_err_t play_( long count, sample_t* out ) = 0;
// Music_Emu::set_sample_rate_() with the actual rate used. virtual blargg_err_t skip_( long count );
virtual blargg_err_t set_sample_rate_( int sample_rate ) BLARGG_PURE( ; ) protected:
virtual void unload();
// Set equalizer parameters virtual void pre_load();
virtual void set_equalizer_( equalizer_t const& ) { } virtual void post_load_();
private:
// Mute voices based on mask // general
virtual void mute_voices_( int mask ) BLARGG_PURE( ; ) equalizer_t equalizer_;
int max_initial_silence;
// Set tempo to t, which is constrained to the range 0.02 to 4.0. const char** voice_names_;
virtual void set_tempo_( double t ) BLARGG_PURE( ; ) int voice_count_;
int mute_mask_;
// Start track t, where 0 is the first track double tempo_;
virtual blargg_err_t start_track_( int t ) BLARGG_PURE( ; ) // tempo is set before this double gain_;
bool multi_channel_;
// Generate count samples into *out. Count will always be even.
virtual blargg_err_t play_( int count, sample_t out [] ) BLARGG_PURE( ; ) // returns the number of output channels, i.e. usually 2 for stereo, unlesss multi_channel_ == true
int out_channels() const { return this->multi_channel() ? 2*8 : 2; }
// Skip count samples. Count will always be even.
virtual blargg_err_t skip_( int count ); long sample_rate_;
blargg_long msec_to_samples( blargg_long msec ) const;
// Save current state of file to specified writer.
virtual blargg_err_t save_( gme_writer_t, void* ) const { return "Not supported by this format"; } // track-specific
int current_track_;
// Set track info blargg_long out_time; // number of samples played since start of track
virtual blargg_err_t set_track_info_( const track_info_t*, int ) { return "Not supported by this format"; } blargg_long emu_time; // number of samples emulator has generated since start of track
bool emu_track_ended_; // emulator has reached end of track
// Implementation bool emu_autoload_playback_limit_; // whether to load and obey track length by default
public: volatile bool track_ended_;
gme_t(); void clear_track_vars();
~gme_t(); void end_track_if_error( blargg_err_t );
const char** voice_names() const { return CONST_CAST(const char**,voice_names_); }
// fading
protected: blargg_long fade_start;
virtual void unload(); int fade_step;
virtual void pre_load(); void handle_fade( long count, sample_t* out );
virtual blargg_err_t post_load();
// silence detection
private: int silence_lookahead; // speed to run emulator when looking ahead for silence
Track_Filter::setup_t tfilter; bool ignore_silence_;
Track_Filter track_filter; long silence_time; // number of samples where most recent silence began
equalizer_t equalizer_; long silence_count; // number of samples of silence to play before using buf
const char* const* voice_names_; long buf_remain; // number of samples left in silence buffer
int voice_count_; enum { buf_size = 2048 };
int mute_mask_; blargg_vector<sample_t> buf;
double tempo_; void fill_buf();
double gain_; void emu_play( long count, sample_t* out );
int sample_rate_;
int current_track_; Multi_Buffer* effects_buffer;
friend Music_Emu* gme_internal_new_emu_( gme_type_t, int, bool );
bool fade_set; friend void gme_set_stereo_depth( Music_Emu*, double );
int length_msec; };
int fade_msec;
// base class for info-only derivations
void clear_track_vars(); struct Gme_Info_ : Music_Emu
int msec_to_samples( int msec ) const; {
virtual blargg_err_t set_sample_rate_( long sample_rate );
friend Music_Emu* gme_new_emu( gme_type_t, int ); virtual void set_equalizer_( equalizer_t const& );
friend void gme_effects( Music_Emu const*, gme_effects_t* ); virtual void enable_accuracy_( bool );
friend void gme_set_effects( Music_Emu*, gme_effects_t const* ); virtual void mute_voices_( int mask );
friend void gme_set_stereo_depth( Music_Emu*, double ); virtual void set_tempo_( double );
friend const char** gme_voice_names ( Music_Emu const* ); virtual blargg_err_t start_track_( int );
virtual blargg_err_t play_( long count, sample_t* out );
protected: virtual void pre_load();
Multi_Buffer* effects_buffer_; virtual void post_load_();
}; };
// base class for info-only derivations inline blargg_err_t Music_Emu::track_info( track_info_t* out ) const
struct Gme_Info_ : Music_Emu {
{ return track_info( out, current_track_ );
virtual blargg_err_t set_sample_rate_( int sample_rate ); }
virtual void set_equalizer_( equalizer_t const& );
virtual void mute_voices_( int mask ); inline long Music_Emu::sample_rate() const { return sample_rate_; }
virtual void set_tempo_( double ); inline const char** Music_Emu::voice_names() const { return voice_names_; }
virtual blargg_err_t start_track_( int ); inline int Music_Emu::voice_count() const { return voice_count_; }
virtual blargg_err_t play_( int count, sample_t out [] ); inline int Music_Emu::current_track() const { return current_track_; }
virtual void pre_load(); inline bool Music_Emu::track_ended() const { return track_ended_; }
virtual blargg_err_t post_load(); inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; }
};
inline void Music_Emu::enable_accuracy( bool b ) { enable_accuracy_( b ); }
inline blargg_err_t Music_Emu::track_info( track_info_t* out ) const inline void Music_Emu::set_tempo_( double t ) { tempo_ = t; }
{ inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); }
return track_info( out, current_track_ ); inline void Music_Emu::ignore_silence( bool b ) { ignore_silence_ = b; }
} inline blargg_err_t Music_Emu::start_track_( int ) { return 0; }
inline blargg_err_t Music_Emu::save(gme_writer_t writer, void *your_data) const inline void Music_Emu::set_voice_names( const char* const* names )
{ {
return save_( writer, your_data ); // Intentional removal of const, so users don't have to remember obscure const in middle
} voice_names_ = const_cast<const char**> (names);
}
inline blargg_err_t Music_Emu::set_track_info(const track_info_t *in)
{ inline void Music_Emu::mute_voices_( int ) { }
return set_track_info_( in, current_track_ );
} inline void Music_Emu::set_gain( double g )
{
inline blargg_err_t Music_Emu::set_track_info(const track_info_t *in, int track) assert( !sample_rate() ); // you must set gain before setting sample rate
{ gain_ = g;
return set_track_info_( in, track ); }
}
#endif
inline int Music_Emu::sample_rate() const { return sample_rate_; }
inline int Music_Emu::voice_count() const { return voice_count_; }
inline int Music_Emu::current_track() const { return current_track_; }
inline bool Music_Emu::track_ended() const { return track_filter.track_ended(); }
inline const Music_Emu::equalizer_t& Music_Emu::equalizer() const { return equalizer_; }
inline void Music_Emu::ignore_silence( bool b ) { track_filter.ignore_silence( b ); }
inline void Music_Emu::set_tempo_( double t ) { tempo_ = t; }
inline void Music_Emu::remute_voices() { mute_voices( mute_mask_ ); }
inline void Music_Emu::set_voice_names( const char* const p [] ) { voice_names_ = p; }
inline void Music_Emu::mute_voices_( int ) { }
inline void Music_Emu::set_gain( double g )
{
assert( !sample_rate() ); // you must set gain before setting sample rate
gain_ = g;
}
inline blargg_err_t Music_Emu::start_track_( int ) { return blargg_ok; }
inline blargg_err_t Music_Emu::set_sample_rate_( int ) { return blargg_ok; }
inline blargg_err_t Music_Emu::play_( int, sample_t [] ) { return blargg_ok; }
inline blargg_err_t Music_Emu::hash_( Hash_Function& ) const { return BLARGG_ERR( BLARGG_ERR_CALLER, "no hashing function defined" ); }
inline void Music_Emu::Hash_Function::hash_( byte const*, size_t ) { }
#endif

View file

@ -1,394 +1,391 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. 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-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 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;
dmc.prg_reader = NULL;
oscs [0] = &square1; irq_notifier_ = NULL;
oscs [1] = &square2;
oscs [2] = &triangle; oscs [0] = &square1;
oscs [3] = &noise; oscs [1] = &square2;
oscs [4] = &dmc; oscs [2] = &triangle;
oscs [3] = &noise;
set_output( NULL ); oscs [4] = &dmc;
dmc.nonlinear = false;
volume( 1.0 ); output( NULL );
reset( false ); volume( 1.0 );
} 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 ); {
triangle.synth.treble_eq( eq ); square_synth.treble_eq( eq );
noise .synth.treble_eq( eq ); triangle.synth.treble_eq( eq );
dmc .synth.treble_eq( eq ); noise.synth.treble_eq( eq );
} dmc.synth.treble_eq( eq );
}
void Nes_Apu::enable_nonlinear_( double sq, double tnd )
{ void Nes_Apu::enable_nonlinear( double v )
dmc.nonlinear = true; {
square_synth.volume( sq ); dmc.nonlinear = true;
square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v );
triangle.synth.volume( tnd * 2.752 );
noise .synth.volume( tnd * 1.849 ); const double tnd = 0.48 / 202 * nonlinear_tnd_gain();
dmc .synth.volume( tnd ); triangle.synth.volume( 3.0 * tnd );
noise.synth.volume( 2.0 * tnd );
square1 .last_amp = 0; dmc.synth.volume( tnd );
square2 .last_amp = 0;
triangle.last_amp = 0; square1 .last_amp = 0;
noise .last_amp = 0; square2 .last_amp = 0;
dmc .last_amp = 0; triangle.last_amp = 0;
} noise .last_amp = 0;
dmc .last_amp = 0;
void Nes_Apu::volume( double v ) }
{
if ( !dmc.nonlinear ) void Nes_Apu::volume( double v )
{ {
v *= 1.0 / 1.11; // TODO: merge into values below dmc.nonlinear = false;
square_synth .volume( 0.125 / amp_range * v ); // was 0.1128 1.108 square_synth.volume( 0.1128 / amp_range * v );
triangle.synth.volume( 0.150 / amp_range * v ); // was 0.12765 1.175 triangle.synth.volume( 0.12765 / amp_range * v );
noise .synth.volume( 0.095 / amp_range * v ); // was 0.0741 1.282 noise.synth.volume( 0.0741 / amp_range * v );
dmc .synth.volume( 0.450 / 2048 * v ); // was 0.42545 1.058 dmc.synth.volume( 0.42545 / 127 * v );
} }
}
void Nes_Apu::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 ) osc_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; earliest_irq_ = no_irq;
enable_w4011 = true; frame_delay = 1;
earliest_irq_ = no_irq; write_register( 0, 0x4017, 0x00 );
frame_delay = 1; write_register( 0, 0x4015, 0x00 );
write_register( 0, 0x4017, 0x00 );
write_register( 0, 0x4015, 0x00 ); for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ )
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
for ( int addr = io_addr; addr <= 0x4013; addr++ )
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); dmc.dac = initial_dmc_dac;
if ( !dmc.nonlinear )
dmc.dac = initial_dmc_dac; triangle.last_amp = 15;
if ( !dmc.nonlinear ) if ( !dmc.nonlinear ) // TODO: remove?
triangle.last_amp = 15; dmc.last_amp = initial_dmc_dac; // prevent output transition
if ( !dmc.nonlinear ) // TODO: remove? }
dmc.last_amp = initial_dmc_dac; // prevent output transition
} void Nes_Apu::irq_changed()
{
void Nes_Apu::irq_changed() nes_time_t new_irq = dmc.next_irq;
{ if ( dmc.irq_flag | irq_flag ) {
blip_time_t new_irq = dmc.next_irq; new_irq = 0;
if ( dmc.irq_flag | irq_flag ) { }
new_irq = 0; else if ( new_irq > next_irq ) {
} new_irq = next_irq;
else if ( new_irq > next_irq ) { }
new_irq = next_irq;
} if ( new_irq != earliest_irq_ ) {
earliest_irq_ = new_irq;
if ( new_irq != earliest_irq_ ) { if ( irq_notifier_ )
earliest_irq_ = new_irq; irq_notifier_( irq_data );
if ( irq_notifier.f ) }
irq_notifier.f( irq_notifier.data ); }
}
} // frames
// frames void Nes_Apu::run_until( nes_time_t end_time )
{
void Nes_Apu::run_until( blip_time_t end_time ) require( end_time >= last_dmc_time );
{ if ( end_time > next_dmc_read_time() )
require( end_time >= last_dmc_time ); {
if ( end_time > next_dmc_read_time() ) nes_time_t start = last_dmc_time;
{ last_dmc_time = end_time;
blip_time_t start = last_dmc_time; dmc.run( start, end_time );
last_dmc_time = end_time; }
dmc.run( start, end_time ); }
}
} void Nes_Apu::run_until_( nes_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 )
return;
if ( end_time == last_time )
return; if ( last_dmc_time < end_time )
{
if ( last_dmc_time < end_time ) nes_time_t start = last_dmc_time;
{ last_dmc_time = end_time;
blip_time_t start = last_dmc_time; dmc.run( start, end_time );
last_dmc_time = end_time; }
dmc.run( start, end_time );
} while ( true )
{
while ( true ) // earlier of next frame time or end time
{ nes_time_t time = last_time + frame_delay;
// earlier of next frame time or end time if ( time > end_time )
blip_time_t time = last_time + frame_delay; time = end_time;
if ( time > end_time ) frame_delay -= time - last_time;
time = end_time;
frame_delay -= time - last_time; // run oscs to present
square1.run( last_time, time );
// run oscs to present square2.run( last_time, time );
square1.run( last_time, time ); triangle.run( last_time, time );
square2.run( last_time, time ); noise.run( last_time, time );
triangle.run( last_time, time ); last_time = time;
noise.run( last_time, time );
last_time = time; if ( time == end_time )
break; // no more frames to run
if ( time == end_time )
break; // no more frames to run // take frame-specific actions
frame_delay = frame_period;
// take frame-specific actions switch ( frame++ )
frame_delay = frame_period; {
switch ( frame++ ) case 0:
{ if ( !(frame_mode & 0xC0) ) {
case 0: next_irq = time + frame_period * 4 + 2;
if ( !(frame_mode & 0xC0) ) { irq_flag = true;
next_irq = time + frame_period * 4 + 2; }
irq_flag = true; // fall through
} case 2:
// fall through // clock length and sweep on frames 0 and 2
case 2: square1.clock_length( 0x20 );
// clock length and sweep on frames 0 and 2 square2.clock_length( 0x20 );
square1.clock_length( 0x20 ); noise.clock_length( 0x20 );
square2.clock_length( 0x20 ); triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
noise.clock_length( 0x20 );
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle square1.clock_sweep( -1 );
square2.clock_sweep( 0 );
square1.clock_sweep( -1 );
square2.clock_sweep( 0 ); // frame 2 is slightly shorter in mode 1
if ( dmc.pal_mode && frame == 3 )
// frame 2 is slightly shorter in mode 1 frame_delay -= 2;
if ( dmc.pal_mode && frame == 3 ) break;
frame_delay -= 2;
break; case 1:
// frame 1 is slightly shorter in mode 0
case 1: if ( !dmc.pal_mode )
// frame 1 is slightly shorter in mode 0 frame_delay -= 2;
if ( !dmc.pal_mode ) break;
frame_delay -= 2;
break; case 3:
frame = 0;
case 3:
frame = 0; // frame 3 is almost twice as long in mode 1
if ( frame_mode & 0x80 )
// frame 3 is almost twice as long in mode 1 frame_delay += frame_period - (dmc.pal_mode ? 2 : 6);
if ( frame_mode & 0x80 ) break;
frame_delay += frame_period - (dmc.pal_mode ? 2 : 6); }
break;
} // clock envelopes and linear counter every frame
triangle.clock_linear_counter();
// clock envelopes and linear counter every frame square1.clock_envelope();
triangle.clock_linear_counter(); square2.clock_envelope();
square1.clock_envelope(); noise.clock_envelope();
square2.clock_envelope(); }
noise.clock_envelope(); }
}
} template<class T>
inline void zero_apu_osc( T* osc, nes_time_t time )
template<class T> {
inline void zero_apu_osc( T* osc, blip_time_t time ) Blip_Buffer* output = osc->output;
{ int last_amp = osc->last_amp;
Blip_Buffer* output = osc->output; osc->last_amp = 0;
int last_amp = osc->last_amp; if ( output && last_amp )
osc->last_amp = 0; osc->synth.offset( time, -last_amp, output );
if ( output && last_amp ) }
osc->synth.offset( time, -last_amp, output );
} void Nes_Apu::end_frame( nes_time_t end_time )
{
void Nes_Apu::end_frame( blip_time_t end_time ) if ( end_time > last_time )
{ run_until_( end_time );
if ( end_time > last_time )
run_until_( end_time ); if ( dmc.nonlinear )
{
if ( dmc.nonlinear ) zero_apu_osc( &square1, last_time );
{ zero_apu_osc( &square2, last_time );
zero_apu_osc( &square1, last_time ); zero_apu_osc( &triangle, last_time );
zero_apu_osc( &square2, last_time ); zero_apu_osc( &noise, last_time );
zero_apu_osc( &triangle, last_time ); zero_apu_osc( &dmc, last_time );
zero_apu_osc( &noise, last_time ); }
zero_apu_osc( &dmc, last_time );
} // make times relative to new frame
last_time -= end_time;
// make times relative to new frame require( last_time >= 0 );
last_time -= end_time;
require( last_time >= 0 ); last_dmc_time -= end_time;
require( last_dmc_time >= 0 );
last_dmc_time -= end_time;
require( last_dmc_time >= 0 ); if ( next_irq != no_irq ) {
next_irq -= end_time;
if ( next_irq != no_irq ) { check( next_irq >= 0 );
next_irq -= end_time; }
check( next_irq >= 0 ); if ( dmc.next_irq != no_irq ) {
} dmc.next_irq -= end_time;
if ( dmc.next_irq != no_irq ) { check( dmc.next_irq >= 0 );
dmc.next_irq -= end_time; }
check( dmc.next_irq >= 0 ); if ( earliest_irq_ != no_irq ) {
} earliest_irq_ -= end_time;
if ( earliest_irq_ != no_irq ) { if ( earliest_irq_ < 0 )
earliest_irq_ -= end_time; earliest_irq_ = 0;
if ( earliest_irq_ < 0 ) }
earliest_irq_ = 0; }
}
} // registers
// registers static const unsigned char length_table [0x20] = {
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
static const unsigned char length_table [0x20] = { 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, };
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
}; void Nes_Apu::write_register( nes_time_t time, nes_addr_t 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( (unsigned) data <= 0xFF );
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
require( (unsigned) data <= 0xFF ); // Ignore addresses outside range
if ( unsigned (addr - start_addr) > end_addr - start_addr )
// Ignore addresses outside range return;
if ( unsigned (addr - io_addr) >= io_size )
return; run_until_( time );
run_until_( time ); if ( addr < 0x4014 )
{
if ( addr < 0x4014 ) // Write to channel
{ int osc_index = (addr - start_addr) >> 2;
// Write to channel Nes_Osc* osc = oscs [osc_index];
int osc_index = (addr - io_addr) >> 2;
Nes_Osc* osc = oscs [osc_index]; int reg = addr & 3;
osc->regs [reg] = data;
int reg = addr & 3; osc->reg_written [reg] = true;
osc->regs [reg] = data;
osc->reg_written [reg] = true; if ( osc_index == 4 )
{
if ( osc_index == 4 ) // handle DMC specially
{ dmc.write_register( reg, data );
// handle DMC specially }
if ( enable_w4011 || reg != 1 ) else if ( reg == 3 )
dmc.write_register( reg, data ); {
} // load length counter
else if ( reg == 3 ) if ( (osc_enables >> osc_index) & 1 )
{ osc->length_counter = length_table [(data >> 3) & 0x1F];
// load length counter
if ( (osc_enables >> osc_index) & 1 ) // reset square phase
osc->length_counter = length_table [(data >> 3) & 0x1F]; if ( osc_index < 2 )
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
// reset square phase }
if ( osc_index < 2 ) }
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; else if ( addr == 0x4015 )
} {
} // Channel enables
else if ( addr == 0x4015 ) for ( int i = osc_count; i--; )
{ if ( !((data >> i) & 1) )
// Channel enables oscs [i]->length_counter = 0;
for ( int i = osc_count; i--; )
if ( !((data >> i) & 1) ) bool recalc_irq = dmc.irq_flag;
oscs [i]->length_counter = 0; dmc.irq_flag = false;
bool recalc_irq = dmc.irq_flag; int old_enables = osc_enables;
dmc.irq_flag = false; osc_enables = data;
if ( !(data & 0x10) ) {
int old_enables = osc_enables; dmc.next_irq = no_irq;
osc_enables = data; recalc_irq = true;
if ( !(data & 0x10) ) { }
dmc.next_irq = no_irq; else if ( !(old_enables & 0x10) ) {
recalc_irq = true; dmc.start(); // dmc just enabled
} }
else if ( !(old_enables & 0x10) ) {
dmc.start(); // dmc just enabled if ( recalc_irq )
} irq_changed();
}
if ( recalc_irq ) else if ( addr == 0x4017 )
irq_changed(); {
} // Frame mode
else if ( addr == 0x4017 ) frame_mode = data;
{
// Frame mode bool irq_enabled = !(data & 0x40);
frame_mode = data; irq_flag &= irq_enabled;
next_irq = no_irq;
bool irq_enabled = !(data & 0x40);
irq_flag &= irq_enabled; // mode 1
next_irq = no_irq; frame_delay = (frame_delay & 1);
frame = 0;
// mode 1
frame_delay = (frame_delay & 1); if ( !(data & 0x80) )
frame = 0; {
// mode 0
if ( !(data & 0x80) ) frame = 1;
{ frame_delay += frame_period;
// mode 0 if ( irq_enabled )
frame = 1; next_irq = time + frame_delay + frame_period * 3 + 1;
frame_delay += frame_period; }
if ( irq_enabled )
next_irq = time + frame_delay + frame_period * 3 + 1; irq_changed();
} }
}
irq_changed();
} int Nes_Apu::read_status( nes_time_t time )
} {
run_until_( time - 1 );
int Nes_Apu::read_status( blip_time_t time )
{ int result = (dmc.irq_flag << 7) | (irq_flag << 6);
run_until_( time - 1 );
for ( int i = 0; i < osc_count; i++ )
int result = (dmc.irq_flag << 7) | (irq_flag << 6); if ( oscs [i]->length_counter )
result |= 1 << i;
for ( int i = 0; i < osc_count; i++ )
if ( oscs [i]->length_counter ) run_until_( time );
result |= 1 << i;
if ( irq_flag )
run_until_( time ); {
result |= 0x40;
if ( irq_flag ) irq_flag = false;
{ irq_changed();
result |= 0x40; }
irq_flag = false;
irq_changed(); //debug_printf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
}
return result;
//dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result ); }
return result;
}

View file

@ -1,184 +1,179 @@
// NES 2A03 APU sound chip emulator // NES 2A03 APU sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#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"
typedef blargg_long nes_time_t; // CPU clock cycle count
struct apu_state_t; typedef unsigned nes_addr_t; // 16-bit memory address
class Nes_Buffer;
#include "Nes_Oscs.h"
class Nes_Apu {
public: struct apu_state_t;
// Basics class Nes_Buffer;
typedef int nes_time_t; // NES CPU clock cycle count class Nes_Apu {
public:
// Sets memory reader callback used by DMC oscillator to fetch samples. // Set buffer to generate all sound into, or disable sound if NULL
// When callback is invoked, 'user_data' is passed unchanged as the void output( Blip_Buffer* );
// first parameter.
//void dmc_reader( int (*callback)( void* user_data, int addr ), void* user_data = NULL ); // Set memory reader callback used by DMC oscillator to fetch samples.
// When callback is invoked, 'user_data' is passed unchanged as the
// Sets buffer to generate sound into, or 0 to mute output (reduces // first parameter.
// emulation accuracy). void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL );
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 ).
// Write to register (0x4000-0x4017, except 0x4014 and 0x4016)
// Writes to register (0x4000-0x4013, and 0x4015 and 0x4017) enum { start_addr = 0x4000 };
enum { io_addr = 0x4000 }; enum { end_addr = 0x4017 };
enum { io_size = 0x18 }; void write_register( nes_time_t, nes_addr_t, int data );
void write_register( nes_time_t, int addr, int data );
// Read from status register at 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 );
// Run all oscillators up to specified time, end current time frame, then
// Runs all oscillators up to specified time, ends current time frame, then // start 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 );
// Additional optional features (can be ignored without any problem)
// Optional
// Reset internal frame counter, registers, and all oscillators.
// Resets internal frame counter, registers, and all oscillators. // Use PAL timing if pal_timing is true, otherwise use NTSC timing.
// Uses PAL timing if pal_timing is true, otherwise use NTSC timing. // Set 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 );
// Adjust frame period
// Same as set_output(), but for a particular channel void set_tempo( double );
// 0: Square 1, 1: Square 2, 2: Triangle, 3: Noise, 4: DMC
enum { osc_count = 5 }; // Save/load exact emulation state
void set_output( int chan, Blip_Buffer* buf ); void save_state( apu_state_t* out ) const;
void load_state( apu_state_t const& );
// Adjusts frame period
void set_tempo( double ); // Set overall volume (default is 1.0)
void volume( double );
// Saves/loads exact emulation state
void save_state( apu_state_t* out ) const; // Set treble equalization (see notes.txt)
void load_state( apu_state_t const& ); void treble_eq( const blip_eq_t& );
// Sets overall volume (default is 1.0) // Set sound output of specific oscillator to buffer. If buffer is NULL,
void volume( double ); // the specified oscillator is muted and emulation accuracy is reduced.
// The oscillators are indexed as follows: 0) Square 1, 1) Square 2,
// Sets treble equalization (see notes.txt) // 2) Triangle, 3) Noise, 4) DMC.
void treble_eq( const blip_eq_t& ); enum { osc_count = 5 };
void osc_output( int index, Blip_Buffer* buffer );
// Sets IRQ time callback that is invoked when the time of earliest IRQ
// may have changed, or NULL to disable. When callback is invoked, // Set IRQ time callback that is invoked when the time of earliest IRQ
// 'user_data' is passed unchanged as the first parameter. // may have changed, or NULL to disable. When callback is invoked,
//void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); // 'user_data' is passed unchanged as the first parameter.
void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
// Gets time that APU-generated IRQ will occur if no further register reads
// or writes occur. If IRQ is already pending, returns irq_waiting. If no // Get time that APU-generated IRQ will occur if no further register reads
// IRQ will occur, returns no_irq. // or writes occur. If IRQ is already pending, returns irq_waiting. If no
enum { no_irq = INT_MAX/2 + 1 }; // IRQ will occur, returns no_irq.
enum { irq_waiting = 0 }; enum { no_irq = INT_MAX / 2 + 1 };
nes_time_t earliest_irq( nes_time_t ) const; enum { irq_waiting = 0 };
nes_time_t earliest_irq( nes_time_t ) const;
// Counts number of DMC reads that would occur if 'run_until( t )' were executed.
// If last_read is not NULL, set *last_read to the earliest time that // Count number of DMC reads that would occur if 'run_until( t )' were executed.
// 'count_dmc_reads( time )' would result in the same result. // If last_read is not NULL, set *last_read to the earliest time that
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; // 'count_dmc_reads( time )' would result in the same result.
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
// Time when next DMC memory read will occur
nes_time_t next_dmc_read_time() const; // Time when next DMC memory read will occur
nes_time_t next_dmc_read_time() const;
// Runs DMC until specified time, so that any DMC memory reads can be
// accounted for (i.e. inserting CPU wait states). // Run DMC until specified time, so that any DMC memory reads can be
void run_until( nes_time_t ); // accounted for (i.e. inserting CPU wait states).
void run_until( nes_time_t );
// Implementation public:
public: Nes_Apu();
Nes_Apu(); BLARGG_DISABLE_NOTHROW
BLARGG_DISABLE_NOTHROW private:
// Use set_output() in place of these friend class Nes_Nonlinearizer;
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); ) void enable_nonlinear( double volume );
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ); ) static double nonlinear_tnd_gain() { return 0.75; }
private:
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4000 }; ) friend struct Nes_Dmc;
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4017 }; )
// noncopyable
blargg_callback<int (*)( void* user_data, int addr )> dmc_reader; Nes_Apu( const Nes_Apu& );
blargg_callback<void (*)( void* user_data )> irq_notifier; Nes_Apu& operator = ( const Nes_Apu& );
void enable_nonlinear_( double sq, double tnd ); Nes_Osc* oscs [osc_count];
static float tnd_total_() { return 196.015f; } Nes_Square square1;
Nes_Square square2;
void enable_w4011_( bool enable = true ) { enable_w4011 = enable; } Nes_Noise noise;
Nes_Triangle triangle;
private: Nes_Dmc dmc;
friend struct Nes_Dmc;
double tempo_;
// noncopyable nes_time_t last_time; // has been run until this time in current frame
Nes_Apu( const Nes_Apu& ); nes_time_t last_dmc_time;
Nes_Apu& operator = ( const Nes_Apu& ); nes_time_t earliest_irq_;
nes_time_t next_irq;
Nes_Osc* oscs [osc_count]; int frame_period;
Nes_Square square1; int frame_delay; // cycles until frame counter runs next
Nes_Square square2; int frame; // current frame (0-3)
Nes_Noise noise; int osc_enables;
Nes_Triangle triangle; int frame_mode;
Nes_Dmc dmc; bool irq_flag;
void (*irq_notifier_)( void* user_data );
double tempo_; void* irq_data;
nes_time_t last_time; // has been run until this time in current frame Nes_Square::Synth square_synth; // shared by squares
nes_time_t last_dmc_time;
nes_time_t earliest_irq_; void irq_changed();
nes_time_t next_irq; void state_restored();
int frame_period; void run_until_( nes_time_t );
int frame_delay; // cycles until frame counter runs next
int frame; // current frame (0-3) // TODO: remove
int osc_enables; friend class Nes_Core;
int frame_mode; };
bool irq_flag;
bool enable_w4011; inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf )
Nes_Square::Synth square_synth; // shared by squares {
assert( (unsigned) osc < osc_count );
void irq_changed(); oscs [osc]->output = buf;
void state_restored(); }
void run_until_( nes_time_t );
inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
// TODO: remove {
friend class Nes_Core; return earliest_irq_;
}; }
inline void Nes_Apu::set_output( int osc, Blip_Buffer* buf ) inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data )
{ {
assert( (unsigned) osc < osc_count ); dmc.prg_reader_data = user_data;
oscs [osc]->output = buf; dmc.prg_reader = func;
} }
inline Nes_Apu::nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data )
{ {
return earliest_irq_; irq_notifier_ = func;
} irq_data = user_data;
}
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_time_t Nes_Dmc::next_read_time() const
if ( length_counter == 0 ) {
return Nes_Apu::no_irq; // not reading if ( length_counter == 0 )
return Nes_Apu::no_irq; // not reading
return apu->last_dmc_time + delay + (bits_remain - 1) * period;
} return apu->last_dmc_time + delay + long (bits_remain - 1) * period;
}
inline Nes_Apu::nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
BLARGG_DEPRECATED( typedef int nes_time_t; ) // use your own typedef
BLARGG_DEPRECATED( typedef unsigned nes_addr_t; ) // use your own typedef #endif
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::output ( Blip_Buffer* c ) { set_output( c ); } )
BLARGG_DEPRECATED_TEXT( inline void Nes_Apu::osc_output( int i, Blip_Buffer* c ) { set_output( i, c ); } )
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,131 +1,112 @@
// NES CPU emulator // NES 6502 CPU emulator
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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 { typedef blargg_long nes_time_t; // clock cycle count
public: typedef unsigned nes_addr_t; // 16-bit address
typedef BOOST::uint8_t byte; enum { future_nes_time = INT_MAX / 2 + 1 };
typedef int time_t;
typedef int addr_t; class Nes_Cpu {
enum { future_time = INT_MAX/2 + 1 }; public:
// Clear registers, map low memory and its three mirrors to address 0,
// Clears registers and maps all pages to unmapped_page // and mirror unmapped_page in remaining memory
void reset( void const* unmapped_page = NULL ); void reset( void const* unmapped_page = 0 );
// Maps code memory (memory accessed via the program counter). Start and size // Map 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 is true, repeats code page
// mirror_size bytes are repeated over the range. mirror_size must be a // throughout address range.
// multiple of page_size. enum { page_size = 0x800 };
enum { page_bits = 11 }; void map_code( nes_addr_t start, unsigned size, void const* code, bool mirror = false );
enum { page_size = 1 << page_bits };
void map_code( addr_t start, int size, void const* code, int mirror_size = 0 ); // Access emulated memory as CPU does
uint8_t const* get_code( nes_addr_t );
// Accesses emulated memory as CPU does
byte const* get_code( addr_t ) const; // 2KB of RAM at address 0
uint8_t low_mem [0x800];
// NES 6502 registers. NOT kept updated during emulation.
struct registers_t { // NES 6502 registers. Not kept updated during a call to run().
BOOST::uint16_t pc; struct registers_t {
byte a; uint16_t pc;
byte x; uint8_t a;
byte y; uint8_t x;
byte flags; uint8_t y;
byte sp; uint8_t status;
}; uint8_t sp;
registers_t r; };
registers_t r;
// Time of beginning of next instruction to be executed
time_t time() const { return cpu_state->time + cpu_state->base; } // Set end_time and run CPU from current time. Returns true if execution
void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; } // stopped due to encountering bad_opcode.
void adjust_time( int delta ) { cpu_state->time += delta; } bool run( nes_time_t end_time );
// Clocks past end (negative if before) // Time of beginning of next instruction to be executed
int time_past_end() const { return cpu_state->time; } nes_time_t time() const { return state->time + state->base; }
void set_time( nes_time_t t ) { state->time = t - state->base; }
// Time of next IRQ void adjust_time( int delta ) { state->time += delta; }
time_t irq_time() const { return irq_time_; }
void set_irq_time( time_t ); nes_time_t irq_time() const { return irq_time_; }
void set_irq_time( nes_time_t );
// Emulation stops once time >= end_time
time_t end_time() const { return end_time_; } nes_time_t end_time() const { return end_time_; }
void set_end_time( time_t ); void set_end_time( nes_time_t );
// Number of unimplemented instructions encountered and skipped // Number of undefined 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 long error_count() const { return error_count_; }
void count_error() { error_count_++; }
// CPU invokes bad opcode handler if it encounters this
// Unmapped page should be filled with this enum { bad_opcode = 0xF2 };
enum { halt_opcode = 0x22 };
public:
enum { irq_inhibit_mask = 0x04 }; Nes_Cpu() { state = &state_; }
enum { page_bits = 11 };
// Can read this many bytes past end of a page enum { page_count = 0x10000 >> page_bits };
enum { cpu_padding = 8 }; enum { irq_inhibit = 0x04 };
private:
private: struct state_t {
// noncopyable uint8_t const* code_map [page_count + 1];
Nes_Cpu( const Nes_Cpu& ); nes_time_t base;
Nes_Cpu& operator = ( const Nes_Cpu& ); int time;
};
state_t* state; // points to state_ or a local copy within run()
// Implementation state_t state_;
public: nes_time_t irq_time_;
Nes_Cpu() { cpu_state = &cpu_state_; } nes_time_t end_time_;
enum { page_count = 0x10000 >> page_bits }; unsigned long error_count_;
struct cpu_state_t { void set_code_page( int, void const* );
byte const* code_map [page_count + 1]; inline int update_end_time( nes_time_t end, nes_time_t irq );
time_t base; };
int time;
}; inline uint8_t const* Nes_Cpu::get_code( nes_addr_t addr )
cpu_state_t* cpu_state; // points to cpu_state_ or a local copy {
cpu_state_t cpu_state_; return state->code_map [addr >> page_bits] + addr
time_t irq_time_; #if !BLARGG_NONPORTABLE
time_t end_time_; % (unsigned) page_size
unsigned error_count_; #endif
;
private: }
void set_code_page( int, void const* );
inline void update_end_time( time_t end, time_t irq ); inline int Nes_Cpu::update_end_time( nes_time_t t, nes_time_t irq )
}; {
if ( irq < t && !(r.status & irq_inhibit) ) t = irq;
#define NES_CPU_PAGE( addr ) ((unsigned) (addr) >> Nes_Cpu::page_bits) int delta = state->base - t;
state->base = t;
#if BLARGG_NONPORTABLE return delta;
#define NES_CPU_OFFSET( addr ) (addr) }
#else
#define NES_CPU_OFFSET( addr ) ((addr) & (Nes_Cpu::page_size - 1)) inline void Nes_Cpu::set_irq_time( nes_time_t t )
#endif {
state->time += update_end_time( end_time_, (irq_time_ = t) );
inline BOOST::uint8_t const* Nes_Cpu::get_code( addr_t addr ) const }
{
return cpu_state_.code_map [NES_CPU_PAGE( addr )] + NES_CPU_OFFSET( addr ); inline void Nes_Cpu::set_end_time( nes_time_t t )
} {
state->time += update_end_time( (end_time_ = t), irq_time_ );
inline void Nes_Cpu::update_end_time( time_t end, time_t irq ) }
{
if ( end > irq && !(r.flags & irq_inhibit_mask) ) #endif
end = irq;
cpu_state->time += cpu_state->base - end;
cpu_state->base = end;
}
inline void Nes_Cpu::set_irq_time( time_t t )
{
irq_time_ = t;
update_end_time( end_time_, t );
}
inline void Nes_Cpu::set_end_time( time_t t )
{
end_time_ = t;
update_end_time( t, irq_time_ );
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,8 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#include <string.h>
int const fract_range = 65536; int const fract_range = 65536;
void Nes_Fds_Apu::reset() void Nes_Fds_Apu::reset()

View file

@ -12,7 +12,6 @@ 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 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 ); }
@ -29,11 +28,6 @@ public:
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,
Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL );
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; )
BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; )
void osc_output( int, Blip_Buffer* ); void osc_output( int, Blip_Buffer* );
private: private:
enum { wave_size = 0x40 }; enum { wave_size = 0x40 };
@ -66,7 +60,7 @@ private:
// synthesis // synthesis
blip_time_t last_time; blip_time_t last_time;
Blip_Buffer* output_; Blip_Buffer* output_;
Blip_Synth_Fast synth; Blip_Synth<blip_med_quality,1> 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]; }
@ -79,12 +73,7 @@ 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::osc_output( int i, Blip_Buffer* buf )
{
output_ = b;
}
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;
@ -131,7 +120,7 @@ inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
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 ); osc_output( 0, NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }

View file

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

View file

@ -1,131 +1,131 @@
// Sunsoft FME-7 sound emulator // Sunsoft FME-7 sound emulator
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#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]; uint8_t regs [reg_count];
BOOST::uint8_t phases [3]; // 0 or 1 uint8_t phases [3]; // 0 or 1
BOOST::uint8_t latch; uint8_t latch;
BOOST::uint16_t delays [3]; // a, b, c 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 output( Blip_Buffer* );
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int index, Blip_Buffer* ); void osc_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<blip_good_quality,1> 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::osc_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::output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; i++ )
set_output( i, buf ); osc_output( i, buf );
} }
inline Nes_Fme7_Apu::Nes_Fme7_Apu() inline Nes_Fme7_Apu::Nes_Fme7_Apu()
{ {
set_output( NULL ); 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 debug_printf
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); debug_printf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
#endif #endif
return; return;
} }
run_until( time ); run_until( time );
regs [latch] = data; regs [latch] = data;
} }
inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) inline void Nes_Fme7_Apu::end_frame( blip_time_t time )
{ {
if ( time > last_time ) if ( time > last_time )
run_until( time ); run_until( time );
assert( last_time >= time ); assert( last_time >= time );
last_time -= time; last_time -= time;
} }
inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const
{ {
*out = *this; *out = *this;
} }
inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in )
{ {
reset(); reset();
fme7_apu_state_t* state = this; fme7_apu_state_t* state = this;
*state = in; *state = in;
} }
#endif #endif

View file

@ -14,30 +14,20 @@ public:
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 osc_output( int i, 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 { end_addr = 0x5015 }; )
}; };
inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b ) inline void Nes_Mmc5_Apu::osc_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
assert( (unsigned) i < osc_count );
if ( i > 1 ) if ( i > 1 )
i += 2; i += 2;
Nes_Apu::set_output( i, b ); Nes_Apu::osc_output( i, b );
}
inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b )
{
set_output( 0, b );
set_output( 1, b );
set_output( 2, b );
} }
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data ) inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )

View file

@ -1,149 +1,145 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Namco_Apu.h" #include "Nes_Namco_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"
Nes_Namco_Apu::Nes_Namco_Apu() Nes_Namco_Apu::Nes_Namco_Apu()
{ {
set_output( NULL ); output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
void Nes_Namco_Apu::reset() void Nes_Namco_Apu::reset()
{ {
last_time = 0; last_time = 0;
addr_reg = 0; addr_reg = 0;
int i; int i;
for ( i = 0; i < reg_count; i++ ) for ( i = 0; i < reg_count; i++ )
reg [i] = 0; reg [i] = 0;
for ( i = 0; i < osc_count; i++ ) for ( i = 0; i < osc_count; i++ )
{ {
Namco_Osc& osc = oscs [i]; Namco_Osc& osc = oscs [i];
osc.delay = 0; osc.delay = 0;
osc.last_amp = 0; osc.last_amp = 0;
} osc.wave_pos = 0;
} }
}
void Nes_Namco_Apu::set_output( Blip_Buffer* buf )
{ void Nes_Namco_Apu::output( Blip_Buffer* buf )
for ( int i = 0; i < osc_count; ++i ) {
set_output( i, buf ); for ( int i = 0; i < osc_count; i++ )
} osc_output( i, buf );
}
/*
void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) /*
{ void Nes_Namco_Apu::reflect_state( Tagged_Data& data )
reflect_int16( data, BLARGG_4CHAR('A','D','D','R'), &addr_reg ); {
reflect_int16( data, BLARGG_4CHAR('A','D','D','R'), &addr_reg );
static const char hex [17] = "0123456789ABCDEF";
int i; static const char hex [17] = "0123456789ABCDEF";
for ( i = 0; i < reg_count; i++ ) int i;
reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], &reg [i] ); for ( i = 0; i < reg_count; i++ )
reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], &reg [i] );
for ( i = 0; i < osc_count; i++ )
{ for ( i = 0; i < osc_count; i++ )
reflect_int32( data, BLARGG_4CHAR('D','L','Y','0') + i, &oscs [i].delay ); {
reflect_int16( data, BLARGG_4CHAR('P','O','S','0') + i, &oscs [i].wave_pos ); reflect_int32( data, BLARGG_4CHAR('D','L','Y','0') + i, &oscs [i].delay );
} reflect_int16( data, BLARGG_4CHAR('P','O','S','0') + i, &oscs [i].wave_pos );
} }
*/ }
*/
void Nes_Namco_Apu::end_frame( blip_time_t time )
{ void Nes_Namco_Apu::end_frame( blip_time_t time )
if ( time > last_time ) {
run_until( time ); if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= time; assert( last_time >= time );
} last_time -= time;
}
void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
{ void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
int active_oscs = (reg [0x7F] >> 4 & 7) + 1; {
for ( int i = osc_count - active_oscs; i < osc_count; i++ ) int active_oscs = (reg [0x7F] >> 4 & 7) + 1;
{ for ( int i = osc_count - active_oscs; i < osc_count; i++ )
Namco_Osc& osc = oscs [i]; {
Blip_Buffer* output = osc.output; Namco_Osc& osc = oscs [i];
if ( !output ) Blip_Buffer* output = osc.output;
continue; if ( !output )
continue;
blip_resampled_time_t time = output->set_modified();
output->resampled_time( last_time ) + osc.delay;
blip_resampled_time_t end_time = output->resampled_time( nes_end_time ); blip_resampled_time_t time =
osc.delay = 0; output->resampled_time( last_time ) + osc.delay;
if ( time < end_time ) blip_resampled_time_t end_time = output->resampled_time( nes_end_time );
{ osc.delay = 0;
const BOOST::uint8_t* osc_reg = &reg [i * 8 + 0x40]; if ( time < end_time )
if ( !(osc_reg [4] & 0xE0) ) {
continue; const uint8_t* osc_reg = &reg [i * 8 + 0x40];
if ( !(osc_reg [4] & 0xE0) )
int volume = osc_reg [7] & 15; continue;
if ( !volume )
continue; int volume = osc_reg [7] & 15;
if ( !volume )
int freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100 + osc_reg [0]; continue;
if ( freq < 64 * active_oscs )
continue; // prevent low frequencies from excessively delaying freq changes blargg_long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0];
if ( freq < 64 * active_oscs )
int const master_clock_divider = 12; // NES time derived via divider of master clock continue; // prevent low frequencies from excessively delaying freq changes
int const n106_divider = 45; // N106 then divides master clock by this blip_resampled_time_t period =
int const max_freq = 0x3FFFF; output->resampled_duration( 983040 ) / freq * active_oscs;
int const lowest_freq_period = (max_freq + 1) * n106_divider / master_clock_divider;
// divide by 8 to avoid overflow int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4;
blip_resampled_time_t period = if ( !wave_size )
output->resampled_duration( lowest_freq_period / 8 ) / freq * 8 * active_oscs; continue;
int wave_size = 256 - (osc_reg [4] & 0xFC); int last_amp = osc.last_amp;
int wave_pos = osc.wave_pos;
int last_amp = osc.last_amp;
int wave_pos = osc_reg [5] % wave_size; do
{
output->set_modified(); // read wave sample
int addr = wave_pos + osc_reg [6];
do int sample = reg [addr >> 1] >> (addr << 2 & 4);
{ wave_pos++;
// read wave sample sample = (sample & 15) * volume;
int addr = (wave_pos + osc_reg [6]) & 0xFF;
int sample = reg [addr >> 1] >> (addr << 2 & 4); // output impulse if amplitude changed
wave_pos++; int delta = sample - last_amp;
sample = (sample & 15) * volume; if ( delta )
{
// output impulse if amplitude changed last_amp = sample;
int delta = sample - last_amp; synth.offset_resampled( time, delta, output );
if ( delta ) }
{
last_amp = sample; // next sample
synth.offset_resampled( time, delta, output ); time += period;
} if ( wave_pos >= wave_size )
wave_pos = 0;
// next sample }
time += period; while ( time < end_time );
if ( wave_pos >= wave_size )
wave_pos = 0; osc.wave_pos = wave_pos;
} osc.last_amp = last_amp;
while ( time < end_time ); }
osc.delay = time - end_time;
((BOOST::uint8_t*)osc_reg)[5] = wave_pos; }
osc.last_amp = last_amp;
} last_time = nes_end_time;
osc.delay = time - end_time; }
}
last_time = nes_end_time;
}

View file

@ -1,101 +1,102 @@
// Namco 106 sound chip emulator // Namco 106 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#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 output( Blip_Buffer* );
enum { osc_count = 8 }; enum { osc_count = 8 };
void set_output( int index, Blip_Buffer* ); void osc_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; blargg_long delay;
Blip_Buffer* output; Blip_Buffer* output;
short last_amp; short last_amp;
}; short wave_pos;
};
Namco_Osc oscs [osc_count];
Namco_Osc oscs [osc_count];
blip_time_t last_time;
int addr_reg; blip_time_t last_time;
int addr_reg;
enum { reg_count = 0x80 };
BOOST::uint8_t reg [reg_count]; enum { reg_count = 0x80 };
Blip_Synth_Norm synth; uint8_t reg [reg_count];
Blip_Synth<blip_good_quality,15> synth;
BOOST::uint8_t& access();
void run_until( blip_time_t ); uint8_t& access();
}; void run_until( blip_time_t );
/* };
struct namco_state_t /*
{ struct namco_state_t
BOOST::uint8_t regs [0x80]; {
BOOST::uint8_t addr; uint8_t regs [0x80];
BOOST::uint8_t unused; uint8_t addr;
BOOST::uint8_t positions [8]; uint8_t unused;
BOOST::uint32_t delays [8]; uint8_t positions [8];
}; uint32_t delays [8];
*/ };
*/
inline BOOST::uint8_t& Nes_Namco_Apu::access()
{ inline uint8_t& Nes_Namco_Apu::access()
int addr = addr_reg & 0x7F; {
if ( addr_reg & 0x80 ) int addr = addr_reg & 0x7F;
addr_reg = (addr + 1) | 0x80; if ( addr_reg & 0x80 )
return reg [addr]; addr_reg = (addr + 1) | 0x80;
} return reg [addr];
}
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count / 15 * v ); }
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * 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::osc_output( int i, Blip_Buffer* buf )
assert( (unsigned) i < osc_count ); {
oscs [i].output = buf; assert( (unsigned) i < osc_count );
} 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 ); {
access() = data; run_until( time );
} access() = data;
}
#endif
#endif

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,216 +1,215 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. 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 ) Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{ {
for ( int i = 0; i < osc_count; ++i ) output( NULL );
set_output( i, buf ); volume( 1.0 );
} reset();
}
void Nes_Vrc6_Apu::reset()
{ void Nes_Vrc6_Apu::reset()
last_time = 0; {
for ( int i = 0; i < osc_count; i++ ) last_time = 0;
{ for ( int i = 0; i < osc_count; i++ )
Vrc6_Osc& osc = oscs [i]; {
for ( int j = 0; j < reg_count; j++ ) Vrc6_Osc& osc = oscs [i];
osc.regs [j] = 0; for ( int j = 0; j < reg_count; j++ )
osc.delay = 0; osc.regs [j] = 0;
osc.last_amp = 0; osc.delay = 0;
osc.phase = 1; osc.last_amp = 0;
osc.amp = 0; osc.phase = 1;
} osc.amp = 0;
} }
}
Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{ void Nes_Vrc6_Apu::output( Blip_Buffer* buf )
set_output( NULL ); {
volume( 1.0 ); for ( int i = 0; i < osc_count; i++ )
reset(); osc_output( i, buf );
} }
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;
output->set_modified();
int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) ) int volume = osc.regs [0] & 15;
volume = 0; if ( !(osc.regs [2] & 0x80) )
volume = 0;
int gate = osc.regs [0] & 0x80;
int duty = ((osc.regs [0] >> 4) & 7) + 1; int gate = osc.regs [0] & 0x80;
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; int duty = ((osc.regs [0] >> 4) & 7) + 1;
blip_time_t time = last_time; int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
if ( delta ) blip_time_t time = last_time;
{ if ( delta )
osc.last_amp += delta; {
output->set_modified(); osc.last_amp += delta;
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();
do
do {
{ phase++;
phase++; if ( phase == 16 )
if ( phase == 16 ) {
{ phase = 0;
phase = 0; osc.last_amp = volume;
osc.last_amp = volume; square_synth.offset( time, volume, output );
square_synth.offset( time, volume, output ); }
} if ( phase == duty )
if ( phase == duty ) {
{ osc.last_amp = 0;
osc.last_amp = 0; square_synth.offset( time, -volume, output );
square_synth.offset( time, -volume, output ); }
} time += period;
time += period; }
} while ( time < end_time );
while ( time < end_time );
osc.phase = phase;
osc.phase = phase; }
} osc.delay = time - end_time;
osc.delay = time - end_time; }
} }
}
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time )
void Nes_Vrc6_Apu::run_saw( blip_time_t end_time ) {
{ Vrc6_Osc& osc = oscs [2];
Vrc6_Osc& osc = oscs [2]; Blip_Buffer* output = osc.output;
Blip_Buffer* output = osc.output; if ( !output )
if ( !output ) return;
return; output->set_modified();
output->set_modified();
int amp = osc.amp;
int amp = osc.amp; int amp_step = osc.regs [0] & 0x3F;
int amp_step = osc.regs [0] & 0x3F; blip_time_t time = last_time;
blip_time_t time = last_time; int last_amp = osc.last_amp;
int last_amp = osc.last_amp; if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) {
{ osc.delay = 0;
osc.delay = 0; int delta = (amp >> 3) - last_amp;
int delta = (amp >> 3) - last_amp; last_amp = amp >> 3;
last_amp = amp >> 3; saw_synth.offset( time, delta, output );
saw_synth.offset( time, delta, output ); }
} else
else {
{ time += osc.delay;
time += osc.delay; if ( time < end_time )
if ( time < end_time ) {
{ int period = osc.period() * 2;
int period = osc.period() * 2; int phase = osc.phase;
int phase = osc.phase;
do
do {
{ if ( --phase == 0 )
if ( --phase == 0 ) {
{ phase = 7;
phase = 7; amp = 0;
amp = 0; }
}
int delta = (amp >> 3) - last_amp;
int delta = (amp >> 3) - last_amp; if ( delta )
if ( delta ) {
{ last_amp = amp >> 3;
last_amp = amp >> 3; saw_synth.offset( time, delta, output );
saw_synth.offset( time, delta, output ); }
}
time += period;
time += period; amp = (amp + amp_step) & 0xFF;
amp = (amp + amp_step) & 0xFF; }
} while ( time < end_time );
while ( time < end_time );
osc.phase = phase;
osc.phase = phase; osc.amp = amp;
osc.amp = amp; }
}
osc.delay = time - end_time;
osc.delay = time - end_time; }
}
osc.last_amp = last_amp;
osc.last_amp = last_amp; }
}

View file

@ -1,95 +1,95 @@
// Konami VRC6 sound chip emulator // Konami VRC6 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#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 output( Blip_Buffer* );
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int index, Blip_Buffer* ); void osc_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]; 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) * 0x100L + 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<blip_med_quality,1> saw_synth;
Blip_Synth_Norm square_synth; Blip_Synth<blip_good_quality,1> 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]; uint8_t regs [3] [3];
BOOST::uint8_t saw_amp; uint8_t saw_amp;
BOOST::uint16_t delays [3]; uint16_t delays [3];
BOOST::uint8_t phases [3]; uint8_t phases [3];
BOOST::uint8_t unused; uint8_t unused;
}; };
inline void Nes_Vrc6_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;
} }
inline void Nes_Vrc6_Apu::volume( double v ) inline void Nes_Vrc6_Apu::volume( double v )
{ {
double const factor = 0.0967 * 2; double const factor = 0.0967 * 2;
saw_synth.volume( factor / 31 * v ); saw_synth.volume( factor / 31 * v );
square_synth.volume( factor * 0.5 / 15 * v ); square_synth.volume( factor * 0.5 / 15 * v );
} }
inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq )
{ {
saw_synth.treble_eq( eq ); saw_synth.treble_eq( eq );
square_synth.treble_eq( eq ); square_synth.treble_eq( eq );
} }
#endif #endif

View file

@ -1,7 +1,7 @@
#include "Nes_Vrc7_Apu.h" #include "Nes_Vrc7_Apu.h"
extern "C" { extern "C" {
#include "../vgmplay/chips/emu2413.h" #include "../ext/emu2413.h"
} }
#include <string.h> #include <string.h>
@ -10,7 +10,7 @@ extern "C" {
static unsigned char vrc7_inst[(16 + 3) * 8] = static unsigned char vrc7_inst[(16 + 3) * 8] =
{ {
#include "../vgmplay/chips/vrc7tone.h" #include "../ext/vrc7tone.h"
}; };
int const period = 36; // NES CPU clocks per FM clock int const period = 36; // NES CPU clocks per FM clock

View file

@ -18,7 +18,7 @@ public:
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 osc_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& );
@ -37,14 +37,14 @@ private:
struct Vrc7_Osc struct Vrc7_Osc
{ {
BOOST::uint8_t regs [3]; 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];
BOOST::uint8_t kon; uint8_t kon;
BOOST::uint8_t inst [8]; uint8_t inst [8];
void* opll; void* opll;
int addr; int addr;
blip_time_t next_time; blip_time_t next_time;
@ -53,7 +53,7 @@ private:
int last_amp; int last_amp;
} mono; } mono;
Blip_Synth_Fast synth; Blip_Synth<blip_med_quality,1> synth;
void run_until( blip_time_t ); void run_until( blip_time_t );
void output_changed(); void output_changed();
@ -61,13 +61,13 @@ private:
struct vrc7_snapshot_t struct vrc7_snapshot_t
{ {
BOOST::uint8_t latch; uint8_t latch;
BOOST::uint8_t inst [8]; uint8_t inst [8];
BOOST::uint8_t regs [6] [3]; uint8_t regs [6] [3];
BOOST::uint8_t delay; uint8_t delay;
}; };
inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Vrc7_Apu::osc_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

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