Added gme sources and fixed compiler/linker errors.

This commit is contained in:
casey langen 2019-01-02 22:25:38 -08:00
parent 26ae81de1d
commit c12d05da96
265 changed files with 70687 additions and 1 deletions

View File

@ -1,7 +1,130 @@
set (gmedecoder_SOURCES
plugin.cpp
GmeDecoder.cpp
gme/c140.c
gme/dac_control.c
gme/fm.c
gme/fm2612.c
gme/gme_custom_dprintf.c
gme/k051649.c
gme/k053260.c
gme/k054539.c
gme/okim6258.c
gme/okim6295.c
gme/pwm.c
gme/qmix.c
gme/rf5c68.c
gme/s_deltat.c
gme/s_logtbl.c
gme/s_opl.c
gme/s_opltbl.c
gme/scd_pcm.c
gme/segapcm.c
gme/ym2151.c
gme/ym2413.c
gme/ymz280b.c
gme/Ay_Apu.cpp
gme/Ay_Core.cpp
gme/Ay_Cpu.cpp
gme/Ay_Emu.cpp
gme/blargg_common.cpp
gme/blargg_errors.cpp
gme/Blip_Buffer.cpp
gme/Bml_Parser.cpp
gme/C140_Emu.cpp
gme/Classic_Emu.cpp
gme/Data_Reader.cpp
gme/dbopl.cpp
gme/Downsampler.cpp
gme/Dual_Resampler.cpp
gme/Effects_Buffer.cpp
gme/Fir_Resampler.cpp
gme/fmopl.cpp
gme/Gb_Apu.cpp
gme/Gb_Cpu.cpp
gme/Gb_Oscs.cpp
gme/Gbs_Core.cpp
gme/Gbs_Cpu.cpp
gme/Gbs_Emu.cpp
gme/Gme_File.cpp
gme/Gme_Loader.cpp
gme/gme.cpp
gme/Gym_Emu.cpp
gme/Hes_Apu_Adpcm.cpp
gme/Hes_Apu.cpp
gme/Hes_Core.cpp
gme/Hes_Cpu.cpp
gme/Hes_Emu.cpp
gme/K051649_Emu.cpp
gme/K053260_Emu.cpp
gme/K054539_Emu.cpp
gme/Kss_Core.cpp
gme/Kss_Cpu.cpp
gme/Kss_Emu.cpp
gme/Kss_Scc_Apu.cpp
gme/M3u_Playlist.cpp
gme/Multi_Buffer.cpp
gme/Music_Emu.cpp
gme/Nes_Apu.cpp
gme/Nes_Cpu.cpp
gme/Nes_Fds_Apu.cpp
gme/Nes_Fme7_Apu.cpp
gme/Nes_Namco_Apu.cpp
gme/Nes_Oscs.cpp
gme/Nes_Vrc6_Apu.cpp
gme/Nes_Vrc7_Apu.cpp
gme/Nsf_Core.cpp
gme/Nsf_Cpu.cpp
gme/Nsf_Emu.cpp
gme/Nsf_Impl.cpp
gme/Nsfe_Emu.cpp
gme/Okim6258_Emu.cpp
gme/Okim6295_Emu.cpp
gme/Opl_Apu.cpp
gme/Pwm_Emu.cpp
gme/Qsound_Apu.cpp
gme/Resampler.cpp
gme/Rf5C164_Emu.cpp
gme/Rf5C68_Emu.cpp
gme/Rom_Data.cpp
gme/Sap_Apu.cpp
gme/Sap_Core.cpp
gme/Sap_Cpu.cpp
gme/Sap_Emu.cpp
gme/SegaPcm_Emu.cpp
gme/Sgc_Core.cpp
gme/Sgc_Cpu.cpp
gme/Sgc_Emu.cpp
gme/Sgc_Impl.cpp
gme/Sms_Apu.cpp
gme/Sms_Fm_Apu.cpp
gme/Spc_Emu.cpp
gme/Spc_Filter.cpp
gme/Spc_Sfm.cpp
gme/Track_Filter.cpp
gme/Upsampler.cpp
gme/Vgm_Core.cpp
gme/Vgm_Emu.cpp
gme/Ym2151_Emu.cpp
gme/Ym2203_Emu.cpp
gme/Ym2413_Emu.cpp
gme/Ym2608_Emu.cpp
gme/Ym2610b_Emu.cpp
gme/Ym2612_Emu.cpp
gme/Ym3812_Emu.cpp
gme/ymdeltat.cpp
gme/Ymf262_Emu.cpp
gme/Ymz280b_Emu.cpp
gme/Z80_Cpu.cpp
gme/higan/processor/spc700/spc700.cpp
gme/higan/smp/memory.cpp
gme/higan/smp/timing.cpp
gme/higan/smp/smp.cpp
gme/higan/dsp/dsp.cpp
gme/higan/dsp/SPC_DSP.cpp
)
add_definitions(-DHAVE_STDINT_H)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/gme")
add_library(gmedecoder SHARED ${gmedecoder_SOURCES})
target_link_libraries(gmedecoder ${musikcube_LINK_LIBS})

View File

@ -45,7 +45,7 @@ bool GmeDecoder::Open(musik::core::sdk::IDataStream *stream){
}
void GmeDecoder::Release() {
delete this;
delete this;
}
double GmeDecoder::SetPosition(double seconds) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,357 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Ay_Emu.h"
#include "blargg_endian.h"
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// TODO: probably don't need detailed errors as to why file is corrupt
int const spectrum_clock = 3546900; // 128K Spectrum
int const spectrum_period = 70908;
//int const spectrum_clock = 3500000; // 48K Spectrum
//int const spectrum_period = 69888;
int const cpc_clock = 2000000;
Ay_Emu::Ay_Emu()
{
core.set_cpc_callback( enable_cpc_, this );
set_type( gme_ay_type );
set_silence_lookahead( 6 );
}
Ay_Emu::~Ay_Emu() { }
// Track info
// 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 byte const* get_data( Ay_Emu::file_t const& file, byte const ptr [], int min_size )
{
int offset = (BOOST::int16_t) get_be16( ptr );
int pos = ptr - (byte const*) file.header;
int size = file.end - (byte const*) file.header;
assert( (unsigned) pos <= (unsigned) size - 2 );
int limit = size - min_size;
if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit )
return NULL;
return ptr + offset;
}
static blargg_err_t parse_header( byte const in [], int size, Ay_Emu::file_t* out )
{
typedef Ay_Emu::header_t header_t;
if ( size < header_t::size )
return blargg_err_file_type;
out->header = (header_t const*) in;
out->end = in + size;
header_t const& h = *(header_t const*) in;
if ( memcmp( h.tag, "ZXAYEMUL", 8 ) )
return blargg_err_file_type;
out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 );
if ( !out->tracks )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "missing track data" );
return blargg_ok;
}
static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track )
{
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 );
if ( track_info )
out->length = get_be16( track_info + 4 ) * (1000 / 50); // frames to msec
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 ) );
}
static void hash_ay_file( Ay_Emu::file_t const& file, Gme_Info_::Hash_Function& out )
{
out.hash_( &file.header->vers, sizeof(file.header->vers) );
out.hash_( &file.header->player, sizeof(file.header->player) );
out.hash_( &file.header->unused[0], sizeof(file.header->unused) );
out.hash_( &file.header->max_track, sizeof(file.header->max_track) );
out.hash_( &file.header->first_track, sizeof(file.header->first_track) );
for ( unsigned i = 0; i <= file.header->max_track; i++ )
{
byte const* track_info = get_data( file, file.tracks + i * 4 + 2, 14 );
if ( track_info )
{
out.hash_( track_info + 8, 2 );
byte const* points = get_data( file, track_info + 10, 6 );
if ( points ) out.hash_( points, 6 );
byte const* blocks = get_data( file, track_info + 12, 8 );
if ( blocks )
{
int addr = get_be16( blocks );
while ( addr )
{
out.hash_( blocks, 4 );
int len = get_be16( blocks + 2 );
byte const* block = get_data( file, blocks + 4, len );
if ( block ) out.hash_( block, len );
blocks += 6;
addr = get_be16( blocks );
}
}
}
}
}
blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const
{
copy_ay_fields( file, out, track );
return blargg_ok;
}
struct Ay_File : Gme_Info_
{
Ay_Emu::file_t file;
Ay_File() { set_type( gme_ay_type ); }
blargg_err_t load_mem_( byte const begin [], int size )
{
RETURN_ERR( parse_header( begin, size, &file ) );
set_track_count( file.header->max_track + 1 );
return blargg_ok;
}
blargg_err_t track_info_( track_info_t* out, int track ) const
{
copy_ay_fields( file, out, track );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
hash_ay_file( file, out );
return blargg_ok;
}
};
static Music_Emu* new_ay_emu ()
{
return BLARGG_NEW Ay_Emu;
}
static Music_Emu* new_ay_file()
{
return BLARGG_NEW Ay_File;
}
gme_type_t_ const gme_ay_type [1] = {{
"ZX Spectrum",
0,
&new_ay_emu,
&new_ay_file,
"AY",
1
}};
// Setup
blargg_err_t Ay_Emu::load_mem_( byte const in [], int size )
{
assert( offsetof (header_t,track_info [2]) == header_t::size );
RETURN_ERR( parse_header( in, size, &file ) );
set_track_count( file.header->max_track + 1 );
if ( file.header->vers > 2 )
set_warning( "Unknown file version" );
int const osc_count = Ay_Apu::osc_count + 1; // +1 for beeper
set_voice_count( osc_count );
core.apu().volume( gain() );
static const char* const names [osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Beeper"
};
set_voice_names( names );
static int const types [osc_count] = {
wave_type+0, wave_type+1, wave_type+2, mixed_type+1
};
set_voice_types( types );
return setup_buffer( spectrum_clock );
}
void Ay_Emu::update_eq( blip_eq_t const& eq )
{
core.apu().treble_eq( eq );
}
void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* )
{
if ( i >= Ay_Apu::osc_count )
core.set_beeper_output( center );
else
core.apu().set_output( i, center );
}
void Ay_Emu::set_tempo_( double t )
{
int p = spectrum_period;
if ( clock_rate() != spectrum_clock )
p = clock_rate() / 50;
core.set_play_period( blip_time_t (p / t) );
}
blargg_err_t Ay_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
byte* const mem = core.mem();
memset( mem + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
memset( mem + 0x0100, 0xFF, 0x4000 - 0x100 );
memset( mem + core.ram_addr, 0x00, core.mem_size - core.ram_addr );
// locate data blocks
byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
if ( !data )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
byte const* const more_data = get_data( file, data + 10, 6 );
if ( !more_data )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
byte const* blocks = get_data( file, data + 12, 8 );
if ( !blocks )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
// initial addresses
unsigned addr = get_be16( blocks );
if ( !addr )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
unsigned init = get_be16( more_data + 2 );
if ( !init )
init = addr;
// copy blocks into memory
do
{
blocks += 2;
unsigned len = get_be16( blocks ); blocks += 2;
if ( addr + len > core.mem_size )
{
set_warning( "Bad data block size" );
len = core.mem_size - addr;
}
check( len );
byte const* in = get_data( file, blocks, 0 ); blocks += 2;
if ( len > (unsigned) (file.end - in) )
{
set_warning( "File data missing" );
len = file.end - in;
}
//dprintf( "addr: $%04X, len: $%04X\n", addr, len );
if ( addr < core.ram_addr && addr >= 0x400 ) // several tracks use low data
dprintf( "Block addr in ROM\n" );
memcpy( mem + addr, in, len );
if ( file.end - blocks < 8 )
{
set_warning( "File data missing" );
break;
}
}
while ( (addr = get_be16( blocks )) != 0 );
// copy and configure driver
static byte const passive [] = {
0xF3, // DI
0xCD, 0, 0, // CALL init
0xED, 0x5E, // LOOP: IM 2
0xFB, // EI
0x76, // HALT
0x18, 0xFA // JR LOOP
};
static byte const active [] = {
0xF3, // DI
0xCD, 0, 0, // CALL init
0xED, 0x56, // LOOP: IM 1
0xFB, // EI
0x76, // HALT
0xCD, 0, 0, // CALL play
0x18, 0xF7 // JR LOOP
};
memcpy( mem, passive, sizeof passive );
int const play_addr = get_be16( more_data + 4 );
if ( play_addr )
{
memcpy( mem, active, sizeof active );
mem [ 9] = play_addr;
mem [10] = play_addr >> 8;
}
mem [2] = init;
mem [3] = init >> 8;
mem [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
// start at spectrum speed
change_clock_rate( spectrum_clock );
set_tempo( tempo() );
Ay_Core::registers_t r = { };
r.sp = get_be16( more_data );
r.b.a = r.b.b = r.b.d = r.b.h = data [8];
r.b.flags = r.b.c = r.b.e = r.b.l = data [9];
r.alt.w = r.w;
r.ix = r.iy = r.w.hl;
core.start_track( r, play_addr );
return blargg_ok;
}
blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
{
core.end_frame( &duration );
return blargg_ok;
}
inline void Ay_Emu::enable_cpc()
{
change_clock_rate( cpc_clock );
set_tempo( tempo() );
}
void Ay_Emu::enable_cpc_( void* data )
{
STATIC_CAST(Ay_Emu*,data)->enable_cpc();
}
blargg_err_t Ay_Emu::hash_( Hash_Function& out ) const
{
hash_ay_file( file, out );
return blargg_ok;
}

View File

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

View File

@ -0,0 +1,509 @@
// Blip_Buffer $vers. http://www.slack.net/~ant/
#include "Blip_Buffer.h"
#include <math.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"
//// Blip_Buffer
Blip_Buffer::Blip_Buffer()
{
factor_ = UINT_MAX/2 + 1;
buffer_ = NULL;
buffer_center_ = NULL;
buffer_size_ = 0;
sample_rate_ = 0;
bass_shift_ = 0;
clock_rate_ = 0;
bass_freq_ = 16;
length_ = 0;
// assumptions code makes about implementation-defined features
#ifndef NDEBUG
// right shift of negative value preserves sign
int i = -0x7FFFFFFE;
assert( (i >> 1) == -0x3FFFFFFF );
// casting truncates and sign-extends
i = 0x18000;
assert( (BOOST::int16_t) i == -0x8000 );
#endif
clear();
}
Blip_Buffer::~Blip_Buffer()
{
free( buffer_ );
}
void Blip_Buffer::clear()
{
bool const entire_buffer = true;
offset_ = 0;
reader_accum_ = 0;
modified_ = false;
if ( buffer_ )
{
int count = (entire_buffer ? buffer_size_ : samples_avail());
memset( buffer_, 0, (count + blip_buffer_extra_) * sizeof (delta_t) );
}
}
blargg_err_t Blip_Buffer::set_sample_rate( int new_rate, int msec )
{
// Limit to maximum size that resampled time can represent
int max_size = (((blip_resampled_time_t) -1) >> BLIP_BUFFER_ACCURACY) -
blip_buffer_extra_ - 64; // TODO: -64 isn't needed
int new_size = (new_rate * (msec + 1) + 999) / 1000;
if ( new_size > max_size )
new_size = max_size;
// Resize buffer
if ( buffer_size_ != new_size )
{
//dprintf( "%d \n", (new_size + blip_buffer_extra_) * sizeof *buffer_ );
void* p = realloc( buffer_, (new_size + blip_buffer_extra_) * sizeof *buffer_ );
CHECK_ALLOC( p );
buffer_ = (delta_t*) p;
buffer_center_ = buffer_ + BLIP_MAX_QUALITY/2;
buffer_size_ = new_size;
}
// Update sample_rate and things that depend on it
sample_rate_ = new_rate;
length_ = new_size * 1000 / new_rate - 1;
if ( clock_rate_ )
clock_rate( clock_rate_ );
bass_freq( bass_freq_ );
clear();
return blargg_ok;
}
blip_resampled_time_t Blip_Buffer::clock_rate_factor( int rate ) const
{
double ratio = (double) sample_rate_ / rate;
int factor = (int) floor( ratio * (1 << BLIP_BUFFER_ACCURACY) + 0.5 );
assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large
return (blip_resampled_time_t) factor;
}
void Blip_Buffer::bass_freq( int freq )
{
bass_freq_ = freq;
int shift = 31;
if ( freq > 0 && sample_rate_ )
{
shift = 13;
int f = (freq << 16) / sample_rate_;
while ( (f >>= 1) != 0 && --shift ) { }
}
bass_shift_ = shift;
}
void Blip_Buffer::end_frame( blip_time_t t )
{
offset_ += t * factor_;
assert( samples_avail() <= (int) buffer_size_ ); // fails if time is past end of buffer
}
int Blip_Buffer::count_samples( blip_time_t t ) const
{
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);
}
blip_time_t Blip_Buffer::count_clocks( int count ) const
{
if ( count > buffer_size_ )
count = buffer_size_;
blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_);
}
void Blip_Buffer::remove_samples( int count )
{
if ( count )
{
remove_silence( count );
// copy remaining samples to beginning and clear old samples
int remain = samples_avail() + blip_buffer_extra_;
memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ );
memset( buffer_ + remain, 0, count * sizeof *buffer_ );
}
}
int Blip_Buffer::read_samples( blip_sample_t out_ [], int max_samples, bool stereo )
{
int count = samples_avail();
if ( count > max_samples )
count = max_samples;
if ( count )
{
int const bass = highpass_shift();
delta_t const* reader = read_pos() + count;
int reader_sum = integrator();
blip_sample_t* BLARGG_RESTRICT out = out_ + count;
if ( stereo )
out += count;
int offset = -count;
if ( !stereo )
{
do
{
int s = reader_sum >> delta_bits;
reader_sum -= reader_sum >> bass;
reader_sum += reader [offset];
BLIP_CLAMP( s, s );
out [offset] = (blip_sample_t) s;
}
while ( ++offset );
}
else
{
do
{
int s = reader_sum >> delta_bits;
reader_sum -= reader_sum >> bass;
reader_sum += reader [offset];
BLIP_CLAMP( s, s );
out [offset * 2] = (blip_sample_t) s;
}
while ( ++offset );
}
set_integrator( reader_sum );
remove_samples( count );
}
return count;
}
void Blip_Buffer::mix_samples( blip_sample_t const in [], int count )
{
delta_t* out = buffer_center_ + (offset_ >> BLIP_BUFFER_ACCURACY);
int const sample_shift = blip_sample_bits - 16;
int prev = 0;
while ( --count >= 0 )
{
int s = *in++ << sample_shift;
*out += s - prev;
prev = s;
++out;
}
*out -= prev;
}
void Blip_Buffer::save_state( blip_buffer_state_t* out )
{
assert( samples_avail() == 0 );
out->offset_ = offset_;
out->reader_accum_ = reader_accum_;
memcpy( out->buf, &buffer_ [offset_ >> BLIP_BUFFER_ACCURACY], sizeof out->buf );
}
void Blip_Buffer::load_state( blip_buffer_state_t const& in )
{
clear();
offset_ = in.offset_;
reader_accum_ = in.reader_accum_;
memcpy( buffer_, in.buf, sizeof in.buf );
}
//// Blip_Synth_
Blip_Synth_Fast_::Blip_Synth_Fast_()
{
buf = NULL;
last_amp = 0;
delta_factor = 0;
}
void Blip_Synth_Fast_::volume_unit( double new_unit )
{
delta_factor = int (new_unit * (1 << blip_sample_bits) + 0.5);
}
#if BLIP_BUFFER_FAST
void blip_eq_t::generate( float* out, int count ) const { }
#else
Blip_Synth_::Blip_Synth_( short p [], int w ) :
phases( p ),
width( w )
{
volume_unit_ = 0.0;
kernel_unit = 0;
buf = NULL;
last_amp = 0;
delta_factor = 0;
}
#undef PI
#define PI 3.1415926535897932384626433832795029
// Generates right half of sinc kernel (including center point) with cutoff at
// sample rate / 2 / oversample. Frequency response at cutoff frequency is
// treble dB (-6=0.5,-12=0.25). Mid controls frequency that rolloff begins at,
// cut * sample rate / 2.
static void gen_sinc( float out [], int out_size, double oversample,
double treble, double mid )
{
if ( mid > 0.9999 ) mid = 0.9999;
if ( treble < -300.0 ) treble = -300.0;
if ( treble > 5.0 ) treble = 5.0;
double const maxh = 4096.0;
double rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - mid) );
double const pow_a_n = pow( rolloff, maxh - maxh * mid );
double const to_angle = PI / maxh / oversample;
for ( int i = 1; i < out_size; i++ )
{
double angle = i * to_angle;
double c = rolloff * cos( angle * maxh - angle ) -
cos( angle * maxh );
double cos_nc_angle = cos( angle * maxh * mid );
double cos_nc1_angle = cos( angle * maxh * mid - angle );
double cos_angle = cos( angle );
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;
double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle;
out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d
}
// Approximate center by looking at two points to right. Much simpler
// and more reliable than trying to calculate it properly.
out [0] = out [1] + 0.5 * (out [1] - out [2]);
}
// 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().
static void kaiser_window( float io [], int count, float beta )
{
int const accuracy = 10;
float const beta2 = beta * beta;
float const step = (float) 0.5 / count;
float pos = (float) 0.5;
for ( float* const end = io + count; io < end; ++io )
{
float x = (pos - pos*pos) * beta2;
float u = x;
float k = 1;
float n = 2;
// Keep refining until adjustment becomes small
do
{
u *= x / (n * n);
n += 1;
k += u;
}
while ( k <= u * (1 << accuracy) );
pos += step;
*io *= k;
}
}
void blip_eq_t::generate( float out [], int count ) const
{
// lower cutoff freq for narrow kernels with their wider transition band
// (8 points->1.49, 16 points->1.15)
double cutoff_adj = blip_res * 2.25 / count + 0.85;
if ( cutoff_adj < 1.02 )
cutoff_adj = 1.02;
double half_rate = sample_rate * 0.5;
if ( cutoff_freq )
cutoff_adj = half_rate / cutoff_freq;
double cutoff = rolloff_freq * cutoff_adj / half_rate;
gen_sinc( out, count, oversample * cutoff_adj, treble, cutoff );
kaiser_window( out, count, kaiser );
}
void Blip_Synth_::treble_eq( blip_eq_t const& eq )
{
// Generate right half of kernel
int const half_size = blip_eq_t::calc_count( width );
float fimpulse [blip_res / 2 * (BLIP_MAX_QUALITY - 1) + 1];
eq.generate( fimpulse, half_size );
int i;
// Find rescale factor. Summing from small to large (right to left)
// reduces error.
double total = 0.0;
for ( i = half_size; --i > 0; )
total += fimpulse [i];
total = total * 2.0 + fimpulse [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
double rescale = base_unit / total;
kernel_unit = (int) base_unit;
// Integrate, first difference, rescale, convert to int
double sum = 0;
double next = 0;
int const size = impulses_size();
for ( i = 0; i < size; i++ )
{
int j = (half_size - 1) - i;
if ( i >= blip_res )
sum += fimpulse [j + blip_res];
// goes slightly past center, so it needs a little mirroring
next += fimpulse [j < 0 ? -j : j];
// calculate unintereleved index
int x = (~i & (blip_res - 1)) * (width >> 1) + (i >> BLIP_PHASE_BITS);
assert( (unsigned) x < (unsigned) size );
// flooring separately virtually eliminates error
phases [x] = (short) (int)
(floor( sum * rescale + 0.5 ) - floor( next * rescale + 0.5 ));
//phases [x] = (short) (int)
// floor( sum * rescale - next * rescale + 0.5 );
}
adjust_impulse();
// volume might require rescaling
double vol = volume_unit_;
if ( vol )
{
volume_unit_ = 0.0;
volume_unit( vol );
}
}
void Blip_Synth_::adjust_impulse()
{
int const size = impulses_size();
int const half_width = width / 2;
// Sum each phase as would be done when synthesizing, and correct
// any that don't add up to exactly kernel_half.
for ( int phase = blip_res / 2; --phase >= 0; )
{
int const fwd = phase * half_width;
int const rev = size - half_width - fwd;
int error = kernel_unit;
for ( int i = half_width; --i >= 0; )
{
error += phases [fwd + i];
error += phases [rev + i];
}
phases [fwd + half_width - 1] -= (short) error;
// Error shouldn't occur now with improved calculation
//if ( error ) printf( "error: %ld\n", error );
}
#if 0
for ( int i = 0; i < blip_res; i++, printf( "\n" ) )
for ( int j = 0; j < width / 2; j++ )
printf( "%5d,", (int) -phases [j + width/2 * i] );
#endif
}
void Blip_Synth_::rescale_kernel( int shift )
{
// Keep values positive to avoid round-towards-zero of sign-preserving
// right shift for negative values.
int const keep_positive = 0x8000 + (1 << (shift - 1));
int const half_width = width / 2;
for ( int phase = blip_res; --phase >= 0; )
{
int const fwd = phase * half_width;
// 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

@ -0,0 +1,198 @@
// Band-limited sound synthesis buffer
// Blip_Buffer $vers
#ifndef BLIP_BUFFER_H
#define BLIP_BUFFER_H
#include "blargg_common.h"
#include "Blip_Buffer_impl.h"
typedef int blip_time_t; // Source clocks in current time frame
typedef BOOST::int16_t blip_sample_t; // 16-bit signed output sample
int const blip_default_length = 1000 / 4; // Default Blip_Buffer length (1/4 second)
//// Sample buffer for band-limited synthesis
class Blip_Buffer : public Blip_Buffer_ {
public:
// Sets output sample rate and resizes and clears sample buffer
blargg_err_t set_sample_rate( int samples_per_sec, int msec_length = blip_default_length );
// Sets number of source time units per second
void clock_rate( int clocks_per_sec );
// Clears buffer and removes all samples
void clear();
// Use Blip_Synth to add waveform to buffer
// Resamples to time t, then subtracts t from current time. Appends result of resampling
// to buffer for reading.
void end_frame( blip_time_t t );
// Number of samples available for reading with read_samples()
int samples_avail() const;
// Reads at most n samples to out [0 to n-1] and returns number actually read. If stereo
// is true, writes to out [0], out [2], out [4] etc. instead.
int read_samples( blip_sample_t out [], int n, bool stereo = false );
// More features
// Sets flag that tells some Multi_Buffer types that sound was added to buffer,
// 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.
void set_modified() { modified_ = true; }
// Sets high-pass filter frequency, from 0 to 20000 Hz, where higher values reduce bass more
void bass_freq( int frequency );
int length() const; // Length of buffer in milliseconds
int sample_rate() const; // Current output sample rate
int clock_rate() const; // Number of source time units per second
int output_latency() const; // Number of samples delay from offset() to read_samples()
// Low-level features
// Removes the first n samples
void remove_samples( int n );
// Returns number of clocks needed until n samples will be available.
// If buffer cannot even hold n samples, returns number of clocks
// until buffer becomes full.
blip_time_t count_clocks( int n ) const;
// Number of samples that should be mixed before calling end_frame( t )
int count_samples( blip_time_t t ) const;
// Mixes n samples into buffer
void mix_samples( const blip_sample_t in [], int n );
// Resampled time (sorry, poor documentation right now)
// Resampled time is fixed-point, in terms of output samples.
// Converts clock count to resampled time
blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; }
// Converts clock time since beginning of current time frame to resampled time
blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; }
// Returns factor that converts clock rate to resampled time
blip_resampled_time_t clock_rate_factor( int clock_rate ) const;
// State save/load
// Saves state, including high-pass filter and tails of last deltas.
// All samples must have been read from buffer before calling this
// (that is, samples_avail() must return 0).
void save_state( blip_buffer_state_t* out );
// Loads state. State must have been saved from Blip_Buffer with same
// settings during same run of program; states can NOT be stored on disk.
// Clears buffer before loading state.
void load_state( const blip_buffer_state_t& in );
private:
// noncopyable
Blip_Buffer( const Blip_Buffer& );
Blip_Buffer& operator = ( const Blip_Buffer& );
// Implementation
public:
BLARGG_DISABLE_NOTHROW
Blip_Buffer();
~Blip_Buffer();
void remove_silence( int n );
};
//// Adds amplitude changes to Blip_Buffer
template<int quality,int range> class Blip_Synth;
typedef Blip_Synth<8, 1> Blip_Synth_Fast; // faster, but less equalizer control
typedef Blip_Synth<12,1> Blip_Synth_Norm; // good for most things
typedef Blip_Synth<16,1> Blip_Synth_Good; // sharper filter cutoff
template<int quality,int range>
class Blip_Synth {
public:
// Sets volume of amplitude delta unit
void volume( double v ) { impl.volume_unit( 1.0 / range * v ); }
// Configures low-pass filter
void treble_eq( const blip_eq_t& eq ) { impl.treble_eq( eq ); }
// Gets/sets default Blip_Buffer
Blip_Buffer* output() const { return impl.buf; }
void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; }
// Extends waveform to time t at current amplitude, then changes its amplitude to a
// Using this requires a separate Blip_Synth for each waveform.
void update( blip_time_t t, int a );
// Low-level interface
// If no Blip_Buffer* is specified, uses one set by output() above
// Adds amplitude transition at time t. Delta can be positive or negative.
// The actual change in amplitude is delta * volume.
void offset( blip_time_t t, int delta, Blip_Buffer* ) const;
void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); }
// 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( buf->to_fixed( t ), delta, buf ); }
void offset_inline( blip_time_t t, int delta ) const { offset_resampled( impl.buf->to_fixed( t ), delta, impl.buf ); }
// Works directly in terms of fractional output samples. Use resampled time functions in Blip_Buffer
// to convert clock counts to resampled time.
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
// Implementation
public:
BLARGG_DISABLE_NOTHROW
private:
#if BLIP_BUFFER_FAST
Blip_Synth_Fast_ impl;
typedef char coeff_t;
#else
Blip_Synth_ impl;
typedef short coeff_t;
// Left halves of first difference of step response for each possible phase
coeff_t phases [quality / 2 * blip_res];
public:
Blip_Synth() : impl( phases, quality ) { }
#endif
};
//// Low-pass equalization parameters
class blip_eq_t {
double treble, kaiser;
int rolloff_freq, sample_rate, cutoff_freq;
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, int rolloff_freq, int sample_rate, int cutoff_freq = 0,
double kaiser = 5.2 );
// Generate center point and right half of impulse response
virtual void generate( float out [], int count ) const;
virtual ~blip_eq_t() { }
enum { oversample = blip_res };
static int calc_count( int quality ) { return (quality - 1) * (oversample / 2) + 1; }
};
#include "Blip_Buffer_impl2.h"
#endif

View File

@ -0,0 +1,135 @@
// 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

@ -0,0 +1,282 @@
// 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,357 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Bml_Parser.h"
const char * strchr_limited( const char * in, const char * end, char c )
{
while ( in < end && *in != c ) ++in;
if ( in < end ) return in;
else return 0;
}
Bml_Node Bml_Node::emptyNode;
Bml_Node::Bml_Node()
{
name = 0;
value = 0;
}
Bml_Node::Bml_Node(char const* name, size_t max_length)
{
size_t length = 0;
char const* ptr = name;
while (*ptr && length < max_length) ++ptr, ++length;
this->name = new char[ length + 1 ];
memcpy( this->name, name, length );
this->name[ length ] = '\0';
value = 0;
}
Bml_Node::Bml_Node(const Bml_Node &in)
{
size_t length;
name = 0;
if (in.name)
{
length = strlen(in.name);
name = new char[length + 1];
memcpy(name, in.name, length + 1);
}
value = 0;
if (in.value)
{
length = strlen(in.value);
value = new char[length + 1];
memcpy(value, in.value, length + 1);
}
children = in.children;
}
Bml_Node::~Bml_Node()
{
delete [] name;
delete [] value;
}
void Bml_Node::clear()
{
delete [] name;
delete [] value;
name = 0;
value = 0;
children.resize( 0 );
}
void Bml_Node::setLine(const char *line, size_t max_length)
{
delete [] name;
delete [] value;
name = 0;
value = 0;
size_t length = 0;
const char * end = line;
while (*end && length < max_length) ++end;
const char * line_end = strchr_limited(line, end, '\n');
if ( !line_end ) line_end = end;
const char * first_letter = line;
while ( first_letter < line_end && *first_letter <= 0x20 ) first_letter++;
const char * colon = strchr_limited(first_letter, line_end, ':');
const char * last_letter = line_end - 1;
if (colon)
{
const char * first_value_letter = colon + 1;
while (first_value_letter < line_end && *first_value_letter <= 0x20) first_value_letter++;
last_letter = line_end - 1;
while (last_letter > first_value_letter && *last_letter <= 0x20) last_letter--;
value = new char[last_letter - first_value_letter + 2];
memcpy(value, first_value_letter, last_letter - first_value_letter + 1);
value[last_letter - first_value_letter + 1] = '\0';
last_letter = colon - 1;
}
while (last_letter > first_letter && *last_letter <= 0x20) last_letter--;
name = new char[last_letter - first_letter + 2];
memcpy(name, first_letter, last_letter - first_letter + 1);
name[last_letter - first_letter + 1] = '\0';
}
Bml_Node& Bml_Node::addChild(const Bml_Node &child)
{
children.push_back(child);
return *(children.end() - 1);
}
const char * Bml_Node::getName() const
{
return name;
}
const char * Bml_Node::getValue() const
{
return value;
}
void Bml_Node::setValue(char const* value)
{
delete [] this->value;
size_t length = strlen( value ) + 1;
this->value = new char[ length ];
memcpy( this->value, value, length );
}
size_t Bml_Node::getChildCount() const
{
return children.size();
}
Bml_Node const& Bml_Node::getChild(size_t index) const
{
return children[index];
}
Bml_Node & Bml_Node::walkToNode(const char *path, bool use_indexes)
{
Bml_Node * next_node;
Bml_Node * node = this;
while ( *path )
{
bool item_found = false;
size_t array_index = 0;
const char * array_index_start = strchr( path, '[' );
const char * next_separator = strchr( path, ':' );
if ( !next_separator ) next_separator = path + strlen(path);
if ( use_indexes && array_index_start && array_index_start < next_separator )
{
char * temp;
array_index = strtoul( array_index_start + 1, &temp, 10 );
}
else
{
array_index_start = next_separator;
}
if ( use_indexes )
{
for ( std::vector<Bml_Node>::iterator it = node->children.begin(); it != node->children.end(); ++it )
{
if ( array_index_start - path == strlen(it->name) &&
strncmp( it->name, path, array_index_start - path ) == 0 )
{
next_node = &(*it);
item_found = true;
if ( array_index == 0 ) break;
--array_index;
}
if (array_index)
item_found = false;
}
}
else
{
for ( std::vector<Bml_Node>::iterator it = node->children.end(); it != node->children.begin(); )
{
--it;
if ( next_separator - path == strlen(it->name) &&
strncmp( it->name, path, next_separator - path ) == 0 )
{
next_node = &(*it);
item_found = true;
break;
}
}
}
if ( !item_found )
{
Bml_Node child( path, next_separator - path );
node = &(node->addChild( child ));
}
else
node = next_node;
if ( *next_separator )
{
path = next_separator + 1;
}
else break;
}
return *node;
}
Bml_Node const& Bml_Node::walkToNode(const char *path) const
{
Bml_Node const* next_node;
Bml_Node const* node = this;
while ( *path )
{
bool item_found = false;
size_t array_index = ~0;
const char * array_index_start = strchr( path, '[' );
const char * next_separator = strchr( path, ':' );
if ( !next_separator ) next_separator = path + strlen(path);
if ( array_index_start && array_index_start < next_separator )
{
char * temp;
array_index = strtoul( array_index_start + 1, &temp, 10 );
}
else
{
array_index_start = next_separator;
}
for ( std::vector<Bml_Node>::const_iterator it = node->children.begin(), ite = node->children.end(); it != ite; ++it )
{
if ( array_index_start - path == strlen(it->name) &&
strncmp( it->name, path, array_index_start - path ) == 0 )
{
next_node = &(*it);
item_found = true;
if ( array_index == 0 ) break;
--array_index;
}
}
if ( !item_found ) return emptyNode;
node = next_node;
if ( *next_separator )
{
path = next_separator + 1;
}
else break;
}
return *node;
}
void Bml_Parser::parseDocument( const char * source, size_t max_length )
{
std::vector<size_t> indents;
std::string last_name;
std::string current_path;
document.clear();
size_t last_indent = ~0;
Bml_Node node;
size_t length = 0;
const char * end = source;
while ( *end && length < max_length ) ++end, ++length;
while ( source < end )
{
const char * line_end = strchr_limited( source, end, '\n' );
if ( !line_end ) line_end = end;
if ( node.getName() ) last_name = node.getName();
node.setLine( source, line_end - source );
size_t indent = 0;
while ( source < line_end && *source <= 0x20 )
{
source++;
indent++;
}
if ( last_indent == ~0 ) last_indent = indent;
if ( indent > last_indent )
{
indents.push_back( last_indent );
last_indent = indent;
if ( current_path.length() ) current_path += ":";
current_path += last_name;
}
else if ( indent < last_indent )
{
while ( last_indent > indent && indents.size() )
{
last_indent = *(indents.end() - 1);
indents.pop_back();
size_t colon = current_path.find_last_of( ':' );
if ( colon != std::string::npos ) current_path.resize( colon );
else current_path.resize( 0 );
}
last_indent = indent;
}
document.walkToNode( current_path.c_str() ).addChild( node );
source = line_end;
while ( *source && *source == '\n' ) source++;
}
}
const char * Bml_Parser::enumValue(std::string const& path) const
{
return document.walkToNode(path.c_str()).getValue();
}
void Bml_Parser::setValue(std::string const& path, const char *value)
{
document.walkToNode(path.c_str(), true).setValue(value);
}
void Bml_Parser::setValue(std::string const& path, long value)
{
std::ostringstream str;
str << value;
setValue( path, str.str().c_str() );
}
void Bml_Parser::serialize(std::string & out) const
{
std::ostringstream strOut;
serialize(strOut, &document, 0);
out = strOut.str();
}
void Bml_Parser::serialize(std::ostringstream & out, Bml_Node const* node, unsigned int indent) const
{
for (unsigned i = 1; i < indent; ++i) out << " ";
if ( indent )
{
out << node->getName();
if (node->getValue() && strlen(node->getValue())) out << ":" << node->getValue();
out << std::endl;
}
for (unsigned i = 0, j = node->getChildCount(); i < j; ++i)
{
Bml_Node const& child = node->getChild(i);
if ( (!child.getValue() || !strlen(child.getValue())) && !child.getChildCount() )
continue;
serialize( out, &child, indent + 1 );
if ( indent == 0 ) out << std::endl;
}
}

View File

@ -0,0 +1,61 @@
#ifndef BML_PARSER_H
#define BML_PARSER_H
#include <vector>
#include <string>
#include <sstream>
class Bml_Node
{
char * name;
char * value;
std::vector<Bml_Node> children;
static Bml_Node emptyNode;
public:
Bml_Node();
Bml_Node(char const* name, size_t max_length = ~0UL);
Bml_Node(Bml_Node const& in);
~Bml_Node();
void clear();
void setLine(const char * line, size_t max_length = ~0UL);
Bml_Node& addChild(Bml_Node const& child);
const char * getName() const;
const char * getValue() const;
void setValue(char const* value);
size_t getChildCount() const;
Bml_Node const& getChild(size_t index) const;
Bml_Node & walkToNode( const char * path, bool use_indexes = false );
Bml_Node const& walkToNode( const char * path ) const;
};
class Bml_Parser
{
Bml_Node document;
public:
Bml_Parser() { }
void parseDocument(const char * document, size_t max_length = ~0UL);
const char * enumValue(std::string const& path) const;
void setValue(std::string const& path, long value);
void setValue(std::string const& path, const char * value);
void serialize(std::string & out) const;
private:
void serialize(std::ostringstream & out, Bml_Node const* node, unsigned int indent) const;
};
#endif // BML_PARSER_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,525 @@
// File_Extractor 1.0.0. http://www.slack.net/~ant/
#include "Data_Reader.h"
#include "blargg_endian.h"
#include <stdio.h>
#include <errno.h>
/* Copyright (C) 2005-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"
extern char* blargg_to_utf8( const blargg_wchar_t* wpath );
extern size_t utf16_encode_char( unsigned cur_wchar, blargg_wchar_t * out );
extern size_t utf16_decode_char( const blargg_wchar_t * p_source, unsigned * p_out, size_t p_source_length );
extern size_t utf8_decode_char( const char *p_utf8, unsigned & wide, size_t mmax );
extern size_t utf8_encode_char( unsigned wide, char * target );
extern blargg_wchar_t* blargg_to_wide( const char* path );
extern size_t utf8_char_len_from_header( char p_c );
// Data_Reader
blargg_err_t Data_Reader::read( void* p, long n )
{
assert( n >= 0 );
if ( n < 0 )
return blargg_err_caller;
if ( n <= 0 )
return blargg_ok;
if ( n > remain() )
return blargg_err_file_eof;
blargg_err_t err = read_v( p, n );
if ( !err )
remain_ -= n;
return err;
}
blargg_err_t Data_Reader::read_avail( void* p, int* n_ )
{
assert( *n_ >= 0 );
long n = (long) min( (BOOST::uint64_t)(*n_), remain() );
*n_ = 0;
if ( n < 0 )
return blargg_err_caller;
if ( n <= 0 )
return blargg_ok;
blargg_err_t err = read_v( p, n );
if ( !err )
{
remain_ -= n;
*n_ = (int) n;
}
return err;
}
blargg_err_t Data_Reader::read_avail( void* p, long* n )
{
int i = STATIC_CAST(int, *n);
blargg_err_t err = read_avail( p, &i );
*n = i;
return err;
}
blargg_err_t Data_Reader::skip_v( BOOST::uint64_t count )
{
char buf [512];
while ( count )
{
BOOST::uint64_t n = min( count, (BOOST::uint64_t) sizeof buf );
count -= n;
RETURN_ERR( read_v( buf, (long)n ) );
}
return blargg_ok;
}
blargg_err_t Data_Reader::skip( long n )
{
assert( n >= 0 );
if ( n < 0 )
return blargg_err_caller;
if ( n <= 0 )
return blargg_ok;
if ( n > remain() )
return blargg_err_file_eof;
blargg_err_t err = skip_v( n );
if ( !err )
remain_ -= n;
return err;
}
// File_Reader
blargg_err_t File_Reader::seek( BOOST::uint64_t n )
{
assert( n >= 0 );
if ( n == tell() )
return blargg_ok;
if ( n > size() )
return blargg_err_file_eof;
blargg_err_t err = seek_v( n );
if ( !err )
set_tell( n );
return err;
}
blargg_err_t File_Reader::skip_v( BOOST::uint64_t n )
{
return seek_v( tell() + n );
}
// Subset_Reader
Subset_Reader::Subset_Reader( Data_Reader* dr, BOOST::uint64_t size ) :
in( dr )
{
set_remain( min( size, dr->remain() ) );
}
blargg_err_t Subset_Reader::read_v( void* p, long s )
{
return in->read( p, s );
}
// Remaining_Reader
Remaining_Reader::Remaining_Reader( void const* h, int size, Data_Reader* r ) :
in( r )
{
header = h;
header_remain = size;
set_remain( size + r->remain() );
}
blargg_err_t Remaining_Reader::read_v( void* out, long count )
{
long first = min( count, header_remain );
if ( first )
{
memcpy( out, header, first );
header = STATIC_CAST(char const*, header) + first;
header_remain -= first;
}
return in->read( STATIC_CAST(char*, out) + first, count - first );
}
// Mem_File_Reader
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
begin( STATIC_CAST(const char*, p) )
{
set_size( s );
}
blargg_err_t Mem_File_Reader::read_v( void* p, long s )
{
memcpy( p, begin + tell(), s );
return blargg_ok;
}
blargg_err_t Mem_File_Reader::seek_v( BOOST::uint64_t )
{
return blargg_ok;
}
// Callback_Reader
Callback_Reader::Callback_Reader( callback_t c, BOOST::uint64_t s, void* d ) :
callback( c ),
user_data( d )
{
set_remain( s );
}
blargg_err_t Callback_Reader::read_v( void* out, long count )
{
return callback( user_data, out, count );
}
// Callback_File_Reader
Callback_File_Reader::Callback_File_Reader( callback_t c, BOOST::uint64_t s, void* d ) :
callback( c ),
user_data( d )
{
set_size( s );
}
blargg_err_t Callback_File_Reader::read_v( void* out, long count )
{
return callback( user_data, out, count, tell() );
}
blargg_err_t Callback_File_Reader::seek_v( BOOST::uint64_t )
{
return blargg_ok;
}
static const BOOST::uint8_t mask_tab[6]={0x80,0xE0,0xF0,0xF8,0xFC,0xFE};
static const BOOST::uint8_t val_tab[6]={0,0xC0,0xE0,0xF0,0xF8,0xFC};
#ifdef _WIN32
static FILE* blargg_fopen( const char path [], const char mode [] )
{
FILE* file = NULL;
blargg_wchar_t* wmode = NULL;
blargg_wchar_t* wpath = NULL;
wpath = blargg_to_wide( path );
if ( wpath )
{
wmode = blargg_to_wide( mode );
if (wmode)
#if _MSC_VER >= 1300
errno = _wfopen_s(&file, wpath, wmode);
#else
file = _wfopen( wpath, wmode );
#endif
}
// Save and restore errno in case free() clears it
int saved_errno = errno;
free( wmode );
free( wpath );
errno = saved_errno;
return file;
}
#else
static inline FILE* blargg_fopen( const char path [], const char mode [] )
{
return fopen( path, mode );
}
#endif
// Std_File_Reader
Std_File_Reader::Std_File_Reader()
{
file_ = NULL;
}
Std_File_Reader::~Std_File_Reader()
{
close();
}
static blargg_err_t blargg_fopen( FILE** out, const char path [] )
{
errno = 0;
*out = blargg_fopen( path, "rb" );
if ( !*out )
{
#ifdef ENOENT
if ( errno == ENOENT )
return blargg_err_file_missing;
#endif
#ifdef ENOMEM
if ( errno == ENOMEM )
return blargg_err_memory;
#endif
return blargg_err_file_read;
}
return blargg_ok;
}
static blargg_err_t blargg_fsize( FILE* f, long* out )
{
if ( fseek( f, 0, SEEK_END ) )
return blargg_err_file_io;
*out = ftell( f );
if ( *out < 0 )
return blargg_err_file_io;
if ( fseek( f, 0, SEEK_SET ) )
return blargg_err_file_io;
return blargg_ok;
}
blargg_err_t Std_File_Reader::open( const char path [] )
{
close();
FILE* f;
RETURN_ERR( blargg_fopen( &f, path ) );
long s;
blargg_err_t err = blargg_fsize( f, &s );
if ( err )
{
fclose( f );
return err;
}
file_ = f;
set_size( s );
return blargg_ok;
}
void Std_File_Reader::make_unbuffered()
{
#ifdef _WIN32
BOOST::uint64_t offset = _ftelli64( STATIC_CAST(FILE*, file_) );
#else
BOOST::uint64_t offset = ftello( STATIC_CAST(FILE*, file_) );
#endif
if ( setvbuf( STATIC_CAST(FILE*, file_), NULL, _IONBF, 0 ) )
check( false ); // shouldn't fail, but OK if it does
#ifdef _WIN32
_fseeki64( STATIC_CAST(FILE*, file_), offset, SEEK_SET );
#else
fseeko( STATIC_CAST(FILE*, file_), offset, SEEK_SET );
#endif
}
blargg_err_t Std_File_Reader::read_v( void* p, long s )
{
if ( (size_t) s != fread( p, 1, s, STATIC_CAST(FILE*, file_) ) )
{
// Data_Reader's wrapper should prevent EOF
check( !feof( STATIC_CAST(FILE*, file_) ) );
return blargg_err_file_io;
}
return blargg_ok;
}
blargg_err_t Std_File_Reader::seek_v( BOOST::uint64_t n )
{
#ifdef _WIN32
if ( _fseeki64( STATIC_CAST(FILE*, file_), n, SEEK_SET ) )
#else
if ( fseeko( STATIC_CAST(FILE*, file_), n, SEEK_SET ) )
#endif
{
// Data_Reader's wrapper should prevent EOF
check( !feof( STATIC_CAST(FILE*, file_) ) );
return blargg_err_file_io;
}
return blargg_ok;
}
void Std_File_Reader::close()
{
if ( file_ )
{
fclose( STATIC_CAST(FILE*, file_) );
file_ = NULL;
}
}
// Gzip_File_Reader
#ifdef HAVE_ZLIB_H
#include "zlib.h"
static const char* get_gzip_eof( const char path [], long* eof )
{
FILE* file;
RETURN_ERR( blargg_fopen( &file, path ) );
int const h_size = 4;
unsigned char h [h_size];
// read four bytes to ensure that we can seek to -4 later
if ( fread( h, 1, h_size, file ) != (size_t) h_size || h[0] != 0x1F || h[1] != 0x8B )
{
// Not gzipped
if ( ferror( file ) )
return blargg_err_file_io;
if ( fseek( file, 0, SEEK_END ) )
return blargg_err_file_io;
*eof = ftell( file );
if ( *eof < 0 )
return blargg_err_file_io;
}
else
{
// Gzipped; get uncompressed size from end
if ( fseek( file, -h_size, SEEK_END ) )
return blargg_err_file_io;
if ( fread( h, 1, h_size, file ) != (size_t) h_size )
return blargg_err_file_io;
*eof = get_le32( h );
}
if ( fclose( file ) )
check( false );
return blargg_ok;
}
Gzip_File_Reader::Gzip_File_Reader()
{
file_ = NULL;
}
Gzip_File_Reader::~Gzip_File_Reader()
{
close();
}
blargg_err_t Gzip_File_Reader::open( const char path [] )
{
close();
long s;
RETURN_ERR( get_gzip_eof( path, &s ) );
file_ = gzopen( path, "rb" );
if ( !file_ )
return blargg_err_file_read;
set_size( s );
return blargg_ok;
}
static blargg_err_t convert_gz_error( gzFile file )
{
int err;
gzerror( file, &err );
switch ( err )
{
case Z_STREAM_ERROR: break;
case Z_DATA_ERROR: return blargg_err_file_corrupt;
case Z_MEM_ERROR: return blargg_err_memory;
case Z_BUF_ERROR: break;
}
return blargg_err_internal;
}
blargg_err_t Gzip_File_Reader::read_v( void* p, long s )
{
while ( s > 0 )
{
int s_i = (int)( s > INT_MAX ? INT_MAX : s );
int result = gzread( (gzFile) file_, p, s_i );
if ( result != s_i )
{
if ( result < 0 )
return convert_gz_error( (gzFile) file_ );
return blargg_err_file_corrupt;
}
p = (char*)p + result;
s -= result;
}
return blargg_ok;
}
blargg_err_t Gzip_File_Reader::seek_v( BOOST::uint64_t n )
{
if ( gzseek( (gzFile) file_, (long)n, SEEK_SET ) < 0 )
return convert_gz_error( (gzFile) file_ );
return blargg_ok;
}
void Gzip_File_Reader::close()
{
if ( file_ )
{
if ( gzclose( (gzFile) file_ ) )
check( false );
file_ = NULL;
}
}
#endif

View File

@ -0,0 +1,274 @@
// Lightweight interface for reading data from byte stream
// File_Extractor 1.0.0
#ifndef DATA_READER_H
#define DATA_READER_H
#include "blargg_common.h"
/* Some functions accept a long instead of int for convenience where caller has
a long due to some other interface, and would otherwise have to get a warning,
or cast it (and verify that it wasn't outside the range of an int).
To really support huge (>2GB) files, long isn't a solution, since there's no
guarantee it's more than 32 bits. We'd need to use long long (if available), or
something compiler-specific, and change all places file sizes or offsets are
used. */
// Supports reading and finding out how many bytes are remaining
class Data_Reader {
public:
// Reads min(*n,remain()) bytes and sets *n to this number, thus trying to read more
// tham remain() bytes doesn't result in error, just *n being set to remain().
blargg_err_t read_avail( void* p, int* n );
blargg_err_t read_avail( void* p, long* n );
// Reads exactly n bytes, or returns error if they couldn't ALL be read.
// Reading past end of file results in blargg_err_file_eof.
blargg_err_t read( void* p, long n );
// Number of bytes remaining until end of file
BOOST::uint64_t remain() const { return remain_; }
// Reads and discards n bytes. Skipping past end of file results in blargg_err_file_eof.
blargg_err_t skip( long n );
virtual ~Data_Reader() { }
private:
// noncopyable
Data_Reader( const Data_Reader& );
Data_Reader& operator = ( const Data_Reader& );
// Derived interface
protected:
Data_Reader() : remain_( 0 ) { }
// Sets remain
void set_remain( BOOST::uint64_t n ) { assert( n >= 0 ); remain_ = n; }
// Do same as read(). Guaranteed that 0 < n <= remain(). Value of remain() is updated
// AFTER this call succeeds, not before. set_remain() should NOT be called from this.
virtual blargg_err_t read_v( void*, long n ) BLARGG_PURE( { (void)n; return blargg_ok; } )
// Do same as skip(). Guaranteed that 0 < n <= remain(). Default just reads data
// and discards it. Value of remain() is updated AFTER this call succeeds, not
// before. set_remain() should NOT be called from this.
virtual blargg_err_t skip_v( BOOST::uint64_t n );
// Implementation
public:
BLARGG_DISABLE_NOTHROW
private:
BOOST::uint64_t remain_;
};
// Supports seeking in addition to Data_Reader operations
class File_Reader : public Data_Reader {
public:
// Size of file
BOOST::uint64_t size() const { return size_; }
// Current position in file
BOOST::uint64_t tell() const { return size_ - remain(); }
// Goes to new position
blargg_err_t seek( BOOST::uint64_t );
// Derived interface
protected:
// Sets size and resets position
void set_size( BOOST::uint64_t n ) { size_ = n; Data_Reader::set_remain( n ); }
void set_size( int n ) { set_size( STATIC_CAST(BOOST::uint64_t, n) ); }
void set_size( long n ) { set_size( STATIC_CAST(BOOST::uint64_t, n) ); }
// Sets reported position
void set_tell( BOOST::uint64_t i ) { assert( 0 <= i && i <= size_ ); Data_Reader::set_remain( size_ - i ); }
// Do same as seek(). Guaranteed that 0 <= n <= size(). Value of tell() is updated
// AFTER this call succeeds, not before. set_* functions should NOT be called from this.
virtual blargg_err_t seek_v( BOOST::uint64_t n ) BLARGG_PURE( { (void)n; return blargg_ok; } )
// Implementation
protected:
File_Reader() : size_( 0 ) { }
virtual blargg_err_t skip_v( BOOST::uint64_t );
private:
BOOST::uint64_t size_;
void set_remain(); // avoid accidental use of set_remain
};
// Reads from file on disk
class Std_File_Reader : public File_Reader {
public:
// Opens file
blargg_err_t open( const char path [] );
// Closes file if one was open
void close();
// Switches to unbuffered mode. Useful if buffering is already being
// done at a higher level.
void make_unbuffered();
// Implementation
public:
Std_File_Reader();
virtual ~Std_File_Reader();
protected:
virtual blargg_err_t read_v( void*, long );
virtual blargg_err_t seek_v( BOOST::uint64_t );
private:
void* file_;
};
// Treats range of memory as a file
class Mem_File_Reader : public File_Reader {
public:
Mem_File_Reader( const void* begin, long size );
// Implementation
protected:
virtual blargg_err_t read_v( void*, long );
virtual blargg_err_t seek_v( BOOST::uint64_t );
private:
const char* const begin;
};
// Allows only count bytes to be read from reader passed
class Subset_Reader : public Data_Reader {
public:
Subset_Reader( Data_Reader*, BOOST::uint64_t count );
// Implementation
protected:
virtual blargg_err_t read_v( void*, long );
private:
Data_Reader* const in;
};
// Joins already-read header and remaining data into original file.
// Meant for cases where you've already read header and don't want
// to seek and re-read data (for efficiency).
class Remaining_Reader : public Data_Reader {
public:
Remaining_Reader( void const* header, int header_size, Data_Reader* );
// Implementation
protected:
virtual blargg_err_t read_v( void*, long );
private:
Data_Reader* const in;
void const* header;
long header_remain;
};
// Invokes callback function to read data
extern "C" { // necessary to be usable from C
typedef const char* (*callback_reader_func_t)(
void* user_data, // Same value passed to constructor
void* out, // Buffer to place data into
long count // Number of bytes to read
);
}
class Callback_Reader : public Data_Reader {
public:
typedef callback_reader_func_t callback_t;
Callback_Reader( callback_t, BOOST::uint64_t size, void* user_data );
// Implementation
protected:
virtual blargg_err_t read_v( void*, long );
private:
callback_t const callback;
void* const user_data;
};
// Invokes callback function to read data
extern "C" { // necessary to be usable from C
typedef const char* (*callback_file_reader_func_t)(
void* user_data, // Same value passed to constructor
void* out, // Buffer to place data into
long count, // Number of bytes to read
BOOST::uint64_t pos // Position in file to read from
);
}
class Callback_File_Reader : public File_Reader {
public:
typedef callback_file_reader_func_t callback_t;
Callback_File_Reader( callback_t, BOOST::uint64_t size, void* user_data );
// Implementation
protected:
virtual blargg_err_t read_v( void*, long );
virtual blargg_err_t seek_v( BOOST::uint64_t );
private:
callback_t const callback;
void* const user_data;
};
#ifdef HAVE_ZLIB_H
// Reads file compressed with gzip (or uncompressed)
class Gzip_File_Reader : public File_Reader {
public:
// Opens possibly gzipped file
blargg_err_t open( const char path [] );
// Closes file if one was open
void close();
// Implementation
public:
Gzip_File_Reader();
~Gzip_File_Reader();
protected:
virtual blargg_err_t read_v( void*, long );
virtual blargg_err_t seek_v( BOOST::uint64_t );
private:
// void* so "zlib.h" doesn't have to be included here
void* file_;
};
#endif
#ifdef _WIN32
typedef wchar_t blargg_wchar_t;
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
typedef uint16_t blargg_wchar_t;
#else
typedef unsigned short blargg_wchar_t;
#endif
char* blargg_to_utf8( const blargg_wchar_t* );
blargg_wchar_t* blargg_to_wide( const char* );
#endif

View File

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

View File

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

View File

@ -0,0 +1,315 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Dual_Resampler.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"
// TODO: fix this. hack since resampler holds back some output.
int const resampler_extra = 34;
int const stereo = 2;
Dual_Resampler::Dual_Resampler() { }
Dual_Resampler::~Dual_Resampler() { }
blargg_err_t Dual_Resampler::reset( int pairs )
{
// expand allocations a bit
RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
resize( pairs );
resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2);
RETURN_ERR( resampler.resize_buffer( resampler_size ) );
resampler.clear();
return blargg_ok;
}
void Dual_Resampler::resize( int pairs )
{
int new_sample_buf_size = pairs * 2;
//new_sample_buf_size = new_sample_buf_size / 4 * 4; // TODO: needed only for 3:2 downsampler
if ( sample_buf_size != new_sample_buf_size )
{
if ( (unsigned) new_sample_buf_size > sample_buf.size() )
{
check( false );
return;
}
sample_buf_size = new_sample_buf_size;
oversamples_per_frame = int (pairs * resampler.rate()) * 2 + 2;
clear();
}
}
void Dual_Resampler::clear()
{
buf_pos = buffered = 0;
resampler.clear();
}
int Dual_Resampler::play_frame_( Stereo_Buffer& stereo_buf, dsample_t out [], Stereo_Buffer** secondary_buf_set, int secondary_buf_set_count )
{
int pair_count = sample_buf_size >> 1;
blip_time_t blip_time = stereo_buf.center()->count_clocks( pair_count );
int sample_count = oversamples_per_frame - resampler.written() + resampler_extra;
int new_count = set_callback.f( set_callback.data, blip_time, sample_count, resampler.buffer() );
assert( new_count < resampler_size );
stereo_buf.end_frame( blip_time );
assert( stereo_buf.samples_avail() == pair_count * 2 );
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];
blip_time_t blip_time_2 = second_buf->center()->count_clocks( pair_count );
second_buf->end_frame( blip_time_2 );
assert( second_buf->samples_avail() == pair_count * 2 );
}
}
resampler.write( new_count );
int count = resampler.read( sample_buf.begin(), sample_buf_size );
mix_samples( stereo_buf, out, count, secondary_buf_set, secondary_buf_set_count );
pair_count = count >> 1;
stereo_buf.left()->remove_samples( pair_count );
stereo_buf.right()->remove_samples( pair_count );
stereo_buf.center()->remove_samples( pair_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];
second_buf->left()->remove_samples( pair_count );
second_buf->right()->remove_samples( pair_count );
second_buf->center()->remove_samples( pair_count );
}
}
return count;
}
void Dual_Resampler::dual_play( int count, dsample_t out [], Stereo_Buffer& stereo_buf, Stereo_Buffer** secondary_buf_set, int secondary_buf_set_count )
{
// empty extra buffer
int remain = buffered - buf_pos;
if ( remain )
{
if ( remain > count )
remain = count;
count -= remain;
memcpy( out, &sample_buf [buf_pos], remain * sizeof *out );
out += remain;
buf_pos += remain;
}
// entire frames
while ( count >= sample_buf_size )
{
buf_pos = buffered = play_frame_( stereo_buf, out, secondary_buf_set, secondary_buf_set_count );
out += buffered;
count -= buffered;
}
while (count > 0)
{
buffered = play_frame_( stereo_buf, sample_buf.begin(), secondary_buf_set, secondary_buf_set_count );
if ( buffered >= count )
{
buf_pos = count;
memcpy( out, sample_buf.begin(), count * sizeof *out );
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

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

View File

@ -0,0 +1,640 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Effects_Buffer.h"
/* Copyright (C) 2006-2007 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"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
int const fixed_shift = 12;
#define TO_FIXED( f ) fixed_t ((f) * ((fixed_t) 1 << fixed_shift))
#define FROM_FIXED( f ) ((f) >> fixed_shift)
int const max_read = 2560; // determines minimum delay
Effects_Buffer::Effects_Buffer( int max_bufs, int echo_size_ ) : Multi_Buffer( stereo )
{
echo_size = max( max_read * (int) stereo, echo_size_ & ~1 );
clock_rate_ = 0;
bass_freq_ = 90;
bufs = NULL;
bufs_size = 0;
bufs_max = max( max_bufs, (int) extra_chans );
no_echo = true;
no_effects = true;
// defaults
config_.enabled = false;
config_.delay [0] = 120;
config_.delay [1] = 122;
config_.feedback = 0.2f;
config_.treble = 0.4f;
static float const sep = 0.8f;
config_.side_chans [0].pan = -sep;
config_.side_chans [1].pan = +sep;
config_.side_chans [0].vol = 1.0f;
config_.side_chans [1].vol = 1.0f;
memset( &s, 0, sizeof s );
clear();
}
Effects_Buffer::~Effects_Buffer()
{
delete_bufs();
}
// avoid using new []
blargg_err_t Effects_Buffer::new_bufs( int size )
{
bufs = (buf_t*) malloc( size * sizeof *bufs );
CHECK_ALLOC( bufs );
for ( int i = 0; i < size; i++ )
new (bufs + i) buf_t;
bufs_size = size;
return blargg_ok;
}
void Effects_Buffer::delete_bufs()
{
if ( bufs )
{
for ( int i = bufs_size; --i >= 0; )
bufs [i].~buf_t();
free( bufs );
bufs = NULL;
}
bufs_size = 0;
}
blargg_err_t Effects_Buffer::set_sample_rate( int rate, int msec )
{
// extra to allow farther past-the-end pointers
mixer.samples_read = 0;
RETURN_ERR( echo.resize( echo_size + stereo ) );
return Multi_Buffer::set_sample_rate( rate, msec );
}
void Effects_Buffer::clock_rate( int rate )
{
clock_rate_ = rate;
for ( int i = bufs_size; --i >= 0; )
bufs [i].clock_rate( clock_rate_ );
}
void Effects_Buffer::bass_freq( int freq )
{
bass_freq_ = freq;
for ( int i = bufs_size; --i >= 0; )
bufs [i].bass_freq( bass_freq_ );
}
blargg_err_t Effects_Buffer::set_channel_count( int count, int const types [] )
{
RETURN_ERR( Multi_Buffer::set_channel_count( count, types ) );
delete_bufs();
mixer.samples_read = 0;
RETURN_ERR( chans.resize( count + extra_chans ) );
RETURN_ERR( new_bufs( min( bufs_max, count + extra_chans ) ) );
for ( int i = bufs_size; --i >= 0; )
RETURN_ERR( bufs [i].set_sample_rate( sample_rate(), length() ) );
for ( int i = chans.size(); --i >= 0; )
{
chan_t& ch = chans [i];
ch.cfg.vol = 1.0f;
ch.cfg.pan = 0.0f;
ch.cfg.surround = false;
ch.cfg.echo = false;
}
// side channels with echo
chans [2].cfg.echo = true;
chans [3].cfg.echo = true;
clock_rate( clock_rate_ );
bass_freq( bass_freq_ );
apply_config();
clear();
return blargg_ok;
}
void Effects_Buffer::clear_echo()
{
if ( echo.size() )
memset( echo.begin(), 0, echo.size() * sizeof echo [0] );
}
void Effects_Buffer::clear()
{
echo_pos = 0;
s.low_pass [0] = 0;
s.low_pass [1] = 0;
mixer.samples_read = 0;
for ( int i = bufs_size; --i >= 0; )
bufs [i].clear();
clear_echo();
}
Effects_Buffer::channel_t Effects_Buffer::channel( int i )
{
i += extra_chans;
require( extra_chans <= i && i < (int) chans.size() );
return chans [i].channel;
}
// Configuration
// 3 wave positions with/without surround, 2 multi (one with same config as wave)
int const simple_bufs = 3 * 2 + 2 - 1;
Simple_Effects_Buffer::Simple_Effects_Buffer() :
Effects_Buffer( extra_chans + simple_bufs, 18 * 1024 )
{
config_.echo = 0.20f;
config_.stereo = 0.20f;
config_.surround = true;
config_.enabled = false;
}
void Simple_Effects_Buffer::apply_config()
{
Effects_Buffer::config_t& c = Effects_Buffer::config();
c.enabled = config_.enabled;
if ( c.enabled )
{
c.delay [0] = 120;
c.delay [1] = 122;
c.feedback = config_.echo * 0.7f;
c.treble = 0.6f - 0.3f * config_.echo;
float sep = config_.stereo + 0.80f;
if ( sep > 1.0f )
sep = 1.0f;
c.side_chans [0].pan = -sep;
c.side_chans [1].pan = +sep;
for ( int i = channel_count(); --i >= 0; )
{
chan_config_t& ch = Effects_Buffer::chan_config( i );
ch.pan = 0.0f;
ch.surround = config_.surround;
ch.echo = false;
int const type = (channel_types() ? channel_types() [i] : 0);
if ( !(type & noise_type) )
{
int index = (type & type_index_mask) % 6 - 3;
if ( index < 0 )
{
index += 3;
ch.surround = false;
ch.echo = true;
}
if ( index >= 1 )
{
ch.pan = config_.stereo;
if ( index == 1 )
ch.pan = -ch.pan;
}
}
else if ( type & 1 )
{
ch.surround = false;
}
}
}
Effects_Buffer::apply_config();
}
int Effects_Buffer::min_delay() const
{
require( sample_rate() );
return max_read * 1000 / sample_rate();
}
int Effects_Buffer::max_delay() const
{
require( sample_rate() );
return (echo_size / stereo - max_read) * 1000 / sample_rate();
}
void Effects_Buffer::apply_config()
{
int i;
if ( !bufs_size )
return;
s.treble = TO_FIXED( config_.treble );
bool echo_dirty = false;
fixed_t old_feedback = s.feedback;
s.feedback = TO_FIXED( config_.feedback );
if ( !old_feedback && s.feedback )
echo_dirty = true;
// delays
for ( i = stereo; --i >= 0; )
{
int delay = config_.delay [i] * sample_rate() / 1000 * stereo;
delay = max( delay, (int) (max_read * stereo) );
delay = min( delay, (int) (echo_size - max_read * stereo) );
if ( s.delay [i] != delay )
{
s.delay [i] = delay;
echo_dirty = true;
}
}
// side channels
for ( i = 2; --i >= 0; )
{
chans [i+2].cfg.vol = chans [i].cfg.vol = config_.side_chans [i].vol * 0.5f;
chans [i+2].cfg.pan = chans [i].cfg.pan = config_.side_chans [i].pan;
}
// convert volumes
for ( i = chans.size(); --i >= 0; )
{
chan_t& ch = chans [i];
ch.vol [0] = TO_FIXED( ch.cfg.vol - ch.cfg.vol * ch.cfg.pan );
ch.vol [1] = TO_FIXED( ch.cfg.vol + ch.cfg.vol * ch.cfg.pan );
if ( ch.cfg.surround )
ch.vol [0] = -ch.vol [0];
}
assign_buffers();
// set side channels
for ( i = chans.size(); --i >= 0; )
{
chan_t& ch = chans [i];
ch.channel.left = chans [ch.cfg.echo*2 ].channel.center;
ch.channel.right = chans [ch.cfg.echo*2+1].channel.center;
}
bool old_echo = !no_echo && !no_effects;
// determine whether effects and echo are needed at all
no_effects = true;
no_echo = true;
for ( i = chans.size(); --i >= extra_chans; )
{
chan_t& ch = chans [i];
if ( ch.cfg.echo && s.feedback )
no_echo = false;
if ( ch.vol [0] != TO_FIXED( 1 ) || ch.vol [1] != TO_FIXED( 1 ) )
no_effects = false;
}
if ( !no_echo )
no_effects = false;
if ( chans [0].vol [0] != TO_FIXED( 1 ) ||
chans [0].vol [1] != TO_FIXED( 0 ) ||
chans [1].vol [0] != TO_FIXED( 0 ) ||
chans [1].vol [1] != TO_FIXED( 1 ) )
no_effects = false;
if ( !config_.enabled )
no_effects = true;
if ( no_effects )
{
for ( i = chans.size(); --i >= 0; )
{
chan_t& ch = chans [i];
ch.channel.center = &bufs [2];
ch.channel.left = &bufs [0];
ch.channel.right = &bufs [1];
}
}
mixer.bufs [0] = &bufs [0];
mixer.bufs [1] = &bufs [1];
mixer.bufs [2] = &bufs [2];
if ( echo_dirty || (!old_echo && (!no_echo && !no_effects)) )
clear_echo();
channels_changed();
}
void Effects_Buffer::assign_buffers()
{
// assign channels to buffers
int buf_count = 0;
for ( int i = 0; i < (int) chans.size(); i++ )
{
// put second two side channels at end to give priority to main channels
// in case closest matching is necessary
int x = i;
if ( i > 1 )
x += 2;
if ( x >= (int) chans.size() )
x -= (chans.size() - 2);
chan_t& ch = chans [x];
int b = 0;
for ( ; b < buf_count; b++ )
{
if ( ch.vol [0] == bufs [b].vol [0] &&
ch.vol [1] == bufs [b].vol [1] &&
(ch.cfg.echo == bufs [b].echo || !s.feedback) )
break;
}
if ( b >= buf_count )
{
if ( buf_count < bufs_max )
{
bufs [b].vol [0] = ch.vol [0];
bufs [b].vol [1] = ch.vol [1];
bufs [b].echo = ch.cfg.echo;
buf_count++;
}
else
{
// TODO: this is a mess, needs refinement
dprintf( "Effects_Buffer ran out of buffers; using closest match\n" );
b = 0;
fixed_t best_dist = TO_FIXED( 8 );
for ( int h = buf_count; --h >= 0; )
{
#define CALC_LEVELS( vols, sum, diff, surround ) \
fixed_t sum, diff;\
bool surround = false;\
{\
fixed_t vol_0 = vols [0];\
if ( vol_0 < 0 ) vol_0 = -vol_0, surround = true;\
fixed_t vol_1 = vols [1];\
if ( vol_1 < 0 ) vol_1 = -vol_1, surround = true;\
sum = vol_0 + vol_1;\
diff = vol_0 - vol_1;\
}
CALC_LEVELS( ch.vol, ch_sum, ch_diff, ch_surround );
CALC_LEVELS( bufs [h].vol, buf_sum, buf_diff, buf_surround );
fixed_t dist = abs( ch_sum - buf_sum ) + abs( ch_diff - buf_diff );
if ( ch_surround != buf_surround )
dist += TO_FIXED( 1 ) / 2;
if ( s.feedback && ch.cfg.echo != bufs [h].echo )
dist += TO_FIXED( 1 ) / 2;
if ( best_dist > dist )
{
best_dist = dist;
b = h;
}
}
}
}
//dprintf( "ch %d->buf %d\n", x, b );
ch.channel.center = &bufs [b];
}
}
// Mixing
void Effects_Buffer::end_frame( blip_time_t time )
{
for ( int i = bufs_size; --i >= 0; )
bufs [i].end_frame( time );
}
int Effects_Buffer::read_samples( blip_sample_t out [], int out_size )
{
out_size = min( out_size, samples_avail() );
int pair_count = int (out_size >> 1);
require( pair_count * stereo == out_size ); // must read an even number of samples
if ( pair_count )
{
if ( no_effects )
{
mixer.read_pairs( out, pair_count );
}
else
{
int pairs_remain = pair_count;
do
{
// mix at most max_read pairs at a time
int count = max_read;
if ( count > pairs_remain )
count = pairs_remain;
if ( no_echo )
{
// optimization: clear echo here to keep mix_effects() a leaf function
echo_pos = 0;
memset( echo.begin(), 0, count * stereo * sizeof echo [0] );
}
mix_effects( out, count );
int new_echo_pos = echo_pos + count * stereo;
if ( new_echo_pos >= echo_size )
new_echo_pos -= echo_size;
echo_pos = new_echo_pos;
assert( echo_pos < echo_size );
out += count * stereo;
mixer.samples_read += count;
pairs_remain -= count;
}
while ( pairs_remain );
}
if ( samples_avail() <= 0 || immediate_removal() )
{
for ( int i = bufs_size; --i >= 0; )
{
buf_t& b = bufs [i];
// TODO: might miss non-silence settling since it checks END of last read
if ( b.non_silent() )
b.remove_samples( mixer.samples_read );
else
b.remove_silence( mixer.samples_read );
}
mixer.samples_read = 0;
}
}
return out_size;
}
void Effects_Buffer::mix_effects( blip_sample_t out_ [], int pair_count )
{
typedef fixed_t stereo_fixed_t [stereo];
// add channels with echo, do echo, add channels without echo, then convert to 16-bit and output
int echo_phase = 1;
do
{
// mix any modified buffers
{
buf_t* buf = bufs;
int bufs_remain = bufs_size;
do
{
if ( buf->non_silent() && buf->echo == echo_phase )
{
stereo_fixed_t* BLARGG_RESTRICT out = (stereo_fixed_t*) &echo [echo_pos];
int const bass = BLIP_READER_BASS( *buf );
BLIP_READER_BEGIN( in, *buf );
BLIP_READER_ADJ_( in, mixer.samples_read );
fixed_t const vol_0 = buf->vol [0];
fixed_t const vol_1 = buf->vol [1];
int count = (unsigned) (echo_size - echo_pos) / stereo;
int remain = pair_count;
if ( count > remain )
count = remain;
do
{
remain -= count;
BLIP_READER_ADJ_( in, count );
out += count;
int offset = -count;
do
{
fixed_t s = BLIP_READER_READ( in );
BLIP_READER_NEXT_IDX_( in, bass, offset );
out [offset] [0] += s * vol_0;
out [offset] [1] += s * vol_1;
}
while ( ++offset );
out = (stereo_fixed_t*) echo.begin();
count = remain;
}
while ( remain );
BLIP_READER_END( in, *buf );
}
buf++;
}
while ( --bufs_remain );
}
// add echo
if ( echo_phase && !no_echo )
{
fixed_t const feedback = s.feedback;
fixed_t const treble = s.treble;
int i = 1;
do
{
fixed_t low_pass = s.low_pass [i];
fixed_t* echo_end = &echo [echo_size + i];
fixed_t const* BLARGG_RESTRICT in_pos = &echo [echo_pos + i];
int out_offset = echo_pos + i + s.delay [i];
if ( out_offset >= echo_size )
out_offset -= echo_size;
assert( out_offset < echo_size );
fixed_t* BLARGG_RESTRICT out_pos = &echo [out_offset];
// break into up to three chunks to avoid having to handle wrap-around
// in middle of core loop
int remain = pair_count;
do
{
fixed_t const* pos = in_pos;
if ( pos < out_pos )
pos = out_pos;
int count = (unsigned) ((char*) echo_end - (char const*) pos) /
(unsigned) (stereo * sizeof (fixed_t));
if ( count > remain )
count = remain;
remain -= count;
in_pos += count * stereo;
out_pos += count * stereo;
int offset = -count;
do
{
low_pass += FROM_FIXED( in_pos [offset * stereo] - low_pass ) * treble;
out_pos [offset * stereo] = FROM_FIXED( low_pass ) * feedback;
}
while ( ++offset );
if ( in_pos >= echo_end ) in_pos -= echo_size;
if ( out_pos >= echo_end ) out_pos -= echo_size;
}
while ( remain );
s.low_pass [i] = low_pass;
}
while ( --i >= 0 );
}
}
while ( --echo_phase >= 0 );
// clamp to 16 bits
{
stereo_fixed_t const* BLARGG_RESTRICT in = (stereo_fixed_t*) &echo [echo_pos];
typedef blip_sample_t stereo_blip_sample_t [stereo];
stereo_blip_sample_t* BLARGG_RESTRICT out = (stereo_blip_sample_t*) out_;
int count = (unsigned) (echo_size - echo_pos) / (unsigned) stereo;
int remain = pair_count;
if ( count > remain )
count = remain;
do
{
remain -= count;
in += count;
out += count;
int offset = -count;
do
{
fixed_t in_0 = FROM_FIXED( in [offset] [0] );
fixed_t in_1 = FROM_FIXED( in [offset] [1] );
BLIP_CLAMP( in_0, in_0 );
out [offset] [0] = (blip_sample_t) in_0;
BLIP_CLAMP( in_1, in_1 );
out [offset] [1] = (blip_sample_t) in_1;
}
while ( ++offset );
in = (stereo_fixed_t*) echo.begin();
count = remain;
}
while ( remain );
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,712 @@
// Gb_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Gb_Apu.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
bool const cgb_02 = false; // enables bug in early CGB units that causes problems in some games
bool const cgb_05 = false; // enables CGB-05 zombie behavior
int const trigger_mask = 0x80;
int const length_enabled = 0x40;
void Gb_Osc::reset()
{
output = NULL;
last_amp = 0;
delay = 0;
phase = 0;
enabled = false;
}
inline void Gb_Osc::update_amp( blip_time_t time, int new_amp )
{
output->set_modified();
int delta = new_amp - last_amp;
if ( delta )
{
last_amp = new_amp;
fast_synth->offset( time, delta, output );
}
}
// Units
void Gb_Osc::clock_length()
{
if ( (regs [4] & length_enabled) && length_ctr )
{
if ( --length_ctr <= 0 )
enabled = false;
}
}
inline int Gb_Env::reload_env_timer()
{
int raw = regs [2] & 7;
env_delay = (raw ? raw : 8);
return raw;
}
void Gb_Env::clock_envelope()
{
if ( env_enabled && --env_delay <= 0 && reload_env_timer() )
{
int v = volume + (regs [2] & 0x08 ? +1 : -1);
if ( 0 <= v && v <= 15 )
volume = v;
else
env_enabled = false;
}
}
inline void Gb_Sweep_Square::reload_sweep_timer()
{
sweep_delay = (regs [0] & period_mask) >> 4;
if ( !sweep_delay )
sweep_delay = 8;
}
void Gb_Sweep_Square::calc_sweep( bool update )
{
int const shift = regs [0] & shift_mask;
int const delta = sweep_freq >> shift;
sweep_neg = (regs [0] & 0x08) != 0;
int const freq = sweep_freq + (sweep_neg ? -delta : delta);
if ( freq > 0x7FF )
{
enabled = false;
}
else if ( shift && update )
{
sweep_freq = freq;
regs [3] = freq & 0xFF;
regs [4] = (regs [4] & ~0x07) | (freq >> 8 & 0x07);
}
}
void Gb_Sweep_Square::clock_sweep()
{
if ( --sweep_delay <= 0 )
{
reload_sweep_timer();
if ( sweep_enabled && (regs [0] & period_mask) )
{
calc_sweep( true );
calc_sweep( false );
}
}
}
int Gb_Wave::access( int addr ) const
{
if ( enabled )
{
addr = phase & (bank_size - 1);
if ( mode == Gb_Apu::mode_dmg )
{
addr++;
if ( delay > clk_mul )
return -1; // can only access within narrow time window while playing
}
addr >>= 1;
}
return addr & 0x0F;
}
// write_register
int Gb_Osc::write_trig( int frame_phase, int max_len, int old_data )
{
int data = regs [4];
if ( (frame_phase & 1) && !(old_data & length_enabled) && length_ctr )
{
if ( (data & length_enabled) || cgb_02 )
length_ctr--;
}
if ( data & trigger_mask )
{
enabled = true;
if ( !length_ctr )
{
length_ctr = max_len;
if ( (frame_phase & 1) && (data & length_enabled) )
length_ctr--;
}
}
if ( !length_ctr )
enabled = false;
return data & trigger_mask;
}
inline void Gb_Env::zombie_volume( int old, int data )
{
int v = volume;
if ( mode == Gb_Apu::mode_agb || cgb_05 )
{
// CGB-05 behavior, very close to AGB behavior as well
if ( (old ^ data) & 8 )
{
if ( !(old & 8) )
{
v++;
if ( old & 7 )
v++;
}
v = 16 - v;
}
else if ( (old & 0x0F) == 8 )
{
v++;
}
}
else
{
// CGB-04&02 behavior, very close to MGB behavior as well
if ( !(old & 7) && env_enabled )
v++;
else if ( !(old & 8) )
v += 2;
if ( (old ^ data) & 8 )
v = 16 - v;
}
volume = v & 0x0F;
}
bool Gb_Env::write_register( int frame_phase, int reg, int old, int data )
{
int const max_len = 64;
switch ( reg )
{
case 1:
length_ctr = max_len - (data & (max_len - 1));
break;
case 2:
if ( !dac_enabled() )
enabled = false;
zombie_volume( old, data );
if ( (data & 7) && env_delay == 8 )
{
env_delay = 1;
clock_envelope(); // TODO: really happens at next length clock
}
break;
case 4:
if ( write_trig( frame_phase, max_len, old ) )
{
volume = regs [2] >> 4;
reload_env_timer();
env_enabled = true;
if ( frame_phase == 7 )
env_delay++;
if ( !dac_enabled() )
enabled = false;
return true;
}
}
return false;
}
bool Gb_Square::write_register( int frame_phase, int reg, int old_data, int data )
{
bool result = Gb_Env::write_register( frame_phase, reg, old_data, data );
if ( result )
delay = (delay & (4 * clk_mul - 1)) + period();
return result;
}
inline void Gb_Noise::write_register( int frame_phase, int reg, int old_data, int data )
{
if ( Gb_Env::write_register( frame_phase, reg, old_data, data ) )
{
phase = 0x7FFF;
delay += 8 * clk_mul;
}
}
inline void Gb_Sweep_Square::write_register( int frame_phase, int reg, int old_data, int data )
{
if ( reg == 0 && sweep_enabled && sweep_neg && !(data & 0x08) )
enabled = false; // sweep negate disabled after used
if ( Gb_Square::write_register( frame_phase, reg, old_data, data ) )
{
sweep_freq = frequency();
sweep_neg = false;
reload_sweep_timer();
sweep_enabled = (regs [0] & (period_mask | shift_mask)) != 0;
if ( regs [0] & shift_mask )
calc_sweep( false );
}
}
void Gb_Wave::corrupt_wave()
{
int pos = ((phase + 1) & (bank_size - 1)) >> 1;
if ( pos < 4 )
wave_ram [0] = wave_ram [pos];
else
for ( int i = 4; --i >= 0; )
wave_ram [i] = wave_ram [(pos & ~3) + i];
}
inline void Gb_Wave::write_register( int frame_phase, int reg, int old_data, int data )
{
int const max_len = 256;
switch ( reg )
{
case 0:
if ( !dac_enabled() )
enabled = false;
break;
case 1:
length_ctr = max_len - data;
break;
case 4:
bool was_enabled = enabled;
if ( write_trig( frame_phase, max_len, old_data ) )
{
if ( !dac_enabled() )
enabled = false;
else if ( mode == Gb_Apu::mode_dmg && was_enabled &&
(unsigned) (delay - 2 * clk_mul) < 2 * clk_mul )
corrupt_wave();
phase = 0;
delay = period() + 6 * clk_mul;
}
}
}
void Gb_Apu::write_osc( int reg, int old_data, int data )
{
int index = (reg * 3 + 3) >> 4; // avoids divide
assert( index == reg / 5 );
reg -= index * 5;
switch ( index )
{
case 0: square1.write_register( frame_phase, reg, old_data, data ); break;
case 1: square2.write_register( frame_phase, reg, old_data, data ); break;
case 2: wave .write_register( frame_phase, reg, old_data, data ); break;
case 3: noise .write_register( frame_phase, reg, old_data, data ); break;
}
}
// Synthesis
void Gb_Square::run( blip_time_t time, blip_time_t end_time )
{
// Calc duty and phase
static byte const duty_offsets [4] = { 1, 1, 3, 7 };
static byte const duties [4] = { 1, 2, 4, 6 };
int const duty_code = regs [1] >> 6;
int duty_offset = duty_offsets [duty_code];
int duty = duties [duty_code];
if ( mode == Gb_Apu::mode_agb )
{
// AGB uses inverted duty
duty_offset -= duty;
duty = 8 - duty;
}
int ph = (this->phase + duty_offset) & 7;
// Determine what will be generated
int vol = 0;
Blip_Buffer* const out = this->output;
if ( out )
{
int amp = dac_off_amp;
if ( dac_enabled() )
{
if ( enabled )
vol = this->volume;
amp = -dac_bias;
if ( mode == Gb_Apu::mode_agb )
amp = -(vol >> 1);
// Play inaudible frequencies as constant amplitude
if ( frequency() >= 0x7FA && delay < 32 * clk_mul )
{
amp += (vol * duty) >> 3;
vol = 0;
}
if ( ph < duty )
{
amp += vol;
vol = -vol;
}
}
update_amp( time, amp );
}
// Generate wave
time += delay;
if ( time < end_time )
{
int const per = this->period();
if ( !vol )
{
#if GB_APU_FAST
time = end_time;
#else
// Maintain phase when not playing
int count = (end_time - time + per - 1) / per;
ph += count; // will be masked below
time += (blip_time_t) count * per;
#endif
}
else
{
// Output amplitude transitions
int delta = vol;
do
{
ph = (ph + 1) & 7;
if ( ph == 0 || ph == duty )
{
norm_synth->offset_inline( time, delta, out );
delta = -delta;
}
time += per;
}
while ( time < end_time );
if ( delta != vol )
last_amp -= delta;
}
this->phase = (ph - duty_offset) & 7;
}
delay = time - end_time;
}
#if !GB_APU_FAST
// Quickly runs LFSR for a large number of clocks. For use when noise is generating
// no sound.
static unsigned run_lfsr( unsigned s, unsigned mask, int count )
{
bool const optimized = true; // set to false to use only unoptimized loop in middle
// optimization used in several places:
// ((s & (1 << b)) << n) ^ ((s & (1 << b)) << (n + 1)) = (s & (1 << b)) * (3 << n)
if ( mask == 0x4000 && optimized )
{
if ( count >= 32767 )
count %= 32767;
// Convert from Fibonacci to Galois configuration,
// shifted left 1 bit
s ^= (s & 1) * 0x8000;
// Each iteration is equivalent to clocking LFSR 255 times
while ( (count -= 255) > 0 )
s ^= ((s & 0xE) << 12) ^ ((s & 0xE) << 11) ^ (s >> 3);
count += 255;
// Each iteration is equivalent to clocking LFSR 15 times
// (interesting similarity to single clocking below)
while ( (count -= 15) > 0 )
s ^= ((s & 2) * (3 << 13)) ^ (s >> 1);
count += 15;
// Remaining singles
while ( --count >= 0 )
s = ((s & 2) * (3 << 13)) ^ (s >> 1);
// Convert back to Fibonacci configuration
s &= 0x7FFF;
}
else if ( count < 8 || !optimized )
{
// won't fully replace upper 8 bits, so have to do the unoptimized way
while ( --count >= 0 )
s = (s >> 1 | mask) ^ (mask & -((s - 1) & 2));
}
else
{
if ( count > 127 )
{
count %= 127;
if ( !count )
count = 127; // must run at least once
}
// Need to keep one extra bit of history
s = s << 1 & 0xFF;
// Convert from Fibonacci to Galois configuration,
// shifted left 2 bits
s ^= (s & 2) * 0x80;
// Each iteration is equivalent to clocking LFSR 7 times
// (interesting similarity to single clocking below)
while ( (count -= 7) > 0 )
s ^= ((s & 4) * (3 << 5)) ^ (s >> 1);
count += 7;
// Remaining singles
while ( --count >= 0 )
s = ((s & 4) * (3 << 5)) ^ (s >> 1);
// Convert back to Fibonacci configuration and
// repeat last 8 bits above significant 7
s = (s << 7 & 0x7F80) | (s >> 1 & 0x7F);
}
return s;
}
#endif
void Gb_Noise::run( blip_time_t time, blip_time_t end_time )
{
// Determine what will be generated
int vol = 0;
Blip_Buffer* const out = this->output;
if ( out )
{
int amp = dac_off_amp;
if ( dac_enabled() )
{
if ( enabled )
vol = this->volume;
amp = -dac_bias;
if ( mode == Gb_Apu::mode_agb )
amp = -(vol >> 1);
if ( !(phase & 1) )
{
amp += vol;
vol = -vol;
}
}
// AGB negates final output
if ( mode == Gb_Apu::mode_agb )
{
vol = -vol;
amp = -amp;
}
update_amp( time, amp );
}
// Run timer and calculate time of next LFSR clock
static byte const period1s [8] = { 1, 2, 4, 6, 8, 10, 12, 14 };
int const period1 = period1s [regs [3] & 7] * clk_mul;
#if GB_APU_FAST
time += delay;
#else
{
int extra = (end_time - time) - delay;
int const per2 = this->period2();
time += delay + ((divider ^ (per2 >> 1)) & (per2 - 1)) * period1;
int count = (extra < 0 ? 0 : (extra + period1 - 1) / period1);
divider = (divider - count) & period2_mask;
delay = count * period1 - extra;
}
#endif
// Generate wave
if ( time < end_time )
{
unsigned const mask = this->lfsr_mask();
unsigned bits = this->phase;
int per = period2( period1 * 8 );
#if GB_APU_FAST
// Noise can be THE biggest time hog; adjust as necessary
int const min_period = 24;
if ( per < min_period )
per = min_period;
#endif
if ( period2_index() >= 0xE )
{
time = end_time;
}
else if ( !vol )
{
#if GB_APU_FAST
time = end_time;
#else
// Maintain phase when not playing
int count = (end_time - time + per - 1) / per;
time += (blip_time_t) count * per;
bits = run_lfsr( bits, ~mask, count );
#endif
}
else
{
Blip_Synth_Fast const* const synth = fast_synth; // cache
// Output amplitude transitions
int delta = -vol;
do
{
unsigned changed = bits + 1;
bits = bits >> 1 & mask;
if ( changed & 2 )
{
bits |= ~mask;
delta = -delta;
synth->offset_inline( time, delta, out );
}
time += per;
}
while ( time < end_time );
if ( delta == vol )
last_amp += delta;
}
this->phase = bits;
}
#if GB_APU_FAST
delay = time - end_time;
#endif
}
void Gb_Wave::run( blip_time_t time, blip_time_t end_time )
{
// Calc volume
#if GB_APU_NO_AGB
static byte const shifts [4] = { 4+4, 0+4, 1+4, 2+4 };
int const volume_idx = regs [2] >> 5 & 3;
int const volume_shift = shifts [volume_idx];
int const volume_mul = 1;
#else
static byte const volumes [8] = { 0, 4, 2, 1, 3, 3, 3, 3 };
int const volume_shift = 2 + 4;
int const volume_idx = regs [2] >> 5 & (agb_mask | 3); // 2 bits on DMG/CGB, 3 on AGB
int const volume_mul = volumes [volume_idx];
#endif
// Determine what will be generated
int playing = false;
Blip_Buffer* const out = this->output;
if ( out )
{
int amp = dac_off_amp;
if ( dac_enabled() )
{
// Play inaudible frequencies as constant amplitude
amp = 8 << 4; // really depends on average of all samples in wave
// if delay is larger, constant amplitude won't start yet
if ( frequency() <= 0x7FB || delay > 15 * clk_mul )
{
if ( volume_mul && volume_shift != 4+4 )
playing = (int) enabled;
amp = (sample_buf << (phase << 2 & 4) & 0xF0) * playing;
}
amp = ((amp * volume_mul) >> volume_shift) - dac_bias;
}
update_amp( time, amp );
}
// Generate wave
time += delay;
if ( time < end_time )
{
byte const* wave = this->wave_ram;
// wave size and bank
#if GB_APU_NO_AGB
int const wave_mask = 0x1F;
int const swap_banks = 0;
#else
int const size20_mask = 0x20;
int const flags = regs [0] & agb_mask;
int const wave_mask = (flags & size20_mask) | 0x1F;
int swap_banks = 0;
if ( flags & bank40_mask )
{
swap_banks = flags & size20_mask;
wave += bank_size/2 - (swap_banks >> 1);
}
#endif
int ph = this->phase ^ swap_banks;
ph = (ph + 1) & wave_mask; // pre-advance
int const per = this->period();
if ( !playing )
{
#if GB_APU_FAST
time = end_time;
#else
// Maintain phase when not playing
int count = (end_time - time + per - 1) / per;
ph += count; // will be masked below
time += (blip_time_t) count * per;
#endif
}
else
{
Blip_Synth_Fast const* const synth = fast_synth; // cache
// Output amplitude transitions
int lamp = this->last_amp + dac_bias;
do
{
// Extract nibble
int nibble = wave [ph >> 1] << (ph << 2 & 4) & 0xF0;
ph = (ph + 1) & wave_mask;
// Scale by volume
int amp = (nibble * volume_mul) >> volume_shift;
int delta = amp - lamp;
if ( delta )
{
lamp = amp;
synth->offset_inline( time, delta, out );
}
time += per;
}
while ( time < end_time );
this->last_amp = lamp - dac_bias;
}
ph = (ph - 1) & wave_mask; // undo pre-advance and mask position
// Keep track of last byte read
if ( enabled )
sample_buf = wave [ph >> 1];
this->phase = ph ^ swap_banks; // undo swapped banks
}
delay = time - end_time;
}

View File

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

View File

@ -0,0 +1,208 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gbs_Core.h"
#include "blargg_endian.h"
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const tempo_unit = 16;
int const idle_addr = 0xF00D;
int const bank_size = 0x4000;
Gbs_Core::Gbs_Core() : rom( bank_size )
{
tempo = tempo_unit;
assert( offsetof (header_t,copyright [32]) == header_t::size );
}
Gbs_Core::~Gbs_Core() { }
void Gbs_Core::unload()
{
header_.timer_mode = 0; // set_tempo() reads this
rom.clear();
Gme_Loader::unload();
}
bool Gbs_Core::header_t::valid_tag() const
{
return 0 == memcmp( tag, "GBS", 3 );
}
blargg_err_t Gbs_Core::load_( Data_Reader& in )
{
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
if ( !header_.valid_tag() )
return blargg_err_file_type;
if ( header_.vers < 1 || 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

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,183 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gme_File.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_File::unload()
{
clear_playlist(); // BEFORE clearing track count
track_count_ = 0;
raw_track_count_ = 0;
Gme_Loader::unload();
}
Gme_File::Gme_File()
{
type_ = NULL;
user_data_ = NULL;
user_cleanup_ = NULL;
Gme_File::unload(); // clears fields
}
Gme_File::~Gme_File()
{
if ( user_cleanup_ )
user_cleanup_( user_data_ );
}
blargg_err_t Gme_File::post_load()
{
if ( !track_count() )
set_track_count( type()->track_count );
return Gme_Loader::post_load();
}
void Gme_File::clear_playlist()
{
playlist.clear();
clear_playlist_();
track_count_ = raw_track_count_;
}
void Gme_File::copy_field_( char out [], const char* in, int in_size )
{
if ( !in || !*in )
return;
// remove spaces/junk from beginning
while ( in_size && unsigned (*in - 1) <= ' ' - 1 )
{
in++;
in_size--;
}
// truncate
if ( in_size > max_field_ )
in_size = max_field_;
// find terminator
int len = 0;
while ( len < in_size && in [len] )
len++;
// remove spaces/junk from end
while ( len && unsigned (in [len - 1]) <= ' ' )
len--;
// copy
out [len] = 0;
memcpy( out, in, len );
// strip out stupid fields that should have been left blank
if ( !strcmp( out, "?" ) || !strcmp( out, "<?>" ) || !strcmp( out, "< ? >" ) )
out [0] = 0;
}
void Gme_File::copy_field_( char out [], const char* in )
{
copy_field_( out, in, max_field_ );
}
blargg_err_t Gme_File::remap_track_( int* track_io ) const
{
if ( (unsigned) *track_io >= (unsigned) track_count() )
return BLARGG_ERR( BLARGG_ERR_CALLER, "invalid track" );
if ( (unsigned) *track_io < (unsigned) playlist.size() )
{
M3u_Playlist::entry_t const& e = playlist [*track_io];
*track_io = 0;
if ( e.track >= 0 )
{
*track_io = e.track;
// TODO: really needs to be removed?
//if ( !(type_->flags_ & 0x02) )
// *track_io -= e.decimal_track;
}
if ( *track_io >= raw_track_count_ )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "invalid track in m3u playlist" );
}
else
{
check( !playlist.size() );
}
return blargg_ok;
}
blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
{
out->track_count = track_count();
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];
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;
copy_field_( out->song, e.name );
}
// play_length
out->play_length = out->length;
if ( out->play_length <= 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
}
return blargg_ok;
}

View File

@ -0,0 +1,153 @@
// Common interface for track information
// Game_Music_Emu $vers
#ifndef GME_FILE_H
#define GME_FILE_H
#include "gme.h"
#include "Gme_Loader.h"
#include "M3u_Playlist.h"
struct track_info_t
{
int track_count;
/* times in milliseconds; -1 if unknown */
int length; /* total length, if file specifies it */
int intro_length; /* length of song up to looping section */
int loop_length; /* length of looping section */
int fade_length;
int repeat_count;
/* Length if available, otherwise intro_length+loop_length*2 if available,
otherwise a default of 150000 (2.5 minutes). */
int play_length;
/* empty string if not available */
char system [256];
char game [256];
char song [256];
char author [256];
char composer [256];
char engineer [256];
char sequencer [256];
char tagger [256];
char copyright [256];
char date [256];
char comment [256];
char dumper [256];
char disc [256];
char track [256];
char ost [256];
};
enum { gme_max_field = 255 };
class Gme_File : public Gme_Loader {
public:
// Type of emulator. For example if this returns gme_nsfe_type, this object
// is an NSFE emulator, and you can downcast to an Nsfe_Emu* if necessary.
gme_type_t type() const;
// Loads an m3u playlist. Must be done AFTER loading main music file.
blargg_err_t load_m3u( const char path [] );
blargg_err_t load_m3u( Data_Reader& in );
// Clears any loaded m3u playlist and any internal playlist that the music
// format supports (NSFE for example).
void clear_playlist();
// Number of tracks or 0 if no file has been loaded
int track_count() const;
// Gets information for a track (length, name, author, etc.)
// See gme.h for definition of struct track_info_t.
blargg_err_t track_info( track_info_t* out, int track ) const;
// User data/cleanup
// Sets/gets pointer to data you want to associate with this emulator.
// You can use this for whatever you want.
void set_user_data( void* p ) { user_data_ = p; }
void* user_data() const { return user_data_; }
// Registers cleanup function to be called when deleting emulator, or NULL to
// clear it. Passes user_data to cleanup function.
void set_user_cleanup( gme_user_cleanup_t func ) { user_cleanup_ = func; }
public:
Gme_File();
~Gme_File();
protected:
// Services
void set_type( gme_type_t t ) { type_ = t; }
void set_track_count( int n ) { track_count_ = raw_track_count_ = n; }
// Must be overridden
virtual blargg_err_t track_info_( track_info_t* out, int track ) const BLARGG_PURE( ; )
// Optionally overridden
virtual void clear_playlist_() { }
protected: // Gme_Loader overrides
virtual void unload();
virtual blargg_err_t post_load();
protected:
blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu
private:
gme_type_t type_;
void* user_data_;
gme_user_cleanup_t user_cleanup_;
int track_count_;
int raw_track_count_;
M3u_Playlist playlist;
char playlist_warning [64];
blargg_err_t load_m3u_( blargg_err_t );
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 );
};
struct gme_type_t_
{
const char* system; /* name of system this music file type is generally for */
int track_count; /* non-zero for formats with a fixed number of tracks */
Music_Emu* (*new_emu)(); /* Create new emulator for this type (C++ only) */
Music_Emu* (*new_info)();/* Create new info reader for this type (C++ only) */
/* internal */
const char* extension_;
int flags_;
};
/* Emulator type constants for each supported file type */
extern const gme_type_t_
gme_ay_type [1],
gme_gbs_type [1],
gme_gym_type [1],
gme_hes_type [1],
gme_kss_type [1],
gme_nsf_type [1],
gme_nsfe_type [1],
gme_sap_type [1],
gme_sfm_type [1],
gme_sgc_type [1],
gme_spc_type [1],
gme_vgm_type [1],
gme_vgz_type [1];
#define GME_COPY_FIELD( in, out, name ) \
{ Gme_File::copy_field_( out->name, in.name, sizeof in.name ); }
inline gme_type_t Gme_File::type() const { return type_; }
inline int Gme_File::track_count() const { return track_count_; }
inline blargg_err_t Gme_File::track_info_( track_info_t*, int ) const { return blargg_ok; }
#endif

View File

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

View File

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

View File

@ -0,0 +1,428 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Gym_Emu.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"
double const min_tempo = 0.25;
double const oversample = 5 / 3.0;
double const fm_gain = 3.0;
int const base_clock = 53700300;
int const clock_rate = base_clock / 15;
Gym_Emu::Gym_Emu()
{
resampler.set_callback( play_frame_, this );
pos = NULL;
disable_oversampling_ = false;
set_type( gme_gym_type );
set_silence_lookahead( 1 ); // tracks should already be trimmed
pcm_buf = stereo_buf.center();
}
Gym_Emu::~Gym_Emu() { }
// Track info
static void get_gym_info( Gym_Emu::header_t const& h, int length, track_info_t* out )
{
if ( 0 != memcmp( h.tag, "GYMX", 4 ) )
return;
length = length * 50 / 3; // 1000 / 60
int loop = get_le32( h.loop_start );
if ( loop )
{
out->intro_length = loop * 50 / 3;
out->loop_length = length - out->intro_length;
}
else
{
out->length = length;
out->intro_length = length; // make it clear that track is no longer than length
out->loop_length = 0;
}
// more stupidity where the field should have been left blank
if ( strcmp( h.song, "Unknown Song" ) )
GME_COPY_FIELD( h, out, song );
if ( strcmp( h.game, "Unknown Game" ) )
GME_COPY_FIELD( h, out, game );
if ( strcmp( h.copyright, "Unknown Publisher" ) )
GME_COPY_FIELD( h, out, copyright );
if ( strcmp( h.dumper, "Unknown Person" ) )
GME_COPY_FIELD( h, out, dumper );
if ( strcmp( h.comment, "Header added by YMAMP" ) )
GME_COPY_FIELD( h, out, comment );
}
static void hash_gym_file( Gym_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
{
out.hash_( &h.loop_start[0], sizeof(h.loop_start) );
out.hash_( &h.packed[0], sizeof(h.packed) );
out.hash_( data, data_size );
}
static int gym_track_length( byte const p [], byte const* end )
{
int time = 0;
while ( p < end )
{
switch ( *p++ )
{
case 0:
time++;
break;
case 1:
case 2:
p += 2;
break;
case 3:
p += 1;
break;
}
}
return time;
}
blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const
{
get_gym_info( header_, gym_track_length( log_begin(), file_end() ), out );
return blargg_ok;
}
static blargg_err_t check_header( byte const in [], int size, int* data_offset = NULL )
{
if ( size < 4 )
return blargg_err_file_type;
if ( memcmp( in, "GYMX", 4 ) == 0 )
{
if ( size < Gym_Emu::header_t::size + 1 )
return blargg_err_file_type;
if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 )
return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "packed GYM file" );
if ( data_offset )
*data_offset = Gym_Emu::header_t::size;
}
else if ( *in > 3 )
{
return blargg_err_file_type;
}
return blargg_ok;
}
struct Gym_File : Gme_Info_
{
int data_offset;
Gym_File() { set_type( gme_gym_type ); }
blargg_err_t load_mem_( byte const in [], int size )
{
data_offset = 0;
return check_header( in, size, &data_offset );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
int length = gym_track_length( &file_begin() [data_offset], file_end() );
get_gym_info( *(Gym_Emu::header_t const*) file_begin(), length, out );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
Gym_Emu::header_t const* h = ( Gym_Emu::header_t const* ) file_begin();
byte const* data = &file_begin() [data_offset];
hash_gym_file( *h, data, file_end() - data, out );
return blargg_ok;
}
};
static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; }
gme_type_t_ const gme_gym_type [1] = {{ "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 }};
// Setup
blargg_err_t Gym_Emu::set_sample_rate_( int sample_rate )
{
blip_eq_t eq( -32, 8000, sample_rate );
apu.treble_eq( eq );
pcm_synth.treble_eq( eq );
apu.volume( 0.135 * fm_gain * gain() );
double factor = oversample;
if ( disable_oversampling_ )
factor = (double) base_clock / 7 / 144 / sample_rate;
RETURN_ERR( resampler.setup( factor, 0.990, fm_gain * gain() ) );
factor = resampler.rate();
double fm_rate = sample_rate * factor;
RETURN_ERR( stereo_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) );
stereo_buf.clock_rate( clock_rate );
RETURN_ERR( fm.set_rate( fm_rate, base_clock / 7.0 ) );
RETURN_ERR( resampler.reset( (int) (1.0 / 60 / min_tempo * sample_rate) ) );
return blargg_ok;
}
void Gym_Emu::set_tempo_( double t )
{
if ( t < min_tempo )
{
set_tempo( min_tempo );
return;
}
if ( stereo_buf.sample_rate() )
{
double denom = tempo() * 60;
clocks_per_frame = (int) (clock_rate / denom);
resampler.resize( (int) (sample_rate() / denom) );
}
}
void Gym_Emu::mute_voices_( int mask )
{
Music_Emu::mute_voices_( mask );
fm.mute_voices( mask );
apu.set_output( (mask & 0x80) ? 0 : stereo_buf.center() );
pcm_synth.volume( (mask & 0x40) ? 0.0 : 0.125 / 256 * fm_gain * gain() );
}
blargg_err_t Gym_Emu::load_mem_( byte const in [], int size )
{
assert( offsetof (header_t,packed [4]) == header_t::size );
log_offset = 0;
RETURN_ERR( check_header( in, size, &log_offset ) );
loop_begin = NULL;
static const char* const names [] = {
"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
};
set_voice_names( names );
set_voice_count( 8 );
if ( log_offset )
header_ = *(header_t const*) in;
else
memset( &header_, 0, sizeof header_ );
return blargg_ok;
}
// Emulation
blargg_err_t Gym_Emu::start_track_( int track )
{
RETURN_ERR( Music_Emu::start_track_( track ) );
pos = log_begin();
loop_remain = get_le32( header_.loop_start );
prev_pcm_count = 0;
pcm_enabled = 0;
pcm_amp = -1;
fm.reset();
apu.reset();
stereo_buf.clear();
resampler.clear();
pcm_buf = stereo_buf.center();
return blargg_ok;
}
void Gym_Emu::run_pcm( byte const pcm_in [], int pcm_count )
{
// Guess beginning and end of sample and adjust rate and buffer position accordingly.
// count dac samples in next frame
int next_pcm_count = 0;
const byte* p = this->pos;
int cmd;
while ( (cmd = *p++) != 0 )
{
int data = *p++;
if ( cmd <= 2 )
++p;
if ( cmd == 1 && data == 0x2A )
next_pcm_count++;
}
// detect beginning and end of sample
int rate_count = pcm_count;
int start = 0;
if ( !prev_pcm_count && next_pcm_count && pcm_count < next_pcm_count )
{
rate_count = next_pcm_count;
start = next_pcm_count - pcm_count;
}
else if ( prev_pcm_count && !next_pcm_count && pcm_count < prev_pcm_count )
{
rate_count = prev_pcm_count;
}
// Evenly space samples within buffer section being used
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;
int pcm_amp = this->pcm_amp;
if ( pcm_amp < 0 )
pcm_amp = pcm_in [0];
for ( int i = 0; i < pcm_count; i++ )
{
int delta = pcm_in [i] - pcm_amp;
pcm_amp += delta;
pcm_synth.offset_resampled( time, delta, pcm_buf );
time += period;
}
this->pcm_amp = pcm_amp;
pcm_buf->set_modified();
}
void Gym_Emu::parse_frame()
{
byte pcm [1024]; // all PCM writes for frame
int pcm_size = 0;
const byte* pos = this->pos;
if ( loop_remain && !--loop_remain )
loop_begin = pos; // find loop on first time through sequence
int cmd;
while ( (cmd = *pos++) != 0 )
{
int data = *pos++;
if ( cmd == 1 )
{
int data2 = *pos++;
if ( data == 0x2A )
{
pcm [pcm_size] = data2;
if ( pcm_size < (int) sizeof pcm - 1 )
pcm_size += pcm_enabled;
}
else
{
if ( data == 0x2B )
pcm_enabled = data2 >> 7 & 1;
fm.write0( data, data2 );
}
}
else if ( cmd == 2 )
{
int data2 = *pos++;
if ( data == 0xB6 )
{
Blip_Buffer * pcm_buf = NULL;
switch ( data2 >> 6 )
{
case 0: pcm_buf = NULL; break;
case 1: pcm_buf = stereo_buf.right(); break;
case 2: pcm_buf = stereo_buf.left(); break;
case 3: pcm_buf = stereo_buf.center(); break;
}
/*if ( this->pcm_buf != pcm_buf )
{
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 );
}*/
this->pcm_buf = pcm_buf;
}
fm.write1( data, data2 );
}
else if ( cmd == 3 )
{
apu.write_data( 0, data );
}
else
{
// to do: many GYM streams are full of errors, and error count should
// reflect cases where music is really having problems
//log_error();
--pos; // put data back
}
}
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

@ -0,0 +1,88 @@
// Sega Genesis/Mega Drive GYM music file emulator
// Performs PCM timing recovery to improve sample quality.
// Game_Music_Emu $vers
#ifndef GYM_EMU_H
#define GYM_EMU_H
#include "Dual_Resampler.h"
#include "Ym2612_Emu.h"
#include "Music_Emu.h"
#include "Sms_Apu.h"
class Gym_Emu : public Music_Emu {
public:
// GYM file header (optional; many files have NO header at all)
struct header_t
{
enum { size = 428 };
char tag [ 4];
char song [ 32];
char game [ 32];
char copyright [ 32];
char emulator [ 32];
char dumper [ 32];
char comment [256];
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_; }
static gme_type_t static_type() { return gme_gym_type; }
// Disables running FM chips at higher than normal rate. Will result in slightly
// more aliasing of high notes.
void disable_oversampling( bool disable = true ) { disable_oversampling_ = disable; }
blargg_err_t hash_( Hash_Function& ) const;
// Implementation
public:
Gym_Emu();
~Gym_Emu();
protected:
virtual blargg_err_t load_mem_( byte const [], int );
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t set_sample_rate_( int sample_rate );
virtual blargg_err_t start_track_( int );
virtual blargg_err_t play_( int count, sample_t [] );
virtual void mute_voices_( int );
virtual void set_tempo_( double );
private:
// Log
byte const* pos; // current position
byte const* loop_begin;
int log_offset; // size of header (0 or header_t::size)
int loop_remain; // frames remaining until loop_begin has been located
int clocks_per_frame;
bool disable_oversampling_;
// PCM
int pcm_amp;
int prev_pcm_count; // for detecting beginning/end of group of samples
int pcm_enabled;
// large objects
Dual_Resampler resampler;
Stereo_Buffer stereo_buf;
Blip_Buffer * pcm_buf;
Ym2612_Emu fm;
Sms_Apu apu;
Blip_Synth_Fast pcm_synth;
header_t header_;
byte const* log_begin() const { return file_begin() + log_offset; }
void parse_frame();
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

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

View File

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

View File

@ -0,0 +1,309 @@
// 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

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,192 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Hes_Emu.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"
Hes_Emu::Hes_Emu()
{
set_type( gme_hes_type );
set_silence_lookahead( 6 );
set_gain( 1.11 );
}
Hes_Emu::~Hes_Emu() { }
void Hes_Emu::unload()
{
core.unload();
Music_Emu::unload();
}
static byte const* copy_field( byte const in [], char* out )
{
if ( in )
{
int len = 0x20;
if ( in [0x1F] && !in [0x2F] )
len = 0x30; // fields are sometimes 16 bytes longer (ugh)
// since text fields are where any data could be, detect non-text
// and fields with data after zero byte terminator
int i = 0;
for ( ; i < len && in [i]; i++ )
if ( (unsigned) (in [i] - ' ') >= 0xFF - ' ' ) // also treat 0xFF as non-text
return 0; // non-ASCII found
for ( ; i < len; i++ )
if ( in [i] )
return 0; // data after terminator
Gme_File::copy_field_( out, (char const*) in, len );
in += len;
}
return in;
}
static byte const* copy_hes_fields( byte const in [], track_info_t* out )
{
byte const* in_offset = in;
if ( *in_offset >= ' ' )
{
in_offset = copy_field( in_offset, out->game );
in_offset = copy_field( in_offset, out->author );
in_offset = copy_field( in_offset, out->copyright );
}
return in_offset ? in_offset : in;
}
static void hash_hes_file( Hes_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out )
{
out.hash_( &h.vers, sizeof(h.vers) );
out.hash_( &h.first_track, sizeof(h.first_track) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.banks[0], sizeof(h.banks) );
out.hash_( &h.data_size[0], sizeof(h.data_size) );
out.hash_( &h.addr[0], sizeof(h.addr) );
out.hash_( &h.unused[0], sizeof(h.unused) );
out.hash_( data, Hes_Core::info_offset );
track_info_t temp; // GCC whines about passing a pointer to a temporary here
byte const* more_data = copy_hes_fields( data + Hes_Core::info_offset, &temp );
out.hash_( more_data, data_size - ( more_data - data ) );
}
blargg_err_t Hes_Emu::track_info_( track_info_t* out, int ) const
{
copy_hes_fields( core.data() + core.info_offset, out );
return blargg_ok;
}
struct Hes_File : Gme_Info_
{
enum { fields_offset = Hes_Core::header_t::size + Hes_Core::info_offset };
union header_t {
Hes_Core::header_t header;
byte data [fields_offset + 0x30 * 3];
} const* h;
Hes_File()
{
set_type( gme_hes_type );
}
blargg_err_t load_mem_( byte const begin [], int size )
{
h = ( header_t const* ) begin;
if ( !h->header.valid_tag() )
return blargg_err_file_type;
return blargg_ok;
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_hes_fields( h->data + fields_offset, out );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
hash_hes_file( h->header, file_begin() + h->header.size, file_end() - file_begin() - h->header.size, out );
return blargg_ok;
}
};
static Music_Emu* new_hes_emu () { return BLARGG_NEW Hes_Emu ; }
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 }};
blargg_err_t Hes_Emu::load_( Data_Reader& in )
{
RETURN_ERR( core.load( in ) );
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"
};
set_voice_names( names );
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
};
set_voice_types( types );
set_voice_count( core.apu().osc_count + core.adpcm().osc_count );
core.apu().volume( gain() );
core.adpcm().volume( gain() );
return setup_buffer( 7159091 );
}
void Hes_Emu::update_eq( blip_eq_t const& eq )
{
core.apu().treble_eq( eq );
core.adpcm().treble_eq( eq );
}
void Hes_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
{
if ( i < core.apu().osc_count )
core.apu().set_output( i, c, l, r );
else if ( i == core.apu().osc_count )
core.adpcm().set_output( 0, c, l, r );
}
void Hes_Emu::set_tempo_( double t )
{
core.set_tempo( t );
}
blargg_err_t Hes_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
return core.start_track( track );
}
blargg_err_t Hes_Emu::run_clocks( blip_time_t& duration_, int )
{
return core.end_frame( duration_ );
}
blargg_err_t Hes_Emu::hash_( Hash_Function& out ) const
{
hash_hes_file( header(), core.data(), core.data_size(), out );
return blargg_ok;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,35 @@
// $package. http://www.slack.net/~ant/
#include "Kss_Core.h"
#include "blargg_endian.h"
//#include "z80_cpu_log.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
#define IN_PORT( addr ) cpu_in( TIME(), addr )
#define WRITE_MEM( addr, data ) {FLUSH_TIME(); cpu_write( addr, data );}
#define IDLE_ADDR idle_addr
#define CPU cpu
#define CPU_BEGIN \
bool Kss_Core::run_cpu( time_t end_time )\
{\
cpu.set_end_time( end_time );
#include "Z80_Cpu_run.h"
return warning;
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

@ -0,0 +1,290 @@
// Blip_Buffer $vers. http://www.slack.net/~ant/
#include "Multi_Buffer.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf )
{
length_ = 0;
sample_rate_ = 0;
channels_changed_count_ = 1;
channel_types_ = NULL;
channel_count_ = 0;
immediate_removal_ = true;
}
Multi_Buffer::channel_t Multi_Buffer::channel( int /*index*/ )
{
channel_t ch;
ch.center = ch.left = ch.right = NULL;
return ch;
}
// Silent_Buffer
Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse
{
// TODO: better to use empty Blip_Buffer so caller never has to check for NULL?
chan.left = NULL;
chan.center = NULL;
chan.right = NULL;
}
// Mono_Buffer
Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 )
{
chan.center = &buf;
chan.left = &buf;
chan.right = &buf;
}
Mono_Buffer::~Mono_Buffer() { }
blargg_err_t Mono_Buffer::set_sample_rate( int rate, int msec )
{
RETURN_ERR( buf.set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() );
}
// Tracked_Blip_Buffer
int const blip_buffer_extra = 32; // TODO: explain why this value
Tracked_Blip_Buffer::Tracked_Blip_Buffer()
{
last_non_silence = 0;
}
void Tracked_Blip_Buffer::clear()
{
last_non_silence = 0;
Blip_Buffer::clear();
}
void Tracked_Blip_Buffer::end_frame( blip_time_t t )
{
Blip_Buffer::end_frame( t );
if ( modified() )
{
clear_modified();
last_non_silence = samples_avail() + blip_buffer_extra;
}
}
unsigned Tracked_Blip_Buffer::non_silent() const
{
return last_non_silence | unsettled();
}
inline void Tracked_Blip_Buffer::remove_( int n )
{
if ( (last_non_silence -= n) < 0 )
last_non_silence = 0;
}
void Tracked_Blip_Buffer::remove_silence( int n )
{
remove_( n );
Blip_Buffer::remove_silence( n );
}
void Tracked_Blip_Buffer::remove_samples( int n )
{
remove_( n );
Blip_Buffer::remove_samples( n );
}
void Tracked_Blip_Buffer::remove_all_samples()
{
int avail = samples_avail();
if ( !non_silent() )
remove_silence( avail );
else
remove_samples( avail );
}
int Tracked_Blip_Buffer::read_samples( blip_sample_t out [], int count )
{
count = Blip_Buffer::read_samples( out, count );
remove_( count );
return count;
}
// Stereo_Buffer
int const stereo = 2;
Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 )
{
chan.center = mixer.bufs [2] = &bufs [2];
chan.left = mixer.bufs [0] = &bufs [0];
chan.right = mixer.bufs [1] = &bufs [1];
mixer.samples_read = 0;
}
Stereo_Buffer::~Stereo_Buffer() { }
blargg_err_t Stereo_Buffer::set_sample_rate( int rate, int msec )
{
mixer.samples_read = 0;
for ( int i = bufs_size; --i >= 0; )
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
}
void Stereo_Buffer::clock_rate( int rate )
{
for ( int i = bufs_size; --i >= 0; )
bufs [i].clock_rate( rate );
}
void Stereo_Buffer::bass_freq( int bass )
{
for ( int i = bufs_size; --i >= 0; )
bufs [i].bass_freq( bass );
}
void Stereo_Buffer::clear()
{
mixer.samples_read = 0;
for ( int i = bufs_size; --i >= 0; )
bufs [i].clear();
}
void Stereo_Buffer::end_frame( blip_time_t time )
{
for ( int i = bufs_size; --i >= 0; )
bufs [i].end_frame( time );
}
int Stereo_Buffer::read_samples( blip_sample_t out [], int out_size )
{
require( (out_size & 1) == 0 ); // must read an even number of samples
out_size = min( out_size, samples_avail() );
int pair_count = int (out_size >> 1);
if ( pair_count )
{
mixer.read_pairs( out, pair_count );
if ( samples_avail() <= 0 || immediate_removal() )
{
for ( int i = bufs_size; --i >= 0; )
{
buf_t& b = bufs [i];
// TODO: might miss non-silence settling since it checks END of last read
if ( !b.non_silent() )
b.remove_silence( mixer.samples_read );
else
b.remove_samples( mixer.samples_read );
}
mixer.samples_read = 0;
}
}
return out_size;
}
// Stereo_Mixer
// mixers use a single index value to improve performance on register-challenged processors
// offset goes from negative to zero
void Stereo_Mixer::read_pairs( blip_sample_t out [], int count )
{
// TODO: if caller never marks buffers as modified, uses mono
// except that buffer isn't cleared, so caller can encounter
// subtle problems and not realize the cause.
samples_read += count;
if ( bufs [0]->non_silent() | bufs [1]->non_silent() )
mix_stereo( out, count );
else
mix_mono( out, count );
}
void Stereo_Mixer::mix_mono( blip_sample_t out_ [], int count )
{
int const bass = bufs [2]->highpass_shift();
Blip_Buffer::delta_t const* center = bufs [2]->read_pos() + samples_read;
int center_sum = bufs [2]->integrator();
typedef blip_sample_t stereo_blip_sample_t [stereo];
stereo_blip_sample_t* BLARGG_RESTRICT out = (stereo_blip_sample_t*) out_ + count;
int offset = -count;
do
{
int s = center_sum >> bufs [2]->delta_bits;
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

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

View File

@ -0,0 +1,244 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Music_Emu.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
int const stereo = 2; // number of channels for stereo
Music_Emu::equalizer_t const Music_Emu::tv_eq = { -8.0, 180, 0,0,0,0,0,0,0,0 };
void Music_Emu::clear_track_vars()
{
current_track_ = -1;
warning(); // clear warning
track_filter.stop();
}
void Music_Emu::unload()
{
voice_count_ = 0;
clear_track_vars();
Gme_File::unload();
}
Music_Emu::gme_t()
{
effects_buffer_ = NULL;
sample_rate_ = 0;
mute_mask_ = 0;
tempo_ = 1.0;
gain_ = 1.0;
fade_set = false;
// defaults
tfilter = track_filter.setup();
set_max_initial_silence( 15 );
set_silence_lookahead( 3 );
ignore_silence( false );
equalizer_.treble = -1.0;
equalizer_.bass = 60;
static const char* const names [] = {
"Voice 1", "Voice 2", "Voice 3", "Voice 4",
"Voice 5", "Voice 6", "Voice 7", "Voice 8"
};
set_voice_names( names );
Music_Emu::unload(); // clears fields
}
Music_Emu::~gme_t()
{
assert( !effects_buffer_ );
}
blargg_err_t Music_Emu::set_sample_rate( int rate )
{
require( !sample_rate() ); // sample rate can't be changed once set
RETURN_ERR( set_sample_rate_( rate ) );
RETURN_ERR( track_filter.init( this ) );
sample_rate_ = rate;
tfilter.max_silence = 6 * stereo * sample_rate();
return blargg_ok;
}
void Music_Emu::pre_load()
{
require( sample_rate() ); // set_sample_rate() must be called before loading a file
Gme_File::pre_load();
}
void Music_Emu::set_equalizer( equalizer_t const& eq )
{
// TODO: why is GCC generating memcpy call here?
// Without the 'if', valgrind flags it.
if ( &eq != &equalizer_ )
equalizer_ = eq;
set_equalizer_( eq );
}
void Music_Emu::mute_voice( int index, bool mute )
{
require( (unsigned) index < (unsigned) voice_count() );
int bit = 1 << index;
int mask = mute_mask_ | bit;
if ( !mute )
mask ^= bit;
mute_voices( mask );
}
void Music_Emu::mute_voices( int mask )
{
require( sample_rate() ); // sample rate must be set first
mute_mask_ = mask;
mute_voices_( mask );
}
const char* Music_Emu::voice_name( int i ) const
{
if ( (unsigned) i < (unsigned) voice_count_ )
return voice_names_ [i];
//check( false ); // TODO: enable?
return "";
}
void Music_Emu::set_tempo( double t )
{
require( sample_rate() ); // sample rate must be set first
double const min = 0.02;
double const max = 4.00;
if ( t < min ) t = min;
if ( t > max ) t = max;
tempo_ = t;
set_tempo_( t );
}
blargg_err_t Music_Emu::post_load()
{
set_tempo( tempo_ );
remute_voices();
return Gme_File::post_load();
}
// Tell/Seek
int Music_Emu::msec_to_samples( int msec ) const
{
int sec = msec / 1000;
msec -= sec * 1000;
return (sec * sample_rate() + msec * sample_rate() / 1000) * stereo;
}
int Music_Emu::tell() const
{
int rate = sample_rate() * stereo;
int sec = track_filter.sample_count() / rate;
return sec * 1000 + (track_filter.sample_count() - sec * rate) * 1000 / rate;
}
blargg_err_t Music_Emu::seek( int msec )
{
int time = msec_to_samples( msec );
if ( time < track_filter.sample_count() )
{
RETURN_ERR( start_track( current_track_ ) );
if ( fade_set )
set_fade( length_msec, fade_msec );
}
return skip( time - track_filter.sample_count() );
}
blargg_err_t Music_Emu::skip( int count )
{
require( current_track() >= 0 ); // start_track() must have been called already
return track_filter.skip( count );
}
blargg_err_t Music_Emu::skip_( int count )
{
// for long skip, mute sound
const int threshold = 32768;
if ( count > threshold )
{
int saved_mute = mute_mask_;
mute_voices( ~0 );
int n = count - threshold/2;
n &= ~(2048-1); // round to multiple of 2048
count -= n;
RETURN_ERR( track_filter.skip_( n ) );
mute_voices( saved_mute );
}
return track_filter.skip_( count );
}
// Playback
blargg_err_t Music_Emu::start_track( int track )
{
clear_track_vars();
int remapped = track;
RETURN_ERR( remap_track_( &remapped ) );
current_track_ = track;
blargg_err_t err = start_track_( remapped );
if ( err )
{
current_track_ = -1;
return err;
}
// convert filter times to samples
Track_Filter::setup_t s = tfilter;
s.max_initial *= sample_rate() * stereo;
#if GME_DISABLE_SILENCE_LOOKAHEAD
s.lookahead = 1;
#endif
track_filter.setup( s );
return track_filter.start_track();
}
void Music_Emu::set_fade( int start_msec, int length_msec )
{
fade_set = true;
this->length_msec = start_msec;
this->fade_msec = length_msec;
track_filter.set_fade( start_msec < 0 ? Track_Filter::indefinite_count : msec_to_samples( start_msec ),
length_msec * sample_rate() / (1000 / stereo) );
}
blargg_err_t Music_Emu::play( int out_count, sample_t out [] )
{
require( current_track() >= 0 );
require( out_count % stereo == 0 );
return track_filter.play( out_count, out );
}
// Gme_Info_
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
void Gme_Info_::set_equalizer_( equalizer_t const& ){ 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 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" ); }

View File

@ -0,0 +1,280 @@
// Common interface to game music file emulators
// Game_Music_Emu $vers
#ifndef MUSIC_EMU_H
#define MUSIC_EMU_H
#include "Gme_File.h"
#include "Track_Filter.h"
#include "blargg_errors.h"
class Multi_Buffer;
struct gme_t : public Gme_File, private Track_Filter::callbacks_t {
public:
// Sets output sample rate. Must be called only once before loading file.
blargg_err_t set_sample_rate( int sample_rate );
// Sample rate sound is generated at
int sample_rate() const;
// File loading
// See Gme_Loader.h
// Basic playback
// Starts a track, where 0 is the first track. Also clears warning string.
blargg_err_t start_track( int );
// Generates 'count' samples info 'buf'. Output is in stereo. Any emulation
// errors set warning string, and major errors also end track.
typedef short sample_t;
blargg_err_t play( int count, sample_t* buf );
// Track information
// See Gme_File.h
// Index of current track or -1 if one hasn't been started
int current_track() const;
// Info for currently playing track
using Gme_File::track_info;
blargg_err_t track_info( track_info_t* out ) const;
blargg_err_t set_track_info( const track_info_t* in );
blargg_err_t set_track_info( const track_info_t* in, int track_number );
struct Hash_Function
{
virtual void hash_( byte const* data, size_t size ) BLARGG_PURE( ; )
};
virtual blargg_err_t hash_( Hash_Function& ) const BLARGG_PURE( ; )
blargg_err_t save( gme_writer_t writer, void* your_data) const;
// Track status/control
// Number of milliseconds played since beginning of track (1000 per second)
int tell() const;
// Seeks to new time in track. Seeking backwards or far forward can take a while.
blargg_err_t seek( int msec );
// Skips n samples
blargg_err_t skip( int n );
// True if a track has reached its end
bool track_ended() const;
// 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
// at any time.
void set_fade( int start_msec, int length_msec = 8000 );
// Disables automatic end-of-track detection and skipping of silence at beginning
void ignore_silence( bool disable = true );
// Voices
// Number of voices used by currently loaded file
int voice_count() const;
// Name of voice i, from 0 to voice_count()-1
const char* voice_name( int i ) const;
// Mutes/unmutes voice i, where voice 0 is first voice
void mute_voice( int index, bool mute = true );
// Sets muting state of all voices at once using a bit mask, where -1 mutes them all,
// 0 unmutes them all, 0x01 mutes just the first voice, etc.
void mute_voices( int mask );
// Sound customization
// Adjusts 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.
void set_tempo( double );
// Changes overall output amplitude, where 1.0 results in minimal clamping.
// Must be called before set_sample_rate().
void set_gain( double );
// 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().
virtual void set_buffer( class Multi_Buffer* ) { }
// Sound equalization (treble/bass)
// Frequency equalizer parameters (see gme.txt)
// See gme.h for definition of struct gme_equalizer_t.
typedef gme_equalizer_t equalizer_t;
// Current frequency equalizater parameters
equalizer_t const& equalizer() const;
// Sets frequency equalizer parameters
void set_equalizer( equalizer_t const& );
// Equalizer preset for a TV speaker
static equalizer_t const tv_eq;
// Derived interface
protected:
// Cause any further generated samples to be silence, instead of calling play_()
void set_track_ended() { track_filter.set_track_ended(); }
// If more than secs of silence are encountered, track is ended
void set_max_initial_silence( int secs ) { tfilter.max_initial = secs; }
// Sets rate emulator is run at when scanning ahead for silence. 1=100%, 2=200% etc.
void set_silence_lookahead( int rate ) { tfilter.lookahead = rate; }
// Sets number of voices
void set_voice_count( int n ) { voice_count_ = n; }
// Sets names of voices
void set_voice_names( const char* const names [] );
// Current gain
double gain() const { return gain_; }
// Current tempo
double tempo() const { return tempo_; }
// Re-applies muting mask using mute_voices_()
void remute_voices();
// Overrides should do the indicated task
// Set sample rate as close as possible to sample_rate, then call
// Music_Emu::set_sample_rate_() with the actual rate used.
virtual blargg_err_t set_sample_rate_( int sample_rate ) BLARGG_PURE( ; )
// Set equalizer parameters
virtual void set_equalizer_( equalizer_t const& ) { }
// Mute voices based on mask
virtual void mute_voices_( int mask ) BLARGG_PURE( ; )
// Set tempo to t, which is constrained to the range 0.02 to 4.0.
virtual void set_tempo_( double t ) BLARGG_PURE( ; )
// Start track t, where 0 is the first track
virtual blargg_err_t start_track_( int t ) BLARGG_PURE( ; ) // tempo is set before this
// Generate count samples into *out. Count will always be even.
virtual blargg_err_t play_( int count, sample_t out [] ) BLARGG_PURE( ; )
// Skip count samples. Count will always be even.
virtual blargg_err_t skip_( int count );
// Save current state of file to specified writer.
virtual blargg_err_t save_( gme_writer_t, void* ) const { return "Not supported by this format"; }
// Set track info
virtual blargg_err_t set_track_info_( const track_info_t*, int ) { return "Not supported by this format"; }
// Implementation
public:
gme_t();
~gme_t();
BLARGG_DEPRECATED( const char** voice_names() const { return CONST_CAST(const char**,voice_names_); } )
protected:
virtual void unload();
virtual void pre_load();
virtual blargg_err_t post_load();
private:
Track_Filter::setup_t tfilter;
Track_Filter track_filter;
equalizer_t equalizer_;
const char* const* voice_names_;
int voice_count_;
int mute_mask_;
double tempo_;
double gain_;
int sample_rate_;
int current_track_;
bool fade_set;
int length_msec;
int fade_msec;
void clear_track_vars();
int msec_to_samples( int msec ) const;
friend Music_Emu* gme_new_emu( gme_type_t, int );
friend void gme_effects( Music_Emu const*, gme_effects_t* );
friend void gme_set_effects( Music_Emu*, gme_effects_t const* );
friend void gme_set_stereo_depth( Music_Emu*, double );
friend const char** gme_voice_names ( Music_Emu const* );
protected:
Multi_Buffer* effects_buffer_;
};
// base class for info-only derivations
struct Gme_Info_ : Music_Emu
{
virtual blargg_err_t set_sample_rate_( int sample_rate );
virtual void set_equalizer_( equalizer_t const& );
virtual void mute_voices_( int mask );
virtual void set_tempo_( double );
virtual blargg_err_t start_track_( int );
virtual blargg_err_t play_( int count, sample_t out [] );
virtual void pre_load();
virtual blargg_err_t post_load();
};
inline blargg_err_t Music_Emu::track_info( track_info_t* out ) const
{
return track_info( out, current_track_ );
}
inline blargg_err_t Music_Emu::save(gme_writer_t writer, void *your_data) const
{
return save_( writer, your_data );
}
inline blargg_err_t Music_Emu::set_track_info(const track_info_t *in)
{
return set_track_info_( in, current_track_ );
}
inline blargg_err_t Music_Emu::set_track_info(const track_info_t *in, int track)
{
return set_track_info_( in, track );
}
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

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,152 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Namco_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
Nes_Namco_Apu::Nes_Namco_Apu()
{
set_output( NULL );
volume( 1.0 );
reset();
}
void Nes_Namco_Apu::reset()
{
last_time = 0;
addr_reg = 0;
int i;
for ( i = 0; i < reg_count; i++ )
reg [i] = 0;
for ( i = 0; i < osc_count; i++ )
{
Namco_Osc& osc = oscs [i];
osc.delay = 0;
osc.last_amp = 0;
osc.wave_pos = 0;
}
}
void Nes_Namco_Apu::set_output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; ++i )
set_output( i, buf );
}
/*
void Nes_Namco_Apu::reflect_state( Tagged_Data& data )
{
reflect_int16( data, BLARGG_4CHAR('A','D','D','R'), &addr_reg );
static const char hex [17] = "0123456789ABCDEF";
int 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++ )
{
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 )
{
if ( time > last_time )
run_until( time );
assert( last_time >= time );
last_time -= 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++ )
{
Namco_Osc& osc = oscs [i];
Blip_Buffer* output = osc.output;
if ( !output )
continue;
blip_resampled_time_t time =
output->resampled_time( last_time ) + osc.delay;
blip_resampled_time_t end_time = output->resampled_time( nes_end_time );
osc.delay = 0;
if ( time < end_time )
{
const BOOST::uint8_t* osc_reg = &reg [i * 8 + 0x40];
if ( !(osc_reg [4] & 0xE0) )
continue;
int volume = osc_reg [7] & 15;
if ( !volume )
continue;
int freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100 + osc_reg [0];
if ( freq < 64 * active_oscs )
continue; // prevent low frequencies from excessively delaying freq changes
int const master_clock_divider = 12; // NES time derived via divider of master clock
int const n106_divider = 45; // N106 then divides master clock by this
int const max_freq = 0x3FFFF;
int const lowest_freq_period = (max_freq + 1) * n106_divider / master_clock_divider;
// divide by 8 to avoid overflow
blip_resampled_time_t period =
output->resampled_duration( lowest_freq_period / 8 ) / freq * 8 * active_oscs;
int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4;
if ( !wave_size )
continue;
int last_amp = osc.last_amp;
int wave_pos = osc.wave_pos;
output->set_modified();
do
{
// read wave sample
int addr = wave_pos + osc_reg [6];
int sample = reg [addr >> 1] >> (addr << 2 & 4);
wave_pos++;
sample = (sample & 15) * volume;
// output impulse if amplitude changed
int delta = sample - last_amp;
if ( delta )
{
last_amp = sample;
synth.offset_resampled( time, delta, output );
}
// next sample
time += period;
if ( wave_pos >= wave_size )
wave_pos = 0;
}
while ( time < end_time );
osc.wave_pos = wave_pos;
osc.last_amp = last_amp;
}
osc.delay = time - end_time;
}
last_time = nes_end_time;
}

View File

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

View File

@ -0,0 +1,578 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/
#include "Nes_Apu.h"
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h"
// Nes_Osc
void Nes_Osc::clock_length( int halt_mask )
{
if ( length_counter && !(regs [0] & halt_mask) )
length_counter--;
}
void Nes_Envelope::clock_envelope()
{
int period = regs [0] & 15;
if ( reg_written [3] )
{
reg_written [3] = false;
env_delay = period;
envelope = 15;
}
else if ( --env_delay < 0 )
{
env_delay = period;
if ( envelope | (regs [0] & 0x20) )
envelope = (envelope - 1) & 15;
}
}
int Nes_Envelope::volume() const
{
return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope;
}
// Nes_Square
void Nes_Square::clock_sweep( int negative_adjust )
{
int sweep = regs [1];
if ( --sweep_delay < 0 )
{
reg_written [1] = true;
int period = this->period();
int shift = sweep & shift_mask;
if ( shift && (sweep & 0x80) && period >= 8 )
{
int offset = period >> shift;
if ( sweep & negate_flag )
offset = negative_adjust - offset;
if ( period + offset < 0x800 )
{
period += offset;
// rewrite period
regs [2] = period & 0xFF;
regs [3] = (regs [3] & ~7) | ((period >> 8) & 7);
}
}
}
if ( reg_written [1] )
{
reg_written [1] = false;
sweep_delay = (sweep >> 4) & 7;
}
}
// TODO: clean up
inline Nes_Square::nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period )
{
nes_time_t remain = end_time - time;
if ( remain > 0 )
{
int count = (remain + timer_period - 1) / timer_period;
phase = (phase + count) & (phase_range - 1);
time += count * timer_period;
}
return time;
}
void Nes_Square::run( nes_time_t time, nes_time_t end_time )
{
const int period = this->period();
const int timer_period = (period + 1) * 2;
if ( !output )
{
delay = maintain_phase( time + delay, end_time, timer_period ) - end_time;
return;
}
int offset = period >> (regs [1] & shift_mask);
if ( regs [1] & negate_flag )
offset = 0;
const int volume = this->volume();
if ( volume == 0 || period < 8 || (period + offset) >= 0x800 )
{
if ( last_amp )
{
output->set_modified();
synth.offset( time, -last_amp, output );
last_amp = 0;
}
time += delay;
time = maintain_phase( time, end_time, timer_period );
}
else
{
// handle duty select
int duty_select = (regs [0] >> 6) & 3;
int duty = 1 << duty_select; // 1, 2, 4, 2
int amp = 0;
if ( duty_select == 3 )
{
duty = 2; // negated 25%
amp = volume;
}
if ( phase < duty )
amp ^= volume;
output->set_modified();
{
int delta = update_amp( amp );
if ( delta )
synth.offset( time, delta, output );
}
time += delay;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
const Synth& synth = this->synth;
int delta = amp * 2 - volume;
int phase = this->phase;
do
{
phase = (phase + 1) & (phase_range - 1);
if ( phase == 0 || phase == duty )
{
delta = -delta;
synth.offset_inline( time, delta, output );
}
time += timer_period;
}
while ( time < end_time );
last_amp = (delta + volume) >> 1;
this->phase = phase;
}
}
delay = time - end_time;
}
// Nes_Triangle
void Nes_Triangle::clock_linear_counter()
{
if ( reg_written [3] )
linear_counter = regs [0] & 0x7F;
else if ( linear_counter )
linear_counter--;
if ( !(regs [0] & 0x80) )
reg_written [3] = false;
}
inline int Nes_Triangle::calc_amp() const
{
int amp = phase_range - phase;
if ( amp < 0 )
amp = phase - (phase_range + 1);
return amp;
}
// TODO: clean up
inline Nes_Square::nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period )
{
nes_time_t remain = end_time - time;
if ( remain > 0 )
{
int count = (remain + timer_period - 1) / timer_period;
phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1);
phase++;
time += count * timer_period;
}
return time;
}
void Nes_Triangle::run( nes_time_t time, nes_time_t end_time )
{
const int timer_period = period() + 1;
if ( !output )
{
time += delay;
delay = 0;
if ( length_counter && linear_counter && timer_period >= 3 )
delay = maintain_phase( time, end_time, timer_period ) - end_time;
return;
}
// to do: track phase when period < 3
// to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks.
int delta = update_amp( calc_amp() );
if ( delta )
{
output->set_modified();
synth.offset( time, delta, output );
}
time += delay;
if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 )
{
time = end_time;
}
else if ( time < end_time )
{
Blip_Buffer* const output = this->output;
int phase = this->phase;
int volume = 1;
if ( phase > phase_range )
{
phase -= phase_range;
volume = -volume;
}
output->set_modified();
do
{
if ( --phase == 0 )
{
phase = phase_range;
volume = -volume;
}
else
{
synth.offset_inline( time, volume, output );
}
time += timer_period;
}
while ( time < end_time );
if ( volume < 0 )
phase += phase_range;
this->phase = phase;
last_amp = calc_amp();
}
delay = time - end_time;
}
// Nes_Dmc
void Nes_Dmc::reset()
{
address = 0;
dac = 0;
buf = 0;
bits_remain = 1;
bits = 0;
buf_full = false;
silence = true;
next_irq = Nes_Apu::no_irq;
irq_flag = false;
irq_enabled = false;
Nes_Osc::reset();
period = 0x1AC;
}
void Nes_Dmc::recalc_irq()
{
nes_time_t irq = Nes_Apu::no_irq;
if ( irq_enabled && length_counter )
irq = apu->last_dmc_time + delay +
((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1;
if ( irq != next_irq )
{
next_irq = irq;
apu->irq_changed();
}
}
int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const
{
if ( last_read )
*last_read = time;
if ( length_counter == 0 )
return 0; // not reading
nes_time_t first_read = next_read_time();
nes_time_t avail = time - first_read;
if ( avail <= 0 )
return 0;
int count = (avail - 1) / (period * 8) + 1;
if ( !(regs [0] & loop_flag) && count > length_counter )
count = length_counter;
if ( last_read )
{
*last_read = first_read + (count - 1) * (period * 8) + 1;
check( *last_read <= time );
check( count == count_reads( *last_read, NULL ) );
check( count - 1 == count_reads( *last_read - 1, NULL ) );
}
return count;
}
static short const dmc_period_table [2] [16] = {
{428, 380, 340, 320, 286, 254, 226, 214, // NTSC
190, 160, 142, 128, 106, 84, 72, 54},
{398, 354, 316, 298, 276, 236, 210, 198, // PAL
176, 148, 132, 118, 98, 78, 66, 50}
};
inline void Nes_Dmc::reload_sample()
{
address = 0x4000 + regs [2] * 0x40;
length_counter = regs [3] * 0x10 + 1;
}
static int const dmc_table [128] =
{
0, 24, 48, 71, 94, 118, 141, 163, 186, 209, 231, 253, 275, 297, 319, 340,
361, 383, 404, 425, 445, 466, 486, 507, 527, 547, 567, 587, 606, 626, 645, 664,
683, 702, 721, 740, 758, 777, 795, 813, 832, 850, 867, 885, 903, 920, 938, 955,
972, 989,1006,1023,1040,1056,1073,1089,1105,1122,1138,1154,1170,1185,1201,1217,
1232,1248,1263,1278,1293,1308,1323,1338,1353,1368,1382,1397,1411,1425,1440,1454,
1468,1482,1496,1510,1523,1537,1551,1564,1578,1591,1604,1618,1631,1644,1657,1670,
1683,1695,1708,1721,1733,1746,1758,1771,1783,1795,1807,1819,1831,1843,1855,1867,
1879,1890,1902,1914,1925,1937,1948,1959,1971,1982,1993,2004,2015,2026,2037,2048,
};
inline int Nes_Dmc::update_amp_nonlinear( int in )
{
if ( !nonlinear )
in = dmc_table [in];
int delta = in - last_amp;
last_amp = in;
return delta;
}
void Nes_Dmc::write_register( int addr, int data )
{
if ( addr == 0 )
{
period = dmc_period_table [pal_mode] [data & 15];
irq_enabled = (data & 0xC0) == 0x80; // enabled only if loop disabled
irq_flag &= irq_enabled;
recalc_irq();
}
else if ( addr == 1 )
{
dac = data & 0x7F;
}
}
void Nes_Dmc::start()
{
reload_sample();
fill_buffer();
recalc_irq();
}
void Nes_Dmc::fill_buffer()
{
if ( !buf_full && length_counter )
{
require( apu->dmc_reader.f ); // dmc_reader must be set
buf = apu->dmc_reader.f( apu->dmc_reader.data, 0x8000u + address );
address = (address + 1) & 0x7FFF;
buf_full = true;
if ( --length_counter == 0 )
{
if ( regs [0] & loop_flag )
{
reload_sample();
}
else
{
apu->osc_enables &= ~0x10;
irq_flag = irq_enabled;
next_irq = Nes_Apu::no_irq;
apu->irq_changed();
}
}
}
}
void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
{
int delta = update_amp_nonlinear( dac );
if ( !output )
{
silence = true;
}
else if ( delta )
{
output->set_modified();
synth.offset( time, delta, output );
}
time += delay;
if ( time < end_time )
{
int bits_remain = this->bits_remain;
if ( silence && !buf_full )
{
int count = (end_time - time + period - 1) / period;
bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1;
time += count * period;
}
else
{
Blip_Buffer* const output = this->output;
const int period = this->period;
int bits = this->bits;
int dac = this->dac;
if ( output )
output->set_modified();
do
{
if ( !silence )
{
int step = (bits & 1) * 4 - 2;
bits >>= 1;
if ( unsigned (dac + step) <= 0x7F )
{
dac += step;
synth.offset_inline( time, update_amp_nonlinear( dac ), output );
}
}
time += period;
if ( --bits_remain == 0 )
{
bits_remain = 8;
if ( !buf_full )
{
silence = true;
}
else
{
silence = false;
bits = buf;
buf_full = false;
if ( !output )
silence = true;
fill_buffer();
}
}
}
while ( time < end_time );
this->dac = dac;
this->bits = bits;
}
this->bits_remain = bits_remain;
}
delay = time - end_time;
}
// Nes_Noise
static short const noise_period_table [16] = {
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
};
void Nes_Noise::run( nes_time_t time, nes_time_t end_time )
{
int period = noise_period_table [regs [2] & 15];
if ( !output )
{
// TODO: clean up
time += delay;
delay = time + (end_time - time + period - 1) / period * period - end_time;
return;
}
const int volume = this->volume();
int amp = (noise & 1) ? volume : 0;
{
int delta = update_amp( amp );
if ( delta )
{
output->set_modified();
synth.offset( time, delta, output );
}
}
time += delay;
if ( time < end_time )
{
const int mode_flag = 0x80;
if ( !volume )
{
// round to next multiple of period
time += (end_time - time + period - 1) / period * period;
// approximate noise cycling while muted, by shuffling up noise register
// to do: precise muted noise cycling?
if ( !(regs [2] & mode_flag) )
{
int feedback = (noise << 13) ^ (noise << 14);
noise = (feedback & 0x4000) | (noise >> 1);
}
}
else
{
Blip_Buffer* const output = this->output;
// using resampled time avoids conversion in synth.offset()
blip_resampled_time_t rperiod = output->resampled_duration( period );
blip_resampled_time_t rtime = output->resampled_time( time );
int noise = this->noise;
int delta = amp * 2 - volume;
const int tap = (regs [2] & mode_flag ? 8 : 13);
output->set_modified();
do
{
int feedback = (noise << tap) ^ (noise << 14);
time += period;
if ( (noise + 1) & 2 )
{
// bits 0 and 1 of noise differ
delta = -delta;
synth.offset_resampled( rtime, delta, output );
}
rtime += rperiod;
noise = (feedback & 0x4000) | (noise >> 1);
}
while ( time < end_time );
last_amp = (delta + volume) >> 1;
this->noise = noise;
}
}
delay = time - end_time;
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,206 @@
#include "Nes_Vrc7_Apu.h"
#include "ym2413.h"
#include <string.h>
#include "blargg_source.h"
int const period = 36; // NES CPU clocks per FM clock
Nes_Vrc7_Apu::Nes_Vrc7_Apu()
{
opll = 0;
}
blargg_err_t Nes_Vrc7_Apu::init()
{
CHECK_ALLOC( opll = ym2413_init( 3579545, 3579545 / 72, 1 ) );
set_output( 0 );
volume( 1.0 );
reset();
return 0;
}
Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
{
if ( opll )
ym2413_shutdown( opll );
}
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; ++i )
oscs [i].output = buf;
output_changed();
}
void Nes_Vrc7_Apu::output_changed()
{
mono.output = oscs [0].output;
for ( int i = osc_count; --i; )
{
if ( mono.output != oscs [i].output )
{
mono.output = 0;
break;
}
}
if ( mono.output )
{
for ( int i = osc_count; --i; )
{
mono.last_amp += oscs [i].last_amp;
oscs [i].last_amp = 0;
}
}
}
void Nes_Vrc7_Apu::reset()
{
addr = 0;
next_time = 0;
mono.last_amp = 0;
for ( int i = osc_count; --i >= 0; )
{
Vrc7_Osc& osc = oscs [i];
osc.last_amp = 0;
for ( int j = 0; j < 3; ++j )
osc.regs [j] = 0;
}
ym2413_reset_chip( opll );
}
void Nes_Vrc7_Apu::write_reg( int data )
{
addr = data;
}
void Nes_Vrc7_Apu::write_data( blip_time_t time, int data )
{
int type = (addr >> 4) - 1;
int chan = addr & 15;
if ( (unsigned) type < 3 && chan < osc_count )
oscs [chan].regs [type] = data;
if ( time > next_time )
run_until( time );
ym2413_write( opll, 0, addr );
ym2413_write( opll, 1, data );
}
void Nes_Vrc7_Apu::end_frame( blip_time_t time )
{
if ( time > next_time )
run_until( time );
next_time -= time;
assert( next_time >= 0 );
for ( int i = osc_count; --i >= 0; )
{
Blip_Buffer* output = oscs [i].output;
if ( output )
output->set_modified();
}
}
void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const
{
out->latch = addr;
out->delay = next_time;
for ( int i = osc_count; --i >= 0; )
{
for ( int j = 0; j < 3; ++j )
out->regs [i] [j] = oscs [i].regs [j];
}
memcpy( out->inst, ym2413_get_inst0( opll ), 8 );
}
void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
{
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
reset();
next_time = in.delay;
write_reg( in.latch );
int i;
for ( i = 0; i < osc_count; ++i )
{
for ( int j = 0; j < 3; ++j )
oscs [i].regs [j] = in.regs [i] [j];
}
for ( i = 0; i < 8; ++i )
{
ym2413_write( opll, 0, i );
ym2413_write( opll, 1, in.inst [i] );
}
for ( i = 0; i < 3; ++i )
{
for ( int j = 0; j < 6; ++j )
{
ym2413_write( opll, 0, 0x10 + i * 0x10 + j );
ym2413_write( opll, 1, oscs [j].regs [i] );
}
}
}
void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
{
require( end_time > next_time );
blip_time_t time = next_time;
void* opll = this->opll; // cache
Blip_Buffer* const mono_output = mono.output;
if ( mono_output )
{
// optimal case
do
{
ym2413_advance_lfo( opll );
int amp = 0;
for ( int i = 0; i < osc_count; i++ )
amp += ym2413_calcch( opll, i );
ym2413_advance( opll );
int delta = amp - mono.last_amp;
if ( delta )
{
mono.last_amp = amp;
synth.offset_inline( time, delta, mono_output );
}
time += period;
}
while ( time < end_time );
}
else
{
mono.last_amp = 0;
do
{
ym2413_advance_lfo( opll );
for ( int i = 0; i < osc_count; ++i )
{
Vrc7_Osc& osc = oscs [i];
if ( osc.output )
{
int amp = ym2413_calcch( opll, i );
int delta = amp - osc.last_amp;
if ( delta )
{
osc.last_amp = amp;
synth.offset( time, delta, osc.output );
}
}
}
ym2413_advance( opll );
time += period;
}
while ( time < end_time );
}
next_time = time;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,342 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Nsf_Emu.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_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80, 0,0,0,0,0,0,0,0 };
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 };
Nsf_Emu::Nsf_Emu()
{
set_type( gme_nsf_type );
set_silence_lookahead( 6 );
set_gain( 1.4 );
set_equalizer( nes_eq );
}
Nsf_Emu::~Nsf_Emu()
{
unload();
}
void Nsf_Emu::unload()
{
core_.unload();
Music_Emu::unload();
}
// Track info
static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out )
{
GME_COPY_FIELD( h, out, game );
GME_COPY_FIELD( h, out, author );
GME_COPY_FIELD( h, out, copyright );
if ( h.chip_flags )
Music_Emu::copy_field_( out->system, "Famicom" );
}
void hash_nsf_file( Nsf_Core::header_t const& h, unsigned char const* data, int data_size, Music_Emu::Hash_Function& out )
{
out.hash_( &h.vers, sizeof(h.vers) );
out.hash_( &h.track_count, sizeof(h.track_count) );
out.hash_( &h.first_track, sizeof(h.first_track) );
out.hash_( &h.load_addr[0], sizeof(h.load_addr) );
out.hash_( &h.init_addr[0], sizeof(h.init_addr) );
out.hash_( &h.play_addr[0], sizeof(h.play_addr) );
out.hash_( &h.ntsc_speed[0], sizeof(h.ntsc_speed) );
out.hash_( &h.banks[0], sizeof(h.banks) );
out.hash_( &h.pal_speed[0], sizeof(h.pal_speed) );
out.hash_( &h.speed_flags, sizeof(h.speed_flags) );
out.hash_( &h.chip_flags, sizeof(h.chip_flags) );
out.hash_( &h.unused[0], sizeof(h.unused) );
out.hash_( data, data_size );
}
blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const
{
copy_nsf_fields( header(), out );
return blargg_ok;
}
static blargg_err_t check_nsf_header( Nsf_Emu::header_t const& h )
{
if ( !h.valid_tag() )
return blargg_err_file_type;
return blargg_ok;
}
struct Nsf_File : Gme_Info_
{
Nsf_Emu::header_t const* h;
Nsf_File() { set_type( gme_nsf_type ); }
blargg_err_t load_mem_( byte const begin [], int size )
{
h = ( Nsf_Emu::header_t const* ) begin;
if ( h->vers != 1 )
set_warning( "Unknown file version" );
int unsupported_chips = ~Nsf_Core::chips_mask;
#if NSF_EMU_NO_VRC7
unsupported_chips |= Nsf_Emu::header_t::vrc7_mask;
#endif
if ( h->chip_flags & unsupported_chips )
set_warning( "Uses unsupported audio expansion hardware" );
set_track_count( h->track_count );
return check_nsf_header( *h );
}
blargg_err_t track_info_( track_info_t* out, int ) const
{
copy_nsf_fields( *h, out );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
hash_nsf_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
return blargg_ok;
}
};
static Music_Emu* new_nsf_emu () { return BLARGG_NEW Nsf_Emu ; }
static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; }
gme_type_t_ const gme_nsf_type [1] = {{ "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 }};
// Setup
void Nsf_Emu::set_tempo_( double t )
{
core_.set_tempo( t );
}
void Nsf_Emu::append_voices( const char* const names [], int const types [], int count )
{
assert( voice_count_ + count < max_voices );
for ( int i = 0; i < count; i++ )
{
voice_names_ [voice_count_ + i] = names [i];
voice_types_ [voice_count_ + i] = types [i];
}
voice_count_ += count;
set_voice_count( voice_count_ );
set_voice_types( voice_types_ );
}
blargg_err_t Nsf_Emu::init_sound()
{
voice_count_ = 0;
set_voice_names( voice_names_ );
{
int const count = Nes_Apu::osc_count;
static const char* const names [Nes_Apu::osc_count] = {
"Square 1", "Square 2", "Triangle", "Noise", "DMC"
};
static int const types [count] = {
wave_type+1, wave_type+2, mixed_type+1, noise_type+0, mixed_type+1
};
append_voices( names, types, count );
}
// Make adjusted_gain * 0.75 = 1.0 so usual APU and one sound chip uses 1.0
double adjusted_gain = 1.0 / 0.75 * gain();
#if !NSF_EMU_APU_ONLY
// TODO: order of chips here must match that in set_voice()
if ( core_.vrc6_apu() )
{
int const count = Nes_Vrc6_Apu::osc_count;
static const char* const names [count] = {
"Square 3", "Square 4", "Saw Wave"
};
static int const types [count] = {
wave_type+3, wave_type+4, wave_type+5,
};
append_voices( names, types, count );
adjusted_gain *= 0.75;
}
if ( core_.fme7_apu() )
{
int const count = Nes_Fme7_Apu::osc_count;
static const char* const names [count] = {
"Square 3", "Square 4", "Square 5"
};
static int const types [count] = {
wave_type+3, wave_type+4, wave_type+5,
};
append_voices( names, types, count );
adjusted_gain *= 0.75;
}
if ( core_.mmc5_apu() )
{
int const count = Nes_Mmc5_Apu::osc_count;
static const char* const names [count] = {
"Square 3", "Square 4", "PCM"
};
static int const types [count] = {
wave_type+3, wave_type+4, mixed_type+2
};
append_voices( names, types, count );
adjusted_gain *= 0.75;
}
if ( core_.fds_apu() )
{
int const count = Nes_Fds_Apu::osc_count;
static const char* const names [count] = {
"FM"
};
static int const types [count] = {
wave_type+0
};
append_voices( names, types, count );
adjusted_gain *= 0.75;
}
if ( core_.namco_apu() )
{
int const count = Nes_Namco_Apu::osc_count;
static const char* const names [count] = {
"Wave 1", "Wave 2", "Wave 3", "Wave 4",
"Wave 5", "Wave 6", "Wave 7", "Wave 8"
};
static int const types [count] = {
wave_type+3, wave_type+4, wave_type+5, wave_type+ 6,
wave_type+7, wave_type+8, wave_type+9, wave_type+10,
};
append_voices( names, types, count );
adjusted_gain *= 0.75;
}
if ( core_.vrc7_apu() )
{
int const count = Nes_Vrc7_Apu::osc_count;
static const char* const names [count] = {
"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"
};
static int const types [count] = {
wave_type+3, wave_type+4, wave_type+5, wave_type+6,
wave_type+7, wave_type+8
};
append_voices( names, types, count );
adjusted_gain *= 0.75;
}
if ( core_.vrc7_apu() ) core_.vrc7_apu() ->volume( adjusted_gain );
if ( core_.namco_apu() ) core_.namco_apu()->volume( adjusted_gain );
if ( core_.vrc6_apu() ) core_.vrc6_apu() ->volume( adjusted_gain );
if ( core_.fme7_apu() ) core_.fme7_apu() ->volume( adjusted_gain );
if ( core_.mmc5_apu() ) core_.mmc5_apu() ->volume( adjusted_gain );
if ( core_.fds_apu() ) core_.fds_apu() ->volume( adjusted_gain );
#endif
if ( adjusted_gain > gain() )
adjusted_gain = gain(); // only occurs if no other sound chips
core_.nes_apu()->volume( adjusted_gain );
return blargg_ok;
}
blargg_err_t Nsf_Emu::load_( Data_Reader& in )
{
RETURN_ERR( core_.load( in ) );
set_track_count( header().track_count );
RETURN_ERR( check_nsf_header( header() ) );
set_warning( core_.warning() );
RETURN_ERR( init_sound() );
set_tempo( tempo() );
return setup_buffer( (int) (header().clock_rate() + 0.5) );
}
void Nsf_Emu::update_eq( blip_eq_t const& eq )
{
core_.nes_apu()->treble_eq( eq );
#if !NSF_EMU_APU_ONLY
{
if ( core_.namco_apu() ) core_.namco_apu()->treble_eq( eq );
if ( core_.vrc6_apu() ) core_.vrc6_apu() ->treble_eq( eq );
if ( core_.fme7_apu() ) core_.fme7_apu() ->treble_eq( eq );
if ( core_.mmc5_apu() ) core_.mmc5_apu() ->treble_eq( eq );
if ( core_.fds_apu() ) core_.fds_apu() ->treble_eq( eq );
if ( core_.vrc7_apu() ) core_.vrc7_apu() ->treble_eq( eq );
}
#endif
}
void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
{
#define HANDLE_CHIP( chip ) \
if ( chip && (i -= chip->osc_count) < 0 )\
{\
chip->set_output( i + chip->osc_count, buf );\
return;\
}\
HANDLE_CHIP( core_.nes_apu() );
#if !NSF_EMU_APU_ONLY
{
// TODO: order of chips here must match that in init_sound()
HANDLE_CHIP( core_.vrc6_apu() );
HANDLE_CHIP( core_.fme7_apu() );
HANDLE_CHIP( core_.mmc5_apu() );
HANDLE_CHIP( core_.fds_apu() );
HANDLE_CHIP( core_.namco_apu() );
HANDLE_CHIP( core_.vrc7_apu() );
}
#endif
}
blargg_err_t Nsf_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
return core_.start_track( track );
}
blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
{
core_.end_frame( duration );
const char* w = core_.warning();
if ( w )
set_warning( w );
return blargg_ok;
}
blargg_err_t Nsf_Emu::hash_( Hash_Function& out ) const
{
hash_nsf_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
return blargg_ok;
}

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