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

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

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
// $package. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Ay_Apu.h" #include "Ay_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -22,7 +22,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
// Tones above this frequency are treated as disabled tone at half volume. // Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division). // Power of two is more efficient (avoids division).
int const inaudible_freq = 16384; unsigned const inaudible_freq = 16384;
int const period_factor = 16; int const period_factor = 16;
@ -67,18 +67,12 @@ static byte const modes [8] =
MODE( 0,1, 0,0, 0,0 ), 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() Ay_Apu::Ay_Apu()
{ {
// build full table of the upper 8 envelope waveforms // build full table of the upper 8 envelope waveforms
for ( int m = 8; m--; ) for ( int m = 8; m--; )
{ {
byte* out = env_modes [m]; byte* out = env.modes [m];
int flags = modes [m]; int flags = modes [m];
for ( int x = 3; --x >= 0; ) for ( int x = 3; --x >= 0; )
{ {
@ -95,27 +89,27 @@ Ay_Apu::Ay_Apu()
} }
} }
type_ = Ay8910; output( 0 );
set_output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
void Ay_Apu::reset() void Ay_Apu::reset()
{ {
addr_ = 0;
last_time = 0; last_time = 0;
noise_delay = 0; noise.delay = 0;
noise_lfsr = 1; noise.lfsr = 1;
for ( osc_t* osc = &oscs [osc_count]; osc != oscs; ) osc_t* osc = &oscs [osc_count];
do
{ {
osc--; osc--;
osc->period = period_factor; osc->period = period_factor;
osc->delay = 0; osc->delay = 0;
osc->last_amp = 0; osc->last_amp = 0;
osc->phase = 0; osc->phase = 0;
} }
while ( osc != oscs );
for ( int i = sizeof regs; --i >= 0; ) for ( int i = sizeof regs; --i >= 0; )
regs [i] = 0; regs [i] = 0;
@ -123,31 +117,25 @@ void Ay_Apu::reset()
write_data_( 13, 0 ); 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 ) void Ay_Apu::write_data_( int addr, int data )
{ {
assert( (unsigned) addr < reg_count ); assert( (unsigned) addr < reg_count );
if ( (unsigned) addr >= 14 ) if ( (unsigned) addr >= 14 )
dprintf( "Wrote to I/O port %02X\n", (int) addr ); {
#ifdef debug_printf
debug_printf( "Wrote to I/O port %02X\n", (int) addr );
#endif
}
// envelope mode // envelope mode
if ( addr == 13 ) if ( addr == 13 )
{ {
if ( !(data & 8) ) // convert modes 0-7 to proper equivalents if ( !(data & 8) ) // convert modes 0-7 to proper equivalents
data = (data & 4) ? 15 : 9; data = (data & 4) ? 15 : 9;
env_wave = env_modes [data - 7]; env.wave = env.modes [data - 7];
env_pos = -48; env.pos = -48;
env_delay = 0; // will get set to envelope period in run_until() env.delay = 0; // will get set to envelope period in run_until()
} }
regs [addr] = data; regs [addr] = data;
@ -155,7 +143,7 @@ void Ay_Apu::write_data_( int addr, int data )
int i = addr >> 1; int i = addr >> 1;
if ( i < osc_count ) if ( i < osc_count )
{ {
blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100 * period_factor) + blip_time_t period = (regs [i * 2 + 1] & 0x0F) * (0x100L * period_factor) +
regs [i * 2] * period_factor; regs [i * 2] * period_factor;
if ( !period ) if ( !period )
period = period_factor; period = period_factor;
@ -182,23 +170,22 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor; blip_time_t noise_period = (regs [6] & 0x1F) * noise_period_factor;
if ( !noise_period ) if ( !noise_period )
noise_period = noise_period_factor; noise_period = noise_period_factor;
blip_time_t const old_noise_delay = noise_delay; blip_time_t const old_noise_delay = noise.delay;
unsigned const old_noise_lfsr = noise_lfsr; blargg_ulong const old_noise_lfsr = noise.lfsr;
// envelope period // envelope period
int env_step_scale = ((type_ & 0xF0) == 0x00) ? 1 : 0; blip_time_t const env_period_factor = period_factor * 2; // verified
blip_time_t const env_period_factor = period_factor << env_step_scale; // verified blip_time_t env_period = (regs [12] * 0x100L + regs [11]) * env_period_factor;
blip_time_t env_period = (regs [12] * 0x100 + regs [11]) * env_period_factor;
if ( !env_period ) if ( !env_period )
env_period = env_period_factor; // same as period 1 on my AY chip env_period = env_period_factor; // same as period 1 on my AY chip
if ( !env_delay ) if ( !env.delay )
env_delay = env_period; env.delay = env_period;
// run each osc separately // run each osc separately
for ( int index = 0; index < osc_count; index++ ) for ( int index = 0; index < osc_count; index++ )
{ {
osc_t* const osc = &oscs [index]; osc_t* const osc = &oscs [index];
int osc_mode = regs [7] >> index; int osc_mode = regs [7] >> index;
// output // output
Blip_Buffer* const osc_output = osc->output; Blip_Buffer* const osc_output = osc->output;
@ -208,8 +195,8 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
// period // period
int half_vol = 0; int half_vol = 0;
blip_time_t inaudible_period = (unsigned) (osc_output->clock_rate() + blip_time_t inaudible_period = (blargg_ulong) (osc_output->clock_rate() +
inaudible_freq) / (unsigned) (inaudible_freq * 2); inaudible_freq) / (inaudible_freq * 2);
if ( osc->period <= inaudible_period && !(osc_mode & tone_off) ) if ( osc->period <= inaudible_period && !(osc_mode & tone_off) )
{ {
half_vol = 1; // Actually around 60%, but 50% is close enough half_vol = 1; // Actually around 60%, but 50% is close enough
@ -219,23 +206,21 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
// envelope // envelope
blip_time_t start_time = last_time; blip_time_t start_time = last_time;
blip_time_t end_time = final_end_time; blip_time_t end_time = final_end_time;
int const vol_mode = regs [0x08 + index]; int const vol_mode = regs [0x08 + index];
int const vol_mode_mask = type_ == Ay8914 ? 0x30 : 0x10; int volume = amp_table [vol_mode & 0x0F] >> half_vol;
int volume = amp_table [vol_mode & 0x0F] >> (half_vol + env_step_scale); int osc_env_pos = env.pos;
int osc_env_pos = env_pos; if ( vol_mode & 0x10 )
if ( vol_mode & vol_mode_mask )
{ {
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale); volume = env.wave [osc_env_pos] >> half_vol;
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
// use envelope only if it's a repeating wave or a ramp that hasn't finished // use envelope only if it's a repeating wave or a ramp that hasn't finished
if ( !(regs [13] & 1) || osc_env_pos < -32 ) if ( !(regs [13] & 1) || osc_env_pos < -32 )
{ {
end_time = start_time + env_delay; end_time = start_time + env.delay;
if ( end_time >= final_end_time ) if ( end_time >= final_end_time )
end_time = final_end_time; end_time = final_end_time;
//if ( !(regs [12] | regs [11]) ) //if ( !(regs [12] | regs [11]) )
// dprintf( "Used envelope period 0\n" ); // debug_printf( "Used envelope period 0\n" );
} }
else if ( !volume ) else if ( !volume )
{ {
@ -252,20 +237,20 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
blip_time_t time = start_time + osc->delay; blip_time_t time = start_time + osc->delay;
if ( osc_mode & tone_off ) // maintain tone's phase when off if ( osc_mode & tone_off ) // maintain tone's phase when off
{ {
int count = (final_end_time - time + period - 1) / period; blargg_long count = (final_end_time - time + period - 1) / period;
time += count * period; time += count * period;
osc->phase ^= count & 1; osc->phase ^= count & 1;
} }
// noise time // noise time
blip_time_t ntime = final_end_time; blip_time_t ntime = final_end_time;
unsigned noise_lfsr = 1; blargg_ulong noise_lfsr = 1;
if ( !(osc_mode & noise_off) ) if ( !(osc_mode & noise_off) )
{ {
ntime = start_time + old_noise_delay; ntime = start_time + old_noise_delay;
noise_lfsr = old_noise_lfsr; noise_lfsr = old_noise_lfsr;
//if ( (regs [6] & 0x1F) == 0 ) //if ( (regs [6] & 0x1F) == 0 )
// dprintf( "Used noise period 0\n" ); // debug_printf( "Used noise period 0\n" );
} }
// The following efficiently handles several cases (least demanding first): // The following efficiently handles several cases (least demanding first):
@ -326,8 +311,8 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
else else
{ {
// 20 or more noise periods on average for some music // 20 or more noise periods on average for some music
int remain = end - ntime; blargg_long remain = end - ntime;
int count = remain / noise_period; blargg_long count = remain / noise_period;
if ( remain >= 0 ) if ( remain >= 0 )
ntime += noise_period + count * noise_period; ntime += noise_period + count * noise_period;
} }
@ -342,12 +327,11 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
delta = -delta; delta = -delta;
synth_.offset( time, delta, osc_output ); synth_.offset( time, delta, osc_output );
time += period; time += period;
// alternate (less-efficient) implementation
//phase ^= 1; //phase ^= 1;
} }
//assert( phase == (delta > 0) );
phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1); phase = unsigned (-delta) >> (CHAR_BIT * sizeof (unsigned) - 1);
check( phase == (delta > 0) ); // (delta > 0)
} }
else else
{ {
@ -374,11 +358,10 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
// next envelope step // next envelope step
if ( ++osc_env_pos >= 0 ) if ( ++osc_env_pos >= 0 )
osc_env_pos -= 32; osc_env_pos -= 32;
volume = env_wave [osc_env_pos] >> (half_vol + env_step_scale); volume = env.wave [osc_env_pos] >> half_vol;
if ( type_ == Ay8914 ) volume >>= 3 - ( ( vol_mode & vol_mode_mask ) >> 4 );
start_time = end_time; start_time = end_time;
end_time += env_period; end_time += env_period;
if ( end_time > final_end_time ) if ( end_time > final_end_time )
end_time = final_end_time; end_time = final_end_time;
} }
@ -386,27 +369,27 @@ void Ay_Apu::run_until( blip_time_t final_end_time )
if ( !(osc_mode & noise_off) ) if ( !(osc_mode & noise_off) )
{ {
noise_delay = ntime - final_end_time; noise.delay = ntime - final_end_time;
this->noise_lfsr = noise_lfsr; noise.lfsr = noise_lfsr;
} }
} }
// TODO: optimized saw wave envelope? // TODO: optimized saw wave envelope?
// maintain envelope phase // maintain envelope phase
blip_time_t remain = final_end_time - last_time - env_delay; blip_time_t remain = final_end_time - last_time - env.delay;
if ( remain >= 0 ) if ( remain >= 0 )
{ {
int count = (remain + env_period) / env_period; blargg_long count = (remain + env_period) / env_period;
env_pos += count; env.pos += count;
if ( env_pos >= 0 ) if ( env.pos >= 0 )
env_pos = (env_pos & 31) - 32; env.pos = (env.pos & 31) - 32;
remain -= count * env_period; remain -= count * env_period;
assert( -remain <= env_period ); assert( -remain <= env_period );
} }
env_delay = -remain; env.delay = -remain;
assert( env_delay > 0 ); assert( env.delay > 0 );
assert( env_pos < 0 ); assert( env.pos < 0 );
last_time = final_end_time; last_time = final_end_time;
} }

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,11 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
#include <string.h>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -19,9 +20,9 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
Classic_Emu::Classic_Emu() Classic_Emu::Classic_Emu()
{ {
buf = NULL; buf = 0;
stereo_buffer = NULL; stereo_buffer = 0;
voice_types = NULL; voice_types = 0;
// avoid inconsistency in our duplicated constants // avoid inconsistency in our duplicated constants
assert( (int) wave_type == (int) Multi_Buffer::wave_type ); assert( (int) wave_type == (int) Multi_Buffer::wave_type );
@ -32,8 +33,6 @@ Classic_Emu::Classic_Emu()
Classic_Emu::~Classic_Emu() Classic_Emu::~Classic_Emu()
{ {
delete stereo_buffer; delete stereo_buffer;
delete effects_buffer_;
effects_buffer_ = NULL;
} }
void Classic_Emu::set_equalizer_( equalizer_t const& eq ) void Classic_Emu::set_equalizer_( equalizer_t const& eq )
@ -44,7 +43,7 @@ void Classic_Emu::set_equalizer_( equalizer_t const& eq )
buf->bass_freq( (int) equalizer().bass ); buf->bass_freq( (int) equalizer().bass );
} }
blargg_err_t Classic_Emu::set_sample_rate_( int rate ) blargg_err_t Classic_Emu::set_sample_rate_( long rate )
{ {
if ( !buf ) if ( !buf )
{ {
@ -55,6 +54,12 @@ blargg_err_t Classic_Emu::set_sample_rate_( int rate )
return buf->set_sample_rate( rate, 1000 / 20 ); return buf->set_sample_rate( rate, 1000 / 20 );
} }
blargg_err_t Classic_Emu::set_multi_channel ( bool is_enabled )
{
RETURN_ERR( Music_Emu::set_multi_channel_( is_enabled ) );
return 0;
}
void Classic_Emu::mute_voices_( int mask ) void Classic_Emu::mute_voices_( int mask )
{ {
Music_Emu::mute_voices_( mask ); Music_Emu::mute_voices_( mask );
@ -62,11 +67,11 @@ void Classic_Emu::mute_voices_( int mask )
{ {
if ( mask & (1 << i) ) if ( mask & (1 << i) )
{ {
set_voice( i, NULL, NULL, NULL ); set_voice( i, 0, 0, 0 );
} }
else else
{ {
Multi_Buffer::channel_t ch = buf->channel( i ); Multi_Buffer::channel_t ch = buf->channel( i, (voice_types ? voice_types [i] : 0) );
assert( (ch.center && ch.left && ch.right) || assert( (ch.center && ch.left && ch.right) ||
(!ch.center && !ch.left && !ch.right) ); // all or nothing (!ch.center && !ch.left && !ch.right) ); // all or nothing
set_voice( i, ch.center, ch.left, ch.right ); set_voice( i, ch.center, ch.left, ch.right );
@ -74,35 +79,33 @@ void Classic_Emu::mute_voices_( int mask )
} }
} }
void Classic_Emu::change_clock_rate( int rate ) void Classic_Emu::change_clock_rate( long rate )
{ {
clock_rate_ = rate; clock_rate_ = rate;
buf->clock_rate( rate ); buf->clock_rate( rate );
} }
blargg_err_t Classic_Emu::setup_buffer( int rate ) blargg_err_t Classic_Emu::setup_buffer( long rate )
{ {
change_clock_rate( rate ); change_clock_rate( rate );
RETURN_ERR( buf->set_channel_count( voice_count(), voice_types ) ); RETURN_ERR( buf->set_channel_count( voice_count() ) );
set_equalizer( equalizer() ); set_equalizer( equalizer() );
buf_changed_count = buf->channels_changed_count(); buf_changed_count = buf->channels_changed_count();
return blargg_ok; return 0;
} }
blargg_err_t Classic_Emu::start_track_( int track ) blargg_err_t Classic_Emu::start_track_( int track )
{ {
RETURN_ERR( Music_Emu::start_track_( track ) ); RETURN_ERR( Music_Emu::start_track_( track ) );
buf->clear(); buf->clear();
return blargg_ok; return 0;
} }
blargg_err_t Classic_Emu::play_( int count, sample_t out [] ) blargg_err_t Classic_Emu::play_( long count, sample_t* out )
{ {
// read from buffer, then refill buffer and repeat if necessary long remain = count;
int remain = count;
while ( remain ) while ( remain )
{ {
buf->disable_immediate_removal();
remain -= buf->read_samples( &out [count - remain], remain ); remain -= buf->read_samples( &out [count - remain], remain );
if ( remain ) if ( remain )
{ {
@ -111,14 +114,77 @@ blargg_err_t Classic_Emu::play_( int count, sample_t out [] )
buf_changed_count = buf->channels_changed_count(); buf_changed_count = buf->channels_changed_count();
remute_voices(); remute_voices();
} }
// TODO: use more accurate length calculation
int msec = buf->length(); int msec = buf->length();
blip_time_t clocks_emulated = msec * clock_rate_ / 1000 - 100; blip_time_t clocks_emulated = (blargg_long) msec * clock_rate_ / 1000;
RETURN_ERR( run_clocks( clocks_emulated, msec ) ); RETURN_ERR( run_clocks( clocks_emulated, msec ) );
assert( clocks_emulated ); assert( clocks_emulated );
buf->end_frame( clocks_emulated ); buf->end_frame( clocks_emulated );
} }
} }
return blargg_ok; return 0;
}
// Rom_Data
blargg_err_t Rom_Data_::load_rom_data_( Data_Reader& in,
int header_size, void* header_out, int fill, long pad_size )
{
long file_offset = pad_size - header_size;
rom_addr = 0;
mask = 0;
size_ = 0;
rom.clear();
file_size_ = in.remain();
if ( file_size_ <= header_size ) // <= because there must be data after header
return gme_wrong_file_type;
blargg_err_t err = rom.resize( file_offset + file_size_ + pad_size );
if ( !err )
err = in.read( rom.begin() + file_offset, file_size_ );
if ( err )
{
rom.clear();
return err;
}
file_size_ -= header_size;
memcpy( header_out, &rom [file_offset], header_size );
memset( rom.begin() , fill, pad_size );
memset( rom.end() - pad_size, fill, pad_size );
return 0;
}
void Rom_Data_::set_addr_( long addr, int unit )
{
rom_addr = addr - unit - pad_extra;
long rounded = (addr + file_size_ + unit - 1) / unit * unit;
if ( rounded <= 0 )
{
rounded = 0;
}
else
{
int shift = 0;
unsigned long max_addr = (unsigned long) (rounded - 1);
while ( max_addr >> shift )
shift++;
mask = (1L << shift) - 1;
}
if ( addr < 0 )
addr = 0;
size_ = rounded;
if ( rom.resize( rounded - rom_addr + pad_extra ) ) { } // OK if shrink fails
if ( 0 )
{
debug_printf( "addr: %X\n", addr );
debug_printf( "file_size: %d\n", file_size_ );
debug_printf( "rounded: %d\n", rounded );
debug_printf( "mask: $%X\n", mask );
}
} }

View file

@ -1,6 +1,6 @@
// Common aspects of emulators which use Blip_Buffer for sound output // Common aspects of emulators which use Blip_Buffer for sound output
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef CLASSIC_EMU_H #ifndef CLASSIC_EMU_H
#define CLASSIC_EMU_H #define CLASSIC_EMU_H
@ -9,57 +9,34 @@
#include "Music_Emu.h" #include "Music_Emu.h"
class Classic_Emu : public Music_Emu { 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: public:
Classic_Emu(); Classic_Emu();
~Classic_Emu(); ~Classic_Emu();
virtual void set_buffer( Multi_Buffer* ); void set_buffer( Multi_Buffer* ) override;
blargg_err_t set_multi_channel( bool is_enabled ) override;
protected: protected:
virtual blargg_err_t set_sample_rate_( int sample_rate ); // Services
virtual void mute_voices_( int ); enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
virtual void set_equalizer_( equalizer_t const& ); void set_voice_types( int const* t ) { voice_types = t; }
virtual blargg_err_t play_( int, sample_t [] ); blargg_err_t setup_buffer( long clock_rate );
long clock_rate() const { return clock_rate_; }
void change_clock_rate( long ); // experimental
// Overridable
virtual void set_voice( int index, Blip_Buffer* center,
Blip_Buffer* left, Blip_Buffer* right ) = 0;
virtual void update_eq( blip_eq_t const& ) = 0;
virtual blargg_err_t start_track_( int track ) override;
virtual blargg_err_t run_clocks( blip_time_t& time_io, int msec ) = 0;
protected:
blargg_err_t set_sample_rate_( long sample_rate ) override;
void mute_voices_( int ) override;
void set_equalizer_( equalizer_t const& ) override;
blargg_err_t play_( long, sample_t* ) override;
private: private:
Multi_Buffer* buf; Multi_Buffer* buf;
Multi_Buffer* stereo_buffer; // NULL if using custom buffer Multi_Buffer* stereo_buffer; // NULL if using custom buffer
int clock_rate_; long clock_rate_;
unsigned buf_changed_count; unsigned buf_changed_count;
int const* voice_types; int const* voice_types;
}; };
@ -70,10 +47,82 @@ inline void Classic_Emu::set_buffer( Multi_Buffer* new_buf )
buf = new_buf; buf = new_buf;
} }
inline void Classic_Emu::set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ) { } // ROM data handler, used by several Classic_Emu derivitives. Loads file data
// with padding on both sides, allowing direct use in bank mapping. The main purpose
// is to allow all file data to be loaded with only one read() call (for efficiency).
inline void Classic_Emu::update_eq( blip_eq_t const& ) { } class Rom_Data_ {
public:
typedef unsigned char byte;
protected:
enum { pad_extra = 8 };
blargg_vector<byte> rom;
long file_size_;
blargg_long rom_addr;
blargg_long mask;
blargg_long size_; // TODO: eliminate
inline blargg_err_t Classic_Emu::run_clocks( blip_time_t&, int ) { return blargg_ok; } blargg_err_t load_rom_data_( Data_Reader& in, int header_size, void* header_out,
int fill, long pad_size );
void set_addr_( long addr, int unit );
};
template<int unit>
class Rom_Data : public Rom_Data_ {
enum { pad_size = unit + pad_extra };
public:
// Load file data, using already-loaded header 'h' if not NULL. Copy header
// from loaded file data into *out and fill unmapped bytes with 'fill'.
blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill )
{
return load_rom_data_( in, header_size, header_out, fill, pad_size );
}
// Size of file data read in (excluding header)
long file_size() const { return file_size_; }
// Pointer to beginning of file data
byte* begin() const { return rom.begin() + pad_size; }
// Set address that file data should start at
void set_addr( long addr ) { set_addr_( addr, unit ); }
// Free data
void clear() { rom.clear(); }
// Size of data + start addr, rounded to a multiple of unit
long size() const { return size_; }
// Pointer to unmapped page filled with same value
byte* unmapped() { return rom.begin(); }
// Mask address to nearest power of two greater than size()
blargg_long mask_addr( blargg_long addr ) const
{
#ifdef check
check( addr <= mask );
#endif
return addr & mask;
}
// Pointer to page starting at addr. Returns unmapped() if outside data.
byte* at_addr( blargg_long addr )
{
blargg_ulong offset = mask_addr( addr ) - rom_addr;
if ( offset > blargg_ulong (rom.size() - pad_size) )
offset = 0; // unmapped
return &rom [offset];
}
};
#ifndef GME_APU_HOOK
#define GME_APU_HOOK( emu, addr, data ) ((void) 0)
#endif
#ifndef GME_FRAME_HOOK
#define GME_FRAME_HOOK( emu ) ((void) 0)
#else
#define GME_FRAME_HOOK_DEFINED 1
#endif
#endif #endif

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

@ -6,6 +6,7 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <algorithm>
/* Copyright (C) 2005-2006 Shay Green. This module is free software; you /* Copyright (C) 2005-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
@ -20,10 +21,25 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#ifdef HAVE_ZLIB_H
#include <zlib.h>
#include <stdlib.h>
#include <errno.h>
static const unsigned char gz_magic[2] = {0x1f, 0x8b}; /* gzip magic header */
#endif /* HAVE_ZLIB_H */
using std::min;
using std::max;
const char Data_Reader::eof_error [] = "Unexpected end of file"; const char Data_Reader::eof_error [] = "Unexpected end of file";
#define RETURN_VALIDITY_CHECK( cond ) \
do { if ( unlikely( !(cond) ) ) return "Corrupt file"; } while(0)
blargg_err_t Data_Reader::read( void* p, long s ) blargg_err_t Data_Reader::read( void* p, long s )
{ {
RETURN_VALIDITY_CHECK( s > 0 );
long result = read_avail( p, s ); long result = read_avail( p, s );
if ( result != s ) if ( result != s )
{ {
@ -38,6 +54,8 @@ blargg_err_t Data_Reader::read( void* p, long s )
blargg_err_t Data_Reader::skip( long count ) blargg_err_t Data_Reader::skip( long count )
{ {
RETURN_VALIDITY_CHECK( count >= 0 );
char buf [512]; char buf [512];
while ( count ) while ( count )
{ {
@ -54,7 +72,8 @@ long File_Reader::remain() const { return size() - tell(); }
blargg_err_t File_Reader::skip( long n ) blargg_err_t File_Reader::skip( long n )
{ {
assert( n >= 0 ); RETURN_VALIDITY_CHECK( n >= 0 );
if ( !n ) if ( !n )
return 0; return 0;
return seek( tell() + n ); return seek( tell() + n );
@ -67,13 +86,14 @@ Subset_Reader::Subset_Reader( Data_Reader* dr, long size )
in = dr; in = dr;
remain_ = dr->remain(); remain_ = dr->remain();
if ( remain_ > size ) if ( remain_ > size )
remain_ = size; remain_ = max( 0l, size );
} }
long Subset_Reader::remain() const { return remain_; } long Subset_Reader::remain() const { return remain_; }
long Subset_Reader::read_avail( void* p, long s ) long Subset_Reader::read_avail( void* p, long s )
{ {
s = max( 0l, s );
if ( s > remain_ ) if ( s > remain_ )
s = remain_; s = remain_;
remain_ -= s; remain_ -= s;
@ -85,7 +105,7 @@ long Subset_Reader::read_avail( void* p, long s )
Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r ) Remaining_Reader::Remaining_Reader( void const* h, long size, Data_Reader* r )
{ {
header = (char const*) h; header = (char const*) h;
header_end = header + size; header_end = header + max( 0l, size );
in = r; in = r;
} }
@ -93,22 +113,24 @@ long Remaining_Reader::remain() const { return header_end - header + in->remain(
long Remaining_Reader::read_first( void* out, long count ) long Remaining_Reader::read_first( void* out, long count )
{ {
count = max( 0l, count );
long first = header_end - header; long first = header_end - header;
if ( first ) if ( first )
{ {
if ( first > count ) if ( first > count || first < 0 )
first = count; first = count;
void const* old = header; void const* old = header;
header += first; header += first;
memcpy( out, old, first ); memcpy( out, old, (size_t) first );
} }
return first; return first;
} }
long Remaining_Reader::read_avail( void* out, long count ) long Remaining_Reader::read_avail( void* out, long count )
{ {
count = max( 0l, count );
long first = read_first( out, count ); long first = read_first( out, count );
long second = count - first; long second = max( 0l, count - first );
if ( second ) if ( second )
{ {
second = in->read_avail( (char*) out + first, second ); second = in->read_avail( (char*) out + first, second );
@ -120,8 +142,9 @@ long Remaining_Reader::read_avail( void* out, long count )
blargg_err_t Remaining_Reader::read( void* out, long count ) blargg_err_t Remaining_Reader::read( void* out, long count )
{ {
count = max( 0l, count );
long first = read_first( out, count ); long first = read_first( out, count );
long second = count - first; long second = max( 0l, count - first );
if ( !second ) if ( !second )
return 0; return 0;
return in->read( (char*) out + first, second ); return in->read( (char*) out + first, second );
@ -130,41 +153,135 @@ blargg_err_t Remaining_Reader::read( void* out, long count )
// Mem_File_Reader // Mem_File_Reader
Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : Mem_File_Reader::Mem_File_Reader( const void* p, long s ) :
begin( (const char*) p ), m_begin( (const char*) p ),
size_( s ) m_size( max( 0l, s ) ),
m_pos( 0l )
{ {
pos = 0; #ifdef HAVE_ZLIB_H
if( !m_begin )
return;
if ( gz_decompress() )
{
debug_printf( "Loaded compressed data\n" );
m_ownedPtr = true;
}
#endif /* HAVE_ZLIB_H */
} }
long Mem_File_Reader::size() const { return size_; } #ifdef HAVE_ZLIB_H
Mem_File_Reader::~Mem_File_Reader()
{
if ( m_ownedPtr )
free( const_cast<char*>( m_begin ) ); // see gz_compress for the malloc
}
#endif
long Mem_File_Reader::size() const { return m_size; }
long Mem_File_Reader::read_avail( void* p, long s ) long Mem_File_Reader::read_avail( void* p, long s )
{ {
long r = remain(); long r = remain();
if ( s > r ) if ( s > r || s < 0 )
s = r; s = r;
memcpy( p, begin + pos, s ); memcpy( p, m_begin + m_pos, static_cast<size_t>(s) );
pos += s; m_pos += s;
return s; return s;
} }
long Mem_File_Reader::tell() const { return pos; } long Mem_File_Reader::tell() const { return m_pos; }
blargg_err_t Mem_File_Reader::seek( long n ) blargg_err_t Mem_File_Reader::seek( long n )
{ {
if ( n > size_ ) RETURN_VALIDITY_CHECK( n >= 0 );
if ( n > m_size )
return eof_error; return eof_error;
pos = n; m_pos = n;
return 0; return 0;
} }
#ifdef HAVE_ZLIB_H
bool Mem_File_Reader::gz_decompress()
{
if ( m_size >= 2 && memcmp(m_begin, gz_magic, 2) != 0 )
{
/* Don't try to decompress non-GZ files, just assign input pointer */
return false;
}
using vec_size = size_t;
const vec_size full_length = static_cast<vec_size>( m_size );
const vec_size half_length = static_cast<vec_size>( m_size / 2 );
// We use malloc/friends here so we can realloc to grow buffer if needed
char *raw_data = reinterpret_cast<char *> ( malloc( full_length ) );
size_t raw_data_size = full_length;
if ( !raw_data )
return false;
z_stream strm;
strm.next_in = const_cast<Bytef *>( reinterpret_cast<const Bytef *>( m_begin ) );
strm.avail_in = static_cast<uInt>( m_size );
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
bool done = false;
// Adding 16 sets bit 4, which enables zlib to auto-detect the
// header.
if ( inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK )
{
free( raw_data );
return false;
}
while ( !done )
{
/* If our output buffer is too small */
if ( strm.total_out >= raw_data_size )
{
raw_data_size += half_length;
raw_data = reinterpret_cast<char *>( realloc( raw_data, raw_data_size ) );
if ( !raw_data ) {
return false;
}
}
strm.next_out = reinterpret_cast<Bytef *>( raw_data + strm.total_out );
strm.avail_out = static_cast<uInt>( static_cast<uLong>( raw_data_size ) - strm.total_out );
/* Inflate another chunk. */
int err = inflate( &strm, Z_SYNC_FLUSH );
if ( err == Z_STREAM_END )
done = true;
else if ( err != Z_OK )
break;
}
if ( inflateEnd(&strm) != Z_OK )
{
free( raw_data );
return false;
}
m_begin = raw_data;
m_size = static_cast<long>( strm.total_out );
return true;
}
#endif /* HAVE_ZLIB_H */
// Callback_Reader // Callback_Reader
Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) : Callback_Reader::Callback_Reader( callback_t c, long size, void* d ) :
callback( c ), callback( c ),
data( d ) data( d )
{ {
remain_ = size; remain_ = max( 0l, size );
} }
long Callback_Reader::remain() const { return remain_; } long Callback_Reader::remain() const { return remain_; }
@ -173,34 +290,82 @@ long Callback_Reader::read_avail( void* out, long count )
{ {
if ( count > remain_ ) if ( count > remain_ )
count = remain_; count = remain_;
if ( Callback_Reader::read( out, count ) ) if ( count < 0 || Callback_Reader::read( out, count ) )
count = -1; count = -1;
return count; return count;
} }
blargg_err_t Callback_Reader::read( void* out, long count ) blargg_err_t Callback_Reader::read( void* out, long count )
{ {
RETURN_VALIDITY_CHECK( count >= 0 );
if ( count > remain_ ) if ( count > remain_ )
return eof_error; return eof_error;
return callback( data, out, count ); return callback( data, out, (int) count );
} }
// Std_File_Reader // Std_File_Reader
Std_File_Reader::Std_File_Reader() : file_( 0 ) { } #ifdef HAVE_ZLIB_H
static const char* get_gzip_eof( const char* path, long* eof )
{
FILE* file = fopen( path, "rb" );
if ( !file )
return "Couldn't open file";
unsigned char buf [4];
bool found_eof = false;
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{
fseek( file, -4, SEEK_END );
if ( fread( buf, 4, 1, file ) > 0 ) {
*eof = get_le32( buf );
found_eof = true;
}
}
if ( !found_eof )
{
fseek( file, 0, SEEK_END );
*eof = ftell( file );
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : nullptr;
fclose( file );
return err;
}
#endif
Std_File_Reader::Std_File_Reader() :
file_( nullptr )
#ifdef HAVE_ZLIB_H
, size_( 0 )
#endif
{ }
Std_File_Reader::~Std_File_Reader() { close(); } Std_File_Reader::~Std_File_Reader() { close(); }
blargg_err_t Std_File_Reader::open( const char* path ) blargg_err_t Std_File_Reader::open( const char* path )
{ {
#ifdef HAVE_ZLIB_H
// zlib transparently handles uncompressed data if magic header
// not present but we still need to grab size
RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" );
#else
file_ = fopen( path, "rb" ); file_ = fopen( path, "rb" );
#endif
if ( !file_ ) if ( !file_ )
return "Couldn't open file"; return "Couldn't open file";
return 0; return nullptr;
} }
long Std_File_Reader::size() const long Std_File_Reader::size() const
{ {
#ifdef HAVE_ZLIB_H
if ( file_ )
return size_; // Set for both compressed and uncompressed modes
#endif
long pos = tell(); long pos = tell();
fseek( (FILE*) file_, 0, SEEK_END ); fseek( (FILE*) file_, 0, SEEK_END );
long result = tell(); long result = tell();
@ -210,24 +375,64 @@ long Std_File_Reader::size() const
long Std_File_Reader::read_avail( void* p, long s ) long Std_File_Reader::read_avail( void* p, long s )
{ {
return fread( p, 1, s, (FILE*) file_ ); #ifdef HAVE_ZLIB_H
if ( file_ && s > 0 && static_cast<unsigned long>(s) <= UINT_MAX ) {
return gzread( reinterpret_cast<gzFile>(file_),
p, static_cast<unsigned>(s) );
}
return 0l;
#else
const size_t readLength = static_cast<size_t>( max( 0l, s ) );
const auto result = fread( p, 1, readLength, reinterpret_cast<FILE*>(file_) );
return static_cast<long>( result );
#endif /* HAVE_ZLIB_H */
} }
blargg_err_t Std_File_Reader::read( void* p, long s ) blargg_err_t Std_File_Reader::read( void* p, long s )
{ {
if ( s == (long) fread( p, 1, s, (FILE*) file_ ) ) RETURN_VALIDITY_CHECK( s > 0 && static_cast<unsigned long>(s) <= UINT_MAX );
#ifdef HAVE_ZLIB_H
if ( file_ )
{
const auto &gzfile = reinterpret_cast<gzFile>( file_ );
if ( s == gzread( gzfile, p, static_cast<unsigned>( s ) ) )
return nullptr;
if ( gzeof( gzfile ) )
return eof_error;
return "Couldn't read from GZ file";
}
#endif
const auto &file = reinterpret_cast<FILE*>( file_ );
if ( s == static_cast<long>( fread( p, 1, static_cast<size_t>(s), file ) ) )
return 0; return 0;
if ( feof( (FILE*) file_ ) ) if ( feof( file ) )
return eof_error; return eof_error;
return "Couldn't read from file"; return "Couldn't read from file";
} }
long Std_File_Reader::tell() const { return ftell( (FILE*) file_ ); } long Std_File_Reader::tell() const
{
#ifdef HAVE_ZLIB_H
if ( file_ )
return gztell( reinterpret_cast<gzFile>( file_ ) );
#endif
return ftell( reinterpret_cast<FILE*>( file_ ) );
}
blargg_err_t Std_File_Reader::seek( long n ) blargg_err_t Std_File_Reader::seek( long n )
{ {
if ( !fseek( (FILE*) file_, n, SEEK_SET ) ) #ifdef HAVE_ZLIB_H
return 0; if ( file_ )
{
if ( gzseek( reinterpret_cast<gzFile>( file_ ), n, SEEK_SET ) >= 0 )
return nullptr;
if ( n > size_ )
return eof_error;
return "Error seeking in GZ file";
}
#endif
if ( !fseek( reinterpret_cast<FILE*>( file_ ), n, SEEK_SET ) )
return nullptr;
if ( n > size() ) if ( n > size() )
return eof_error; return eof_error;
return "Error seeking in file"; return "Error seeking in file";
@ -237,79 +442,12 @@ void Std_File_Reader::close()
{ {
if ( file_ ) if ( file_ )
{ {
fclose( (FILE*) file_ );
file_ = 0;
}
}
// Gzip_File_Reader
#ifdef HAVE_ZLIB_H #ifdef HAVE_ZLIB_H
gzclose( reinterpret_cast<gzFile>( file_ ) );
#include "zlib.h" #else
fclose( reinterpret_cast<FILE*>( file_ ) );
static const char* get_gzip_eof( const char* path, long* eof )
{
FILE* file = fopen( path, "rb" );
if ( !file )
return "Couldn't open file";
unsigned char buf [4];
if ( fread( buf, 2, 1, file ) > 0 && buf [0] == 0x1F && buf [1] == 0x8B )
{
fseek( file, -4, SEEK_END );
fread( buf, 4, 1, file );
*eof = get_le32( buf );
}
else
{
fseek( file, 0, SEEK_END );
*eof = ftell( file );
}
const char* err = (ferror( file ) || feof( file )) ? "Couldn't get file size" : 0;
fclose( file );
return err;
}
Gzip_File_Reader::Gzip_File_Reader() : file_( 0 ) { }
Gzip_File_Reader::~Gzip_File_Reader() { close(); }
blargg_err_t Gzip_File_Reader::open( const char* path )
{
close();
RETURN_ERR( get_gzip_eof( path, &size_ ) );
file_ = gzopen( path, "rb" );
if ( !file_ )
return "Couldn't open file";
return 0;
}
long Gzip_File_Reader::size() const { return size_; }
long Gzip_File_Reader::read_avail( void* p, long s ) { return gzread( file_, p, s ); }
long Gzip_File_Reader::tell() const { return gztell( file_ ); }
blargg_err_t Gzip_File_Reader::seek( long n )
{
if ( gzseek( file_, n, SEEK_SET ) >= 0 )
return 0;
if ( n > size_ )
return eof_error;
return "Error seeking in file";
}
void Gzip_File_Reader::close()
{
if ( file_ )
{
gzclose( file_ );
file_ = 0;
}
}
#endif #endif
file_ = nullptr;
}
}

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

@ -6,6 +6,10 @@
#include "blargg_common.h" #include "blargg_common.h"
#ifdef HAVE_ZLIB_H
#include <zlib.h>
#endif
// Supports reading and finding out how many bytes are remaining // Supports reading and finding out how many bytes are remaining
class Data_Reader { class Data_Reader {
public: public:
@ -65,13 +69,19 @@ public:
long tell() const; long tell() const;
blargg_err_t seek( long ); blargg_err_t seek( long );
private: private:
void* file_; void* file_; // Either FILE* or zlib's gzFile
#ifdef HAVE_ZLIB_H
long size_; // TODO: Fix ABI compat
#endif /* HAVE_ZLIB_H */
}; };
// Treats range of memory as a file // Treats range of memory as a file
class Mem_File_Reader : public File_Reader { class Mem_File_Reader : public File_Reader {
public: public:
Mem_File_Reader( const void*, long size ); Mem_File_Reader( const void*, long size );
#ifdef HAVE_ZLIB_H
~Mem_File_Reader( );
#endif /* HAVE_ZLIB_H */
public: public:
long size() const; long size() const;
@ -79,11 +89,19 @@ public:
long tell() const; long tell() const;
blargg_err_t seek( long ); blargg_err_t seek( long );
private: private:
const char* const begin; #ifdef HAVE_ZLIB_H
const long size_; bool gz_decompress();
long pos; #endif /* HAVE_ZLIB_H */
const char* m_begin;
long m_size;
long m_pos;
#ifdef HAVE_ZLIB_H
bool m_ownedPtr = false; // set if we must free m_begin
#endif /* HAVE_ZLIB_H */
}; };
// Makes it look like there are only count bytes remaining // Makes it look like there are only count bytes remaining
class Subset_Reader : public Data_Reader { class Subset_Reader : public Data_Reader {
public: public:
@ -116,7 +134,7 @@ private:
// Invokes callback function to read data. Size of data must be specified in advance. // Invokes callback function to read data. Size of data must be specified in advance.
class Callback_Reader : public Data_Reader { class Callback_Reader : public Data_Reader {
public: public:
typedef const char* (*callback_t)( void* data, void* out, long count ); typedef const char* (*callback_t)( void* data, void* out, int count );
Callback_Reader( callback_t, long size, void* data = 0 ); Callback_Reader( callback_t, long size, void* data = 0 );
public: public:
long read_avail( void*, long ); long read_avail( void*, long );
@ -128,24 +146,4 @@ private:
long remain_; long remain_;
}; };
#ifdef HAVE_ZLIB_H
// Gzip compressed file reader
class Gzip_File_Reader : public File_Reader {
public:
blargg_err_t open( const char* path );
void close();
public:
Gzip_File_Reader();
~Gzip_File_Reader();
long size() const;
long read_avail( void*, long );
long tell() const;
blargg_err_t seek( long );
private:
void* file_;
long size_;
};
#endif
#endif #endif

View file

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

View file

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

View file

@ -1,8 +1,11 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Dual_Resampler.h" #include "Dual_Resampler.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include <stdlib.h>
#include <string.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 can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -15,12 +18,13 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
// TODO: fix this. hack since resampler holds back some output. Dual_Resampler::Dual_Resampler() :
int const resampler_extra = 34; sample_buf_size(0),
oversamples_per_frame(-1),
int const stereo = 2; buf_pos(-1),
resampler_size(0)
Dual_Resampler::Dual_Resampler() { } {
}
Dual_Resampler::~Dual_Resampler() { } Dual_Resampler::~Dual_Resampler() { }
@ -30,15 +34,12 @@ blargg_err_t Dual_Resampler::reset( int pairs )
RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) ); RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
resize( pairs ); resize( pairs );
resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2); resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2);
RETURN_ERR( resampler.resize_buffer( resampler_size ) ); return resampler.buffer_size( resampler_size );
resampler.clear();
return blargg_ok;
} }
void Dual_Resampler::resize( int pairs ) void Dual_Resampler::resize( int pairs )
{ {
int new_sample_buf_size = pairs * 2; 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 ( sample_buf_size != new_sample_buf_size )
{ {
if ( (unsigned) new_sample_buf_size > sample_buf.size() ) if ( (unsigned) new_sample_buf_size > sample_buf.size() )
@ -47,69 +48,40 @@ void Dual_Resampler::resize( int pairs )
return; return;
} }
sample_buf_size = new_sample_buf_size; sample_buf_size = new_sample_buf_size;
oversamples_per_frame = int (pairs * resampler.rate()) * 2 + 2; oversamples_per_frame = int (pairs * resampler.ratio()) * 2 + 2;
clear(); clear();
} }
} }
void Dual_Resampler::clear() void Dual_Resampler::play_frame_( Blip_Buffer& blip_buf, dsample_t* out )
{ {
buf_pos = buffered = 0; long pair_count = sample_buf_size >> 1;
resampler.clear(); blip_time_t blip_time = blip_buf.count_clocks( pair_count );
} int sample_count = oversamples_per_frame - resampler.written();
int new_count = play_frame( blip_time, sample_count, resampler.buffer() );
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 ); assert( new_count < resampler_size );
stereo_buf.end_frame( blip_time ); blip_buf.end_frame( blip_time );
assert( stereo_buf.samples_avail() == pair_count * 2 ); assert( blip_buf.samples_avail() == 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];
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 ); resampler.write( new_count );
int count = resampler.read( sample_buf.begin(), sample_buf_size ); #ifdef NDEBUG // Avoid warning when asserts are disabled
resampler.read( sample_buf.begin(), sample_buf_size );
#else
long count = resampler.read( sample_buf.begin(), sample_buf_size );
assert( count == (long) sample_buf_size );
#endif
mix_samples( stereo_buf, out, count, secondary_buf_set, secondary_buf_set_count ); mix_samples( blip_buf, out );
blip_buf.remove_samples( pair_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 ) void Dual_Resampler::dual_play( long count, dsample_t* out, Blip_Buffer& blip_buf )
{ {
// empty extra buffer // empty extra buffer
int remain = buffered - buf_pos; long remain = sample_buf_size - buf_pos;
if ( remain ) if ( remain )
{ {
if ( remain > count ) if ( remain > count )
@ -121,195 +93,47 @@ void Dual_Resampler::dual_play( int count, dsample_t out [], Stereo_Buffer& ster
} }
// entire frames // entire frames
while ( count >= sample_buf_size ) while ( count >= (long) sample_buf_size )
{ {
buf_pos = buffered = play_frame_( stereo_buf, out, secondary_buf_set, secondary_buf_set_count ); play_frame_( blip_buf, out );
out += buffered; out += sample_buf_size;
count -= buffered; count -= sample_buf_size;
} }
while (count > 0) // extra
if ( count )
{ {
buffered = play_frame_( stereo_buf, sample_buf.begin(), secondary_buf_set, secondary_buf_set_count ); play_frame_( blip_buf, sample_buf.begin() );
if ( buffered >= count ) buf_pos = count;
{ memcpy( out, sample_buf.begin(), count * sizeof *out );
buf_pos = count; out += 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 ) void Dual_Resampler::mix_samples( Blip_Buffer& blip_buf, dsample_t* out )
{ {
// lol hax Blip_Reader sn;
if ( ((Tracked_Blip_Buffer*)stereo_buf.left())->non_silent() | ((Tracked_Blip_Buffer*)stereo_buf.right())->non_silent() ) int bass = sn.begin( blip_buf );
mix_stereo( stereo_buf, out_, count ); const dsample_t* in = sample_buf.begin();
else
mix_mono( stereo_buf, out_, count );
if ( secondary_buf_set && secondary_buf_set_count ) for ( int n = sample_buf_size >> 1; n--; )
{
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); int s = sn.read();
BLIP_READER_NEXT_IDX_( sn, bass, offset ); blargg_long l = (blargg_long) in [0] * 2 + s;
if ( (int16_t) l != l )
l = 0x7FFF - (l >> 24);
int l = (in [offset] [0] * gain >> gain_bits) + s; sn.next( bass );
int r = (in [offset] [1] * gain >> gain_bits) + s; blargg_long r = (blargg_long) in [1] * 2 + s;
if ( (int16_t) r != r )
r = 0x7FFF - (r >> 24);
BLIP_CLAMP( l, l ); in += 2;
out [offset] [0] = (blip_sample_t) l; out [0] = l;
out [1] = r;
BLIP_CLAMP( r, r ); out += 2;
out [offset] [1] = (blip_sample_t) r;
} }
while ( ++offset );
BLIP_READER_END( sn, *stereo_buf.center() ); sn.end( blip_buf );
} }
void Dual_Resampler::mix_stereo( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( snc, *stereo_buf.center() );
BLIP_READER_BEGIN( snl, *stereo_buf.left() );
BLIP_READER_BEGIN( snr, *stereo_buf.right() );
count >>= 1;
BLIP_READER_ADJ_( snc, count );
BLIP_READER_ADJ_( snl, count );
BLIP_READER_ADJ_( snr, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
stereo_dsample_t const* BLARGG_RESTRICT in =
(stereo_dsample_t const*) sample_buf.begin() + count;
int offset = -count;
int const gain = gain_;
do
{
int sc = BLIP_READER_READ_RAW( snc ) >> (blip_sample_bits - 16);
int sl = BLIP_READER_READ_RAW( snl ) >> (blip_sample_bits - 16);
int sr = BLIP_READER_READ_RAW( snr ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( snc, bass, offset );
BLIP_READER_NEXT_IDX_( snl, bass, offset );
BLIP_READER_NEXT_IDX_( snr, bass, offset );
int l = (in [offset] [0] * gain >> gain_bits) + sl + sc;
int r = (in [offset] [1] * gain >> gain_bits) + sr + sc;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( snc, *stereo_buf.center() );
BLIP_READER_END( snl, *stereo_buf.left() );
BLIP_READER_END( snr, *stereo_buf.right() );
}
void Dual_Resampler::mix_extra_mono( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( sn, *stereo_buf.center() );
count >>= 1;
BLIP_READER_ADJ_( sn, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
int offset = -count;
do
{
int s = BLIP_READER_READ_RAW( sn ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( sn, bass, offset );
int l = out [offset] [0] + s;
int r = out [offset] [1] + s;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( sn, *stereo_buf.center() );
}
void Dual_Resampler::mix_extra_stereo( Stereo_Buffer& stereo_buf, dsample_t out_ [], int count )
{
int const bass = BLIP_READER_BASS( *stereo_buf.center() );
BLIP_READER_BEGIN( snc, *stereo_buf.center() );
BLIP_READER_BEGIN( snl, *stereo_buf.left() );
BLIP_READER_BEGIN( snr, *stereo_buf.right() );
count >>= 1;
BLIP_READER_ADJ_( snc, count );
BLIP_READER_ADJ_( snl, count );
BLIP_READER_ADJ_( snr, count );
typedef dsample_t stereo_dsample_t [2];
stereo_dsample_t* BLARGG_RESTRICT out = (stereo_dsample_t*) out_ + count;
int offset = -count;
do
{
int sc = BLIP_READER_READ_RAW( snc ) >> (blip_sample_bits - 16);
int sl = BLIP_READER_READ_RAW( snl ) >> (blip_sample_bits - 16);
int sr = BLIP_READER_READ_RAW( snr ) >> (blip_sample_bits - 16);
BLIP_READER_NEXT_IDX_( snc, bass, offset );
BLIP_READER_NEXT_IDX_( snl, bass, offset );
BLIP_READER_NEXT_IDX_( snr, bass, offset );
int l = out [offset] [0] + sl + sc;
int r = out [offset] [1] + sr + sc;
BLIP_CLAMP( l, l );
out [offset] [0] = (blip_sample_t) l;
BLIP_CLAMP( r, r );
out [offset] [1] = (blip_sample_t) r;
}
while ( ++offset );
BLIP_READER_END( snc, *stereo_buf.center() );
BLIP_READER_END( snl, *stereo_buf.left() );
BLIP_READER_END( snr, *stereo_buf.right() );
}

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,149 +1,90 @@
// Multi-channel effects buffer with echo and individual panning for each channel // Multi-channel effects buffer with panning, echo and reverb
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef EFFECTS_BUFFER_H #ifndef EFFECTS_BUFFER_H
#define EFFECTS_BUFFER_H #define EFFECTS_BUFFER_H
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
// See Simple_Effects_Buffer (below) for a simpler interface #include <vector>
// Effects_Buffer uses several buffers and outputs stereo sample pairs.
class Effects_Buffer : public Multi_Buffer { class Effects_Buffer : public Multi_Buffer {
public: public:
// To reduce memory usage, fewer buffers can be used (with a best-fit // nVoices indicates the number of voices for which buffers will be allocated
// approach if there are too few), and maximum echo delay can be reduced // to make Effects_Buffer work as "mix everything to one", nVoices will be 1
Effects_Buffer( int max_bufs = 32, int echo_size = 24 * 1024 ); // If center_only is true, only center buffers are created and
// less memory is used.
Effects_Buffer( int nVoices = 1, bool center_only = false );
struct pan_vol_t // Channel Effect Center Pan
{ // ---------------------------------
float vol; // 0.0 = silent, 0.5 = half volume, 1.0 = normal // 0,5 reverb pan_1
float pan; // -1.0 = left, 0.0 = center, +1.0 = right // 1,6 reverb pan_2
// 2,7 echo -
// 3 echo -
// 4 echo -
// Channel configuration
struct config_t {
double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right
double pan_2;
double echo_delay; // msec
double echo_level; // 0.0 to 1.0
double reverb_delay; // msec
double delay_variance; // difference between left/right delays (msec)
double reverb_level; // 0.0 to 1.0
bool effects_enabled; // if false, use optimized simple mixer
config_t();
}; };
// Global configuration // Set configuration of buffer
struct config_t virtual void config( const config_t& );
{ void set_depth( double );
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: public:
~Effects_Buffer(); ~Effects_Buffer();
blargg_err_t set_sample_rate( int samples_per_sec, int msec = blip_default_length ); blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length );
blargg_err_t set_channel_count( int, int const* = NULL ); void clock_rate( long );
void clock_rate( int );
void bass_freq( int ); void bass_freq( int );
void clear(); void clear();
channel_t channel( int ); channel_t channel( int, int );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
int read_samples( blip_sample_t [], int ); long read_samples( blip_sample_t*, long );
int samples_avail() const { return (bufs [0].samples_avail() - mixer.samples_read) * 2; } long samples_avail() const;
enum { stereo = 2 };
typedef int fixed_t;
protected:
enum { extra_chans = stereo * stereo };
private: private:
typedef long fixed_t;
int max_voices;
enum { max_buf_count = 7 };
std::vector<Blip_Buffer> bufs;
enum { chan_types_count = 3 };
std::vector<channel_t> chan_types;
config_t config_; config_t config_;
int clock_rate_; long stereo_remain;
int bass_freq_; long effect_remain;
int buf_count;
bool effects_enabled;
int echo_size; std::vector<std::vector<blip_sample_t> > reverb_buf;
std::vector<std::vector<blip_sample_t> > echo_buf;
struct chan_t std::vector<int> reverb_pos;
{ std::vector<int> echo_pos;
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 { struct {
int delay [stereo]; fixed_t pan_1_levels [2];
fixed_t treble; fixed_t pan_2_levels [2];
fixed_t feedback; int echo_delay_l;
fixed_t low_pass [stereo]; int echo_delay_r;
} s; fixed_t echo_level;
int reverb_delay_l;
int reverb_delay_r;
fixed_t reverb_level;
} chans;
blargg_vector<fixed_t> echo; void mix_mono( blip_sample_t*, blargg_long );
int echo_pos; void mix_stereo( blip_sample_t*, blargg_long );
void mix_enhanced( blip_sample_t*, blargg_long );
bool no_effects; void mix_mono_enhanced( blip_sample_t*, blargg_long );
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 #endif

View file

@ -1,10 +1,13 @@
// $package. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Fir_Resampler.h" #include "Fir_Resampler.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h> #include <math.h>
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you /* Copyright (C) 2004-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -49,75 +52,148 @@ static void gen_sinc( double rolloff, int width, double offset, double spacing,
} }
} }
Fir_Resampler_::Fir_Resampler_( int width, sample_t impulses_ [] ) : Fir_Resampler_::Fir_Resampler_( int width, sample_t* impulses_ ) :
width_( width ), width_( width ),
write_offset( width * stereo - stereo ),
impulses( impulses_ ) impulses( impulses_ )
{ {
imp = NULL; write_pos = 0;
res = 1;
imp_phase = 0;
skip_bits = 0;
step = stereo;
ratio_ = 1.0;
} }
void Fir_Resampler_::clear_() Fir_Resampler_::~Fir_Resampler_() { }
void Fir_Resampler_::clear()
{ {
imp = impulses; imp_phase = 0;
Resampler::clear_(); if ( buf.size() )
{
write_pos = &buf [write_offset];
memset( buf.begin(), 0, write_offset * sizeof buf [0] );
}
} }
blargg_err_t Fir_Resampler_::set_rate_( double new_factor ) blargg_err_t Fir_Resampler_::buffer_size( int new_size )
{ {
double const rolloff = 0.999; RETURN_ERR( buf.resize( new_size + write_offset ) );
double const gain = 1.0; clear();
return 0;
}
// determine number of sub-phases that yield lowest error double Fir_Resampler_::time_ratio( double new_factor, double rolloff, double gain )
double ratio_ = 0.0; {
int res = -1; ratio_ = new_factor;
double fstep = 0.0;
{ {
double least_error = 2; double least_error = 2;
double pos = 0; double pos = 0;
res = -1;
for ( int r = 1; r <= max_res; r++ ) for ( int r = 1; r <= max_res; r++ )
{ {
pos += new_factor; pos += ratio_;
double nearest = floor( pos + 0.5 ); double nearest = floor( pos + 0.5 );
double error = fabs( pos - nearest ); double error = fabs( pos - nearest );
if ( error < least_error ) if ( error < least_error )
{ {
res = r; res = r;
ratio_ = nearest / res; fstep = nearest / res;
least_error = error; least_error = error;
} }
} }
} }
RETURN_ERR( Resampler::set_rate_( ratio_ ) );
// how much of input is used for each output sample skip_bits = 0;
int const step = stereo * (int) floor( ratio_ );
double fraction = fmod( ratio_, 1.0 );
double const filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_; step = stereo * (int) floor( fstep );
ratio_ = fstep;
fstep = fmod( fstep, 1.0 );
double filter = (ratio_ < 1.0) ? 1.0 : 1.0 / ratio_;
double pos = 0.0; double pos = 0.0;
//int input_per_cycle = 0; input_per_cycle = 0;
sample_t* out = impulses; for ( int i = 0; i < res; i++ )
for ( int n = res; --n >= 0; )
{ {
gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter, gen_sinc( rolloff, int (width_ * filter + 1) & ~1, pos, filter,
double (0x7FFF * gain * filter), (int) width_, out ); double (0x7FFF * gain * filter),
out += width_; (int) width_, impulses + i * width_ );
int cur_step = step; pos += fstep;
pos += fraction; input_per_cycle += step;
if ( pos >= 0.9999999 ) if ( pos >= 0.9999999 )
{ {
pos -= 1.0; pos -= 1.0;
cur_step += stereo; skip_bits |= 1 << i;
input_per_cycle++;
} }
*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; clear();
return blargg_ok; return ratio_;
}
int Fir_Resampler_::input_needed( blargg_long output_count ) const
{
blargg_long input_count = 0;
unsigned long skip = skip_bits >> imp_phase;
int remain = res - imp_phase;
while ( (output_count -= 2) > 0 )
{
input_count += step + (skip & 1) * stereo;
skip >>= 1;
if ( !--remain )
{
skip = skip_bits;
remain = res;
}
output_count -= 2;
}
long input_extra = input_count - (write_pos - &buf [(width_ - 1) * stereo]);
if ( input_extra < 0 )
input_extra = 0;
return input_extra;
}
int Fir_Resampler_::avail_( blargg_long input_count ) const
{
int cycle_count = input_count / input_per_cycle;
int output_count = cycle_count * res * stereo;
input_count -= cycle_count * input_per_cycle;
blargg_ulong skip = skip_bits >> imp_phase;
int remain = res - imp_phase;
while ( input_count >= 0 )
{
input_count -= step + (skip & 1) * stereo;
skip >>= 1;
if ( !--remain )
{
skip = skip_bits;
remain = res;
}
output_count += 2;
}
return output_count;
}
int Fir_Resampler_::skip_input( long count )
{
int remain = write_pos - buf.begin();
int max_count = remain - width_ * stereo;
if ( count > max_count )
count = max_count;
remain -= count;
write_pos = &buf [remain];
memmove( buf.begin(), &buf [count], remain * sizeof buf [0] );
return count;
} }

View file

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

View file

@ -1,10 +1,11 @@
// Gb_Snd_Emu $vers. http://www.slack.net/~ant/ // Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/
#include "Gb_Apu.h" #include "Gb_Apu.h"
//#include "gb_apu_logger.h" #include <string.h>
#include <algorithm>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -17,390 +18,292 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
int const vol_reg = 0xFF24; unsigned const vol_reg = 0xFF24;
int const stereo_reg = 0xFF25; unsigned const status_reg = 0xFF26;
int const status_reg = 0xFF26;
int const wave_ram = 0xFF30;
int const power_mask = 0x80; using std::min;
using std::max;
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() Gb_Apu::Gb_Apu()
{ {
wave.wave_ram = &regs [wave_ram - io_addr]; square1.synth = &square_synth;
square2.synth = &square_synth;
wave.synth = &other_synth;
noise.synth = &other_synth;
oscs [0] = &square1; oscs [0] = &square1;
oscs [1] = &square2; oscs [1] = &square2;
oscs [2] = &wave; oscs [2] = &wave;
oscs [3] = &noise; oscs [3] = &noise;
for ( int i = osc_count; --i >= 0; ) for ( int i = 0; i < osc_count; i++ )
{ {
Gb_Osc& o = *oscs [i]; Gb_Osc& osc = *oscs [i];
o.regs = &regs [i * 5]; osc.regs = &regs [i * 5];
o.output = NULL; osc.output = 0;
o.outputs [0] = NULL; osc.outputs [0] = 0;
o.outputs [1] = NULL; osc.outputs [1] = 0;
o.outputs [2] = NULL; osc.outputs [2] = 0;
o.outputs [3] = NULL; osc.outputs [3] = 0;
o.norm_synth = &norm_synth;
o.fast_synth = &fast_synth;
} }
reduce_clicks_ = false;
set_tempo( 1.0 ); set_tempo( 1.0 );
volume_ = 1.0; volume( 1.0 );
reset(); reset();
} }
void Gb_Apu::run_until_( blip_time_t end_time ) void Gb_Apu::treble_eq( const blip_eq_t& eq )
{ {
if ( !frame_period ) square_synth.treble_eq( eq );
frame_time += end_time - last_time; other_synth.treble_eq( eq );
}
void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
require( (unsigned) index < osc_count );
require( (center && left && right) || (!center && !left && !right) );
Gb_Osc& osc = *oscs [index];
osc.outputs [1] = right;
osc.outputs [2] = left;
osc.outputs [3] = center;
osc.output = osc.outputs [osc.output_select];
}
void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, center, left, right );
}
void Gb_Apu::update_volume()
{
// TODO: doesn't handle differing left/right global volume (support would
// require modification to all oscillator code)
int data = regs [vol_reg - start_addr];
double vol = (max( data & 7, data >> 4 & 7 ) + 1) * volume_unit;
square_synth.volume( vol );
other_synth.volume( vol );
}
static unsigned char const powerup_regs [0x20] = {
0x80,0x3F,0x00,0xFF,0xBF, // square 1
0xFF,0x3F,0x00,0xFF,0xBF, // square 2
0x7F,0xFF,0x9F,0xFF,0xBF, // wave
0xFF,0xFF,0x00,0x00,0xBF, // noise
0x00, // left/right enables
0x77, // master volume
0x80, // power
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
void Gb_Apu::set_tempo( double t )
{
frame_period = 4194304 / 256; // 256 Hz
if ( t != 1.0 )
frame_period = blip_time_t (frame_period / t);
}
void Gb_Apu::reset()
{
next_frame_time = 0;
last_time = 0;
frame_count = 0;
square1.reset();
square2.reset();
wave.reset();
noise.reset();
noise.bits = 1;
wave.wave_pos = 0;
// avoid click at beginning
regs [vol_reg - start_addr] = 0x77;
update_volume();
regs [status_reg - start_addr] = 0x01; // force power
write_register( 0, status_reg, 0x00 );
static unsigned char const initial_wave [] = {
0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, // wave table
0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA
};
memcpy( wave.wave, initial_wave, sizeof initial_wave );
}
void Gb_Apu::run_until( blip_time_t end_time )
{
require( end_time >= last_time ); // end_time must not be before previous time
if ( end_time == last_time )
return;
while ( true ) while ( true )
{ {
// run oscillators blip_time_t time = next_frame_time;
blip_time_t time = end_time; if ( time > end_time )
if ( time > frame_time ) time = end_time;
time = frame_time;
square1.run( last_time, time ); // run oscillators
square2.run( last_time, time ); for ( int i = 0; i < osc_count; ++i )
wave .run( last_time, time ); {
noise .run( last_time, time ); Gb_Osc& osc = *oscs [i];
if ( osc.output )
{
osc.output->set_modified(); // TODO: misses optimization opportunities?
int playing = false;
if ( osc.enabled && osc.volume &&
(!(osc.regs [4] & osc.len_enabled_mask) || osc.length) )
playing = -1;
switch ( i )
{
case 0: square1.run( last_time, time, playing ); break;
case 1: square2.run( last_time, time, playing ); break;
case 2: wave .run( last_time, time, playing ); break;
case 3: noise .run( last_time, time, playing ); break;
}
}
}
last_time = time; last_time = time;
if ( time == end_time ) if ( time == end_time )
break; break;
// run frame sequencer next_frame_time += frame_period;
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: // 256 Hz actions
// 64 Hz square1.clock_length();
frame_phase = 0; square2.clock_length();
wave.clock_length();
noise.clock_length();
frame_count = (frame_count + 1) & 3;
if ( frame_count == 0 )
{
// 64 Hz actions
square1.clock_envelope(); square1.clock_envelope();
square2.clock_envelope(); square2.clock_envelope();
noise .clock_envelope(); noise.clock_envelope();
} }
}
}
inline void Gb_Apu::run_until( blip_time_t time ) if ( frame_count & 1 )
{ square1.clock_sweep(); // 128 Hz action
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 ) void Gb_Apu::end_frame( blip_time_t end_time )
{ {
#ifdef LOG_FRAME
LOG_FRAME( end_time );
#endif
if ( end_time > last_time ) if ( end_time > last_time )
run_until( end_time ); run_until( end_time );
frame_time -= end_time; assert( next_frame_time >= end_time );
assert( frame_time >= 0 ); next_frame_time -= end_time;
assert( last_time >= end_time );
last_time -= end_time; last_time -= end_time;
assert( last_time >= 0 );
} }
void Gb_Apu::silence_osc( Gb_Osc& o ) void Gb_Apu::write_register( blip_time_t time, unsigned addr, int data )
{
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 ); require( (unsigned) data < 0x100 );
int reg = addr - io_addr; int reg = addr - start_addr;
if ( (unsigned) reg >= io_size ) if ( (unsigned) reg >= register_count )
{
require( false );
return; 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 ); run_until( time );
if ( addr >= wave_ram ) int old_reg = regs [reg];
regs [reg] = data;
if ( addr < vol_reg )
{ {
wave.write( addr, data ); write_osc( reg / 5, reg, data );
} }
else else if ( addr == vol_reg && data != old_reg ) // global volume
{ {
int old_data = regs [reg]; // return all oscs to 0
regs [reg] = data; for ( int i = 0; i < osc_count; i++ )
if ( addr < vol_reg )
{ {
// Oscillator Gb_Osc& osc = *oscs [i];
write_osc( reg, old_data, data ); int amp = osc.last_amp;
osc.last_amp = 0;
if ( amp && osc.enabled && osc.output )
other_synth.offset( time, -amp, osc.output );
} }
else if ( addr == vol_reg && data != old_data )
{
// Master volume
for ( int i = osc_count; --i >= 0; )
silence_osc( *oscs [i] );
apply_volume(); if ( wave.outputs [3] )
} other_synth.offset( time, 30, wave.outputs [3] );
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(); update_volume();
if ( wave.mode != mode_dmg )
reset_lengths();
regs [status_reg - io_addr] = data; if ( wave.outputs [3] )
other_synth.offset( time, -30, wave.outputs [3] );
// oscs will update with new amplitude when next run
}
else if ( addr == 0xFF25 || addr == status_reg )
{
int mask = (regs [status_reg - start_addr] & 0x80) ? ~0 : 0;
int flags = regs [0xFF25 - start_addr] & mask;
// left/right assignments
for ( int i = 0; i < osc_count; i++ )
{
Gb_Osc& osc = *oscs [i];
osc.enabled &= mask;
int bits = flags >> i;
Blip_Buffer* old_output = osc.output;
osc.output_select = (bits >> 3 & 2) | (bits & 1);
osc.output = osc.outputs [osc.output_select];
if ( osc.output != old_output )
{
int amp = osc.last_amp;
osc.last_amp = 0;
if ( amp && old_output )
other_synth.offset( time, -amp, old_output );
}
} }
if ( addr == status_reg && data != old_reg )
{
if ( !(data & 0x80) )
{
for ( unsigned i = 0; i < sizeof powerup_regs; i++ )
{
if ( i != status_reg - start_addr )
write_register( time, i + start_addr, powerup_regs [i] );
}
}
else
{
//debug_printf( "APU powered on\n" );
}
}
}
else if ( addr >= 0xFF30 )
{
int index = (addr & 0x0F) * 2;
wave.wave [index] = data >> 4;
wave.wave [index + 1] = data & 0x0F;
} }
} }
int Gb_Apu::read_register( blip_time_t time, int addr ) int Gb_Apu::read_register( blip_time_t time, unsigned addr )
{ {
if ( addr >= status_reg ) run_until( time );
run_until( time );
int reg = addr - io_addr; int index = addr - start_addr;
if ( (unsigned) reg >= io_size ) require( (unsigned) index < register_count );
{ int data = regs [index];
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 ) if ( addr == status_reg )
{ {
data &= 0xF0; data = (data & 0x80) | 0x70;
data |= (int) square1.enabled << 0; for ( int i = 0; i < osc_count; i++ )
data |= (int) square2.enabled << 1; {
data |= (int) wave .enabled << 2; const Gb_Osc& osc = *oscs [i];
data |= (int) noise .enabled << 3; if ( osc.enabled && (osc.length || !(osc.regs [4] & osc.len_enabled_mask)) )
data |= 1 << i;
}
} }
return data; return data;

View file

@ -1,193 +1,90 @@
// Nintendo Game Boy sound hardware emulator with save state support // Nintendo Game Boy PAPU sound chip emulator
// Gb_Snd_Emu $vers // Gb_Snd_Emu 0.1.5
#ifndef GB_APU_H #ifndef GB_APU_H
#define GB_APU_H #define GB_APU_H
#include "Gb_Oscs.h" #include "Gb_Oscs.h"
struct gb_apu_state_t;
class Gb_Apu { class Gb_Apu {
public: public:
// Basics
// Sets buffer(s) to generate sound into, or NULL to mute. If only center is not NULL, // Set overall volume of all oscillators, where 1.0 is full volume
// 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 ); void volume( double );
// Sets treble equalization // Set treble equalization
void treble_eq( blip_eq_t const& ); void treble_eq( const blip_eq_t& );
// Treble and bass values for various hardware. // Outputs can be assigned to a single buffer for mono output, or to three
enum { // buffers for stereo output (using Stereo_Buffer to do the mixing).
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 // Assign all oscillator outputs to specified buffer(s). If buffer
// emulation accuracy, since the clicks are authentic. // is NULL, silences all oscillators.
void reduce_clicks( bool reduce = true ); void output( Blip_Buffer* mono );
void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Assign single oscillator output to buffer(s). Valid indicies are 0 to 3,
// which refer to Square 1, Square 2, Wave, and Noise. If buffer is NULL,
// silences oscillator.
enum { osc_count = 4 };
void osc_output( int index, Blip_Buffer* mono );
void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
// Reset oscillators and internal state
void reset();
// Reads and writes at addr must satisfy start_addr <= addr <= end_addr
enum { start_addr = 0xFF10 };
enum { end_addr = 0xFF3F };
enum { register_count = end_addr - start_addr + 1 };
// Write 'data' to address at specified time
void write_register( blip_time_t, unsigned addr, int data );
// Read from address at specified time
int read_register( blip_time_t, unsigned addr );
// Run all oscillators up to specified time, end current time frame, then
// start a new frame at time 0.
void end_frame( blip_time_t );
// Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the
// tempo in a music player.
void set_tempo( double ); void set_tempo( double );
// Saves full emulation state to state_out. Data format is portable and public:
// includes some extra space to avoid expansion in case more state needs Gb_Apu();
// 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: private:
// noncopyable // noncopyable
Gb_Apu( const Gb_Apu& ); Gb_Apu( const Gb_Apu& );
Gb_Apu& operator = ( const Gb_Apu& ); Gb_Apu& operator = ( const Gb_Apu& );
// Implementation
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]; Gb_Osc* oscs [osc_count];
blip_time_t last_time; // time sound emulator has been run to blip_time_t next_frame_time;
blip_time_t frame_period; // clocks between each frame sequencer step blip_time_t last_time;
double volume_; blip_time_t frame_period;
bool reduce_clicks_; double volume_unit;
int frame_count;
Gb_Sweep_Square square1; Gb_Square square1;
Gb_Square square2; Gb_Square square2;
Gb_Wave wave; Gb_Wave wave;
Gb_Noise noise; Gb_Noise noise;
blip_time_t frame_time; // time of next frame sequencer action uint8_t regs [register_count];
int frame_phase; // phase of next frame sequencer step Gb_Square::Synth square_synth; // used by squares
enum { regs_size = io_size + 0x10 }; Gb_Wave::Synth other_synth; // used by wave and noise
BOOST::uint8_t regs [regs_size];// last values written to registers
// large objects after everything else void update_volume();
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 run_until( blip_time_t );
void silence_osc( Gb_Osc& ); void write_osc( int index, int reg, int data );
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, inline void Gb_Apu::output( Blip_Buffer* b ) { output( b, b, b ); }
// with earlier versions properly opening later save states. Includes some
// room for expansion so the state size shouldn't increase. inline void Gb_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); }
struct gb_apu_state_t
inline void Gb_Apu::volume( double vol )
{ {
#if GB_APU_CUSTOM_STATE volume_unit = 0.60 / osc_count / 15 /*steps*/ / 2 /*?*/ / 8 /*master vol range*/ * vol;
// Values stored as plain int so your code can read/write them easily. update_volume();
// 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 #endif

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,10 @@
// Gb_Snd_Emu $vers. http://www.slack.net/~ant/ // Gb_Snd_Emu 0.1.5. http://www.slack.net/~ant/
#include "Gb_Apu.h" #include "Gb_Apu.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include <string.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 can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -15,698 +17,320 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
bool const cgb_02 = false; // enables bug in early CGB units that causes problems in some games // Gb_Osc
bool const cgb_05 = false; // enables CGB-05 zombie behavior
int const trigger_mask = 0x80;
int const length_enabled = 0x40;
void Gb_Osc::reset() void Gb_Osc::reset()
{ {
output = NULL; delay = 0;
last_amp = 0; last_amp = 0;
delay = 0; length = 0;
phase = 0; output_select = 3;
enabled = false; output = outputs [output_select];
} }
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() void Gb_Osc::clock_length()
{ {
if ( (regs [4] & length_enabled) && length_ctr ) if ( (regs [4] & len_enabled_mask) && length )
{ length--;
if ( --length_ctr <= 0 )
enabled = false;
}
} }
inline int Gb_Env::reload_env_timer() // Gb_Env
{
int raw = regs [2] & 7;
env_delay = (raw ? raw : 8);
return raw;
}
void Gb_Env::clock_envelope() void Gb_Env::clock_envelope()
{ {
if ( env_enabled && --env_delay <= 0 && reload_env_timer() ) if ( env_delay && !--env_delay )
{ {
int v = volume + (regs [2] & 0x08 ? +1 : -1); env_delay = regs [2] & 7;
if ( 0 <= v && v <= 15 ) int v = volume - 1 + (regs [2] >> 2 & 2);
if ( (unsigned) v < 15 )
volume = v; volume = v;
else
env_enabled = false;
} }
} }
inline void Gb_Sweep_Square::reload_sweep_timer() bool Gb_Env::write_register( int reg, int data )
{ {
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 ) switch ( reg )
{ {
case 1: case 1:
length_ctr = max_len - (data & (max_len - 1)); length = 64 - (regs [1] & 0x3F);
break; break;
case 2: case 2:
if ( !dac_enabled() ) if ( !(data >> 4) )
enabled = false; 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; break;
case 4: case 4:
if ( write_trig( frame_phase, max_len, old ) ) if ( data & trigger )
{ {
env_delay = regs [2] & 7;
volume = regs [2] >> 4; volume = regs [2] >> 4;
reload_env_timer(); enabled = true;
env_enabled = true; if ( length == 0 )
if ( frame_phase == 7 ) length = 64;
env_delay++;
if ( !dac_enabled() )
enabled = false;
return true; return true;
} }
} }
return false; return false;
} }
bool Gb_Square::write_register( int frame_phase, int reg, int old_data, int data ) // Gb_Square
void Gb_Square::reset()
{ {
bool result = Gb_Env::write_register( frame_phase, reg, old_data, data ); phase = 0;
if ( result ) sweep_freq = 0;
delay = (delay & (4 * clk_mul - 1)) + period(); sweep_delay = 0;
return result; Gb_Env::reset();
} }
inline void Gb_Noise::write_register( int frame_phase, int reg, int old_data, int data ) void Gb_Square::clock_sweep()
{ {
if ( Gb_Env::write_register( frame_phase, reg, old_data, data ) ) int sweep_period = (regs [0] & period_mask) >> 4;
if ( sweep_period && sweep_delay && !--sweep_delay )
{ {
phase = 0x7FFF; sweep_delay = sweep_period;
delay += 8 * clk_mul; regs [3] = sweep_freq & 0xFF;
regs [4] = (regs [4] & ~0x07) | (sweep_freq >> 8 & 0x07);
int offset = sweep_freq >> (regs [0] & shift_mask);
if ( regs [0] & 0x08 )
offset = -offset;
sweep_freq += offset;
if ( sweep_freq < 0 )
{
sweep_freq = 0;
}
else if ( sweep_freq >= 2048 )
{
sweep_delay = 0; // don't modify channel frequency any further
sweep_freq = 2048; // silence sound immediately
}
} }
} }
inline void Gb_Sweep_Square::write_register( int frame_phase, int reg, int old_data, int data ) void Gb_Square::run( blip_time_t time, blip_time_t end_time, int playing )
{ {
if ( reg == 0 && sweep_enabled && sweep_neg && !(data & 0x08) ) if ( sweep_freq == 2048 )
enabled = false; // sweep negate disabled after used playing = false;
if ( Gb_Square::write_register( frame_phase, reg, old_data, data ) ) static unsigned char const table [4] = { 1, 2, 4, 6 };
int const duty = table [regs [1] >> 6];
int amp = volume & playing;
if ( phase >= duty )
amp = -amp;
int frequency = this->frequency();
if ( unsigned (frequency - 1) > 2040 ) // frequency < 1 || frequency > 2041
{ {
sweep_freq = frequency(); // really high frequency results in DC at half volume
sweep_neg = false; amp = volume >> 1;
reload_sweep_timer(); playing = false;
sweep_enabled = (regs [0] & (period_mask | shift_mask)) != 0;
if ( regs [0] & shift_mask )
calc_sweep( false );
} }
{
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( !playing )
time = end_time;
if ( time < end_time )
{
int const period = (2048 - frequency) * 4;
Blip_Buffer* const output = this->output;
int phase = this->phase;
int delta = amp * 2;
do
{
phase = (phase + 1) & 7;
if ( phase == 0 || phase == duty )
{
delta = -delta;
synth->offset_inline( time, delta, output );
}
time += period;
}
while ( time < end_time );
this->phase = phase;
last_amp = delta >> 1;
}
delay = time - end_time;
} }
void Gb_Wave::corrupt_wave() // Gb_Noise
void Gb_Noise::run( blip_time_t time, blip_time_t end_time, int playing )
{ {
int pos = ((phase + 1) & (bank_size - 1)) >> 1; int amp = volume & playing;
if ( pos < 4 ) int tap = 13 - (regs [3] & 8);
wave_ram [0] = wave_ram [pos]; if ( bits >> tap & 2 )
else amp = -amp;
for ( int i = 4; --i >= 0; )
wave_ram [i] = wave_ram [(pos & ~3) + i]; {
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( !playing )
time = end_time;
if ( time < end_time )
{
static unsigned char const table [8] = { 8, 16, 32, 48, 64, 80, 96, 112 };
int period = table [regs [3] & 7] << (regs [3] >> 4);
// keep parallel resampled time to eliminate time conversion in the loop
Blip_Buffer* const output = this->output;
const blip_resampled_time_t resampled_period =
output->resampled_duration( period );
blip_resampled_time_t resampled_time = output->resampled_time( time );
unsigned bits = this->bits;
int delta = amp * 2;
do
{
unsigned changed = (bits >> tap) + 1;
time += period;
bits <<= 1;
if ( changed & 2 )
{
delta = -delta;
bits |= 1;
synth->offset_resampled( resampled_time, delta, output );
}
resampled_time += resampled_period;
}
while ( time < end_time );
this->bits = bits;
last_amp = delta >> 1;
}
delay = time - end_time;
} }
inline void Gb_Wave::write_register( int frame_phase, int reg, int old_data, int data ) // Gb_Wave
{
int const max_len = 256;
inline void Gb_Wave::write_register( int reg, int data )
{
switch ( reg ) switch ( reg )
{ {
case 0: case 0:
if ( !dac_enabled() ) if ( !(data & 0x80) )
enabled = false; enabled = false;
break; break;
case 1: case 1:
length_ctr = max_len - data; length = 256 - regs [1];
break;
case 2:
volume = data >> 5 & 3;
break; break;
case 4: case 4:
bool was_enabled = enabled; if ( data & trigger & regs [0] )
if ( write_trig( frame_phase, max_len, old_data ) )
{ {
if ( !dac_enabled() ) wave_pos = 0;
enabled = false; enabled = true;
else if ( mode == Gb_Apu::mode_dmg && was_enabled && if ( length == 0 )
(unsigned) (delay - 2 * clk_mul) < 2 * clk_mul ) length = 256;
corrupt_wave();
phase = 0;
delay = period() + 6 * clk_mul;
} }
} }
} }
void Gb_Apu::write_osc( int reg, int old_data, int data ) void Gb_Wave::run( blip_time_t time, blip_time_t end_time, int playing )
{
int volume_shift = (volume - 1) & 7; // volume = 0 causes shift = 7
int frequency;
{
int amp = (wave [wave_pos] >> volume_shift & playing) * 2;
frequency = this->frequency();
if ( unsigned (frequency - 1) > 2044 ) // frequency < 1 || frequency > 2045
{
amp = 30 >> volume_shift & playing;
playing = false;
}
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset( time, delta, output );
}
}
time += delay;
if ( !playing )
time = end_time;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
int const period = (2048 - frequency) * 2;
int wave_pos = (this->wave_pos + 1) & (wave_size - 1);
do
{
int amp = (wave [wave_pos] >> volume_shift) * 2;
wave_pos = (wave_pos + 1) & (wave_size - 1);
int delta = amp - last_amp;
if ( delta )
{
last_amp = amp;
synth->offset_inline( time, delta, output );
}
time += period;
}
while ( time < end_time );
this->wave_pos = (wave_pos - 1) & (wave_size - 1);
}
delay = time - end_time;
}
// Gb_Apu::write_osc
void Gb_Apu::write_osc( int index, int reg, int data )
{ {
int index = (reg * 3 + 3) >> 4; // avoids divide
assert( index == reg / 5 );
reg -= index * 5; reg -= index * 5;
Gb_Square* sq = &square2;
switch ( index ) switch ( index )
{ {
case 0: square1.write_register( frame_phase, reg, old_data, data ); break; case 0:
case 1: square2.write_register( frame_phase, reg, old_data, data ); break; sq = &square1; // FALLTHRU
case 2: wave .write_register( frame_phase, reg, old_data, data ); break; case 1:
case 3: noise .write_register( frame_phase, reg, old_data, data ); break; if ( sq->write_register( reg, data ) && index == 0 )
{
square1.sweep_freq = square1.frequency();
if ( (regs [0] & sq->period_mask) && (regs [0] & sq->shift_mask) )
{
square1.sweep_delay = 1; // cause sweep to recalculate now
square1.clock_sweep();
}
}
break;
case 2:
wave.write_register( reg, data );
break;
case 3:
if ( noise.write_register( reg, data ) )
noise.bits = 0x7FFF;
} }
} }
// 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

@ -1,188 +1,83 @@
// Private oscillators used by Gb_Apu // Private oscillators used by Gb_Apu
// Gb_Snd_Emu $vers // Gb_Snd_Emu 0.1.5
#ifndef GB_OSCS_H #ifndef GB_OSCS_H
#define GB_OSCS_H #define GB_OSCS_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
#ifndef GB_APU_OVERCLOCK struct Gb_Osc
#define GB_APU_OVERCLOCK 1 {
#endif enum { trigger = 0x80 };
enum { len_enabled_mask = 0x40 };
#if GB_APU_OVERCLOCK & (GB_APU_OVERCLOCK - 1) Blip_Buffer* outputs [4]; // NULL, right, left, center
#error "GB_APU_OVERCLOCK must be a power of 2" Blip_Buffer* output;
#endif int output_select;
uint8_t* regs; // osc's 5 registers
class Gb_Osc { int delay;
protected: int last_amp;
int volume;
int length;
int enabled;
// 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(); void reset();
void clock_length();
int frequency() const { return (regs [4] & 7) * 0x100 + regs [3]; }
}; };
class Gb_Env : public Gb_Osc { struct Gb_Env : Gb_Osc
public: {
int env_delay; int env_delay;
int volume;
bool env_enabled;
void reset();
void clock_envelope(); void clock_envelope();
bool write_register( int frame_phase, int reg, int old_data, int data ); bool write_register( int, int );
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 { struct Gb_Square : 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 { period_mask = 0x70 };
enum { shift_mask = 0x07 }; enum { shift_mask = 0x07 };
void calc_sweep( bool update ); typedef Blip_Synth<blip_good_quality,1> Synth;
void reload_sweep_timer(); Synth const* synth;
int sweep_delay;
int sweep_freq;
int phase;
void reset();
void clock_sweep();
void run( blip_time_t, blip_time_t, int playing );
}; };
class Gb_Noise : public Gb_Env { struct Gb_Noise : 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 ); typedef Blip_Synth<blip_med_quality,1> Synth;
return (index < 0 ? 0xFF : wave_bank() [index]); Synth const* synth;
} unsigned bits;
inline void Gb_Wave::write( int addr, int data ) void run( blip_time_t, blip_time_t, int playing );
};
struct Gb_Wave : Gb_Osc
{ {
int index = access( addr ); typedef Blip_Synth<blip_med_quality,1> Synth;
if ( index >= 0 ) Synth const* synth;
wave_bank() [index] = data;; int wave_pos;
enum { wave_size = 32 };
uint8_t wave [wave_size];
void write_register( int, int );
void run( blip_time_t, blip_time_t, int playing );
};
inline void Gb_Env::reset()
{
env_delay = 0;
Gb_Osc::reset();
} }
#endif #endif

View file

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

View file

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

View file

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

View file

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

View file

@ -1,63 +1,88 @@
// Nintendo Game Boy GBS music file emulator // Nintendo Game Boy GBS music file emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef GBS_EMU_H #ifndef GBS_EMU_H
#define GBS_EMU_H #define GBS_EMU_H
#include "Classic_Emu.h" #include "Classic_Emu.h"
#include "Gbs_Core.h" #include "Gb_Apu.h"
#include "Gb_Cpu.h"
class Gbs_Emu : public Classic_Emu { class Gbs_Emu : private Gb_Cpu, public Classic_Emu {
typedef Gb_Cpu cpu;
public: public:
// Equalizer profiles for Game Boy speaker and headphones // Equalizer profiles for Game Boy Color speaker and headphones
static equalizer_t const handheld_eq; static equalizer_t const handheld_eq;
static equalizer_t const headphones_eq; static equalizer_t const headphones_eq;
static equalizer_t const cgb_eq; // Game Boy Color headphones have less bass
// GBS file header (see Gbs_Core.h) // GBS file header
typedef Gbs_Core::header_t header_t; enum { header_size = 112 };
struct header_t
{
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];
char author [32];
char copyright [32];
};
// Header for currently loaded file // Header for currently loaded file
header_t const& header() const { return core_.header(); } header_t const& header() const { return 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; } static gme_type_t static_type() { return gme_gbs_type; }
Gbs_Core& core() { return core_; } public:
// deprecated
using Music_Emu::load;
blargg_err_t load( header_t const& h, Data_Reader& in ) // use Remaining_Reader
{ return load_remaining_( &h, sizeof h, in ); }
blargg_err_t hash_( Hash_Function& ) const;
// Internal
public: public:
Gbs_Emu(); Gbs_Emu();
~Gbs_Emu(); ~Gbs_Emu();
protected: protected:
// Overrides blargg_err_t track_info_( track_info_t*, int track ) const;
virtual blargg_err_t track_info_( track_info_t*, int track ) const; blargg_err_t load_( Data_Reader& );
virtual blargg_err_t load_( Data_Reader& ); blargg_err_t start_track_( int );
virtual blargg_err_t start_track_( int ); blargg_err_t run_clocks( blip_time_t&, int );
virtual blargg_err_t run_clocks( blip_time_t&, int ); void set_tempo_( double );
virtual void set_tempo_( double ); void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); void update_eq( blip_eq_t const& );
virtual void update_eq( blip_eq_t const& ); void unload();
virtual void unload();
private: private:
sound_t sound_hardware; // rom
Gbs_Core core_; enum { bank_size = 0x4000 };
Rom_Data<bank_size> rom;
void set_bank( int );
// timer
blip_time_t cpu_time;
blip_time_t play_period;
blip_time_t next_play;
void update_timer();
header_t header_;
void cpu_jsr( gb_addr_t );
public: private: friend class Gb_Cpu;
blip_time_t clock() const { return cpu_time - cpu::remain(); }
enum { joypad_addr = 0xFF00 };
enum { ram_addr = 0xA000 };
enum { hi_page = 0xFF00 - ram_addr };
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
Gb_Apu apu;
int cpu_read( gb_addr_t );
void cpu_write( gb_addr_t, int );
}; };
#endif #endif

View file

@ -1,8 +1,11 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Gme_File.h" #include "Gme_File.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you #include "blargg_endian.h"
#include <string.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 can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -15,34 +18,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
void Gme_File::unload() const char* const gme_wrong_file_type = "Wrong file type for this emulator";
{
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() void Gme_File::clear_playlist()
{ {
@ -51,7 +27,92 @@ void Gme_File::clear_playlist()
track_count_ = raw_track_count_; track_count_ = raw_track_count_;
} }
void Gme_File::copy_field_( char out [], const char* in, int in_size ) void Gme_File::unload()
{
clear_playlist(); // *before* clearing track count
track_count_ = 0;
raw_track_count_ = 0;
file_data.clear();
}
Gme_File::Gme_File()
{
type_ = 0;
user_data_ = 0;
user_cleanup_ = 0;
unload(); // clears fields
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
}
Gme_File::~Gme_File()
{
if ( user_cleanup_ )
user_cleanup_( user_data_ );
}
blargg_err_t Gme_File::load_mem_( byte const* data, long size )
{
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
Mem_File_Reader in( data, size );
return load_( in );
}
blargg_err_t Gme_File::load_( Data_Reader& in )
{
RETURN_ERR( file_data.resize( in.remain() ) );
RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
return load_mem_( file_data.begin(), file_data.size() );
}
// public load functions call this at beginning
void Gme_File::pre_load() { unload(); }
void Gme_File::post_load_() { }
// public load functions call this at end
blargg_err_t Gme_File::post_load( blargg_err_t err )
{
if ( !track_count() )
set_track_count( type()->track_count );
if ( !err )
post_load_();
else
unload();
return err;
}
// Public load functions
blargg_err_t Gme_File::load_mem( void const* in, long size )
{
pre_load();
return post_load( load_mem_( (byte const*) in, size ) );
}
blargg_err_t Gme_File::load( Data_Reader& in )
{
pre_load();
return post_load( load_( in ) );
}
blargg_err_t Gme_File::load_file( const char* path )
{
pre_load();
GME_FILE_READER in;
RETURN_ERR( in.open( path ) );
return post_load( load_( in ) );
}
blargg_err_t Gme_File::load_remaining_( void const* h, long s, Data_Reader& in )
{
Remaining_Reader rem( h, s, &in );
return load( rem );
}
// Track info
void Gme_File::copy_field_( char* out, const char* in, int in_size )
{ {
if ( !in || !*in ) if ( !in || !*in )
return; return;
@ -85,7 +146,7 @@ void Gme_File::copy_field_( char out [], const char* in, int in_size )
out [0] = 0; out [0] = 0;
} }
void Gme_File::copy_field_( char out [], const char* in ) void Gme_File::copy_field_( char* out, const char* in )
{ {
copy_field_( out, in, max_field_ ); copy_field_( out, in, max_field_ );
} }
@ -93,7 +154,7 @@ void Gme_File::copy_field_( char out [], const char* in )
blargg_err_t Gme_File::remap_track_( int* track_io ) const blargg_err_t Gme_File::remap_track_( int* track_io ) const
{ {
if ( (unsigned) *track_io >= (unsigned) track_count() ) if ( (unsigned) *track_io >= (unsigned) track_count() )
return BLARGG_ERR( BLARGG_ERR_CALLER, "invalid track" ); return "Invalid track";
if ( (unsigned) *track_io < (unsigned) playlist.size() ) if ( (unsigned) *track_io < (unsigned) playlist.size() )
{ {
@ -102,44 +163,44 @@ blargg_err_t Gme_File::remap_track_( int* track_io ) const
if ( e.track >= 0 ) if ( e.track >= 0 )
{ {
*track_io = e.track; *track_io = e.track;
// TODO: really needs to be removed?
if ( !(type_->flags_ & 0x02) ) if ( !(type_->flags_ & 0x02) )
*track_io -= e.decimal_track; *track_io -= e.decimal_track;
} }
if ( *track_io >= raw_track_count_ ) if ( *track_io >= raw_track_count_ )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "invalid track in m3u playlist" ); return "Invalid track in m3u playlist";
} }
else else
{ {
check( !playlist.size() ); check( !playlist.size() );
} }
return blargg_ok; return 0;
} }
blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
{ {
out->track_count = track_count(); out->track_count = track_count();
out->length = -1; out->length = -1;
out->loop_length = -1; out->loop_length = -1;
out->intro_length = -1; out->intro_length = -1;
out->fade_length = -1; out->fade_length = -1;
out->play_length = -1; out->play_length = -1;
out->repeat_count = -1; out->repeat_count = -1;
out->song [0] = 0; out->song [0] = 0;
out->game [0] = 0;
out->author [0] = 0; out->game [0] = 0;
out->composer [0] = 0; out->author [0] = 0;
out->engineer [0] = 0; out->composer [0] = 0;
out->sequencer [0] = 0; out->engineer [0] = 0;
out->tagger [0] = 0; out->sequencer [0] = 0;
out->tagger [0] = 0;
out->copyright [0] = 0; out->copyright [0] = 0;
out->date [0] = 0; out->date [0] = 0;
out->comment [0] = 0; out->comment [0] = 0;
out->dumper [0] = 0; out->dumper [0] = 0;
out->system [0] = 0; out->system [0] = 0;
out->disc [0] = 0; out->disc [0] = 0;
out->track [0] = 0; out->track [0] = 0;
out->ost [0] = 0; out->ost [0] = 0;
copy_field_( out->system, type()->system ); copy_field_( out->system, type()->system );
@ -151,33 +212,23 @@ blargg_err_t Gme_File::track_info( track_info_t* out, int track ) const
if ( playlist.size() ) if ( playlist.size() )
{ {
M3u_Playlist::info_t const& i = playlist.info(); M3u_Playlist::info_t const& i = playlist.info();
copy_field_( out->game , i.title ); copy_field_( out->game , i.title );
copy_field_( out->author , i.artist ); copy_field_( out->author, i.artist );
copy_field_( out->engineer , i.engineer ); copy_field_( out->engineer, i.engineer );
copy_field_( out->composer , i.composer ); copy_field_( out->composer, i.composer );
copy_field_( out->sequencer, i.sequencer ); copy_field_( out->sequencer, i.sequencer );
copy_field_( out->copyright, i.copyright ); copy_field_( out->copyright, i.copyright );
copy_field_( out->dumper , i.ripping ); copy_field_( out->dumper, i.ripping );
copy_field_( out->tagger , i.tagging ); copy_field_( out->tagger, i.tagging );
copy_field_( out->date , i.date ); copy_field_( out->date, i.date );
M3u_Playlist::entry_t const& e = playlist [track]; M3u_Playlist::entry_t const& e = playlist [track];
copy_field_( out->song, e.name );
if ( e.length >= 0 ) out->length = e.length; if ( e.length >= 0 ) out->length = e.length;
if ( e.intro >= 0 ) out->intro_length = e.intro; if ( e.intro >= 0 ) out->intro_length = e.intro;
if ( e.loop >= 0 ) out->loop_length = e.loop; if ( e.loop >= 0 ) out->loop_length = e.loop;
if ( e.fade >= 0 ) out->fade_length = e.fade; if ( e.fade >= 0 ) out->fade_length = e.fade;
if ( e.repeat >= 0 ) out->repeat_count = e.repeat; if ( e.repeat >= 0 ) out->repeat_count = e.repeat;
copy_field_( out->song, e.name );
} }
return 0;
// 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

@ -1,153 +1,190 @@
// Common interface for track information // Common interface to game music file loading and information
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef GME_FILE_H #ifndef GME_FILE_H
#define GME_FILE_H #define GME_FILE_H
#include "gme.h" #include "gme.h"
#include "Gme_Loader.h" #include "blargg_common.h"
#include "Data_Reader.h"
#include "M3u_Playlist.h" #include "M3u_Playlist.h"
struct track_info_t // Error returned if file is wrong type
{ //extern const char gme_wrong_file_type []; // declared in gme.h
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_ struct gme_type_t_
{ {
const char* system; /* name of system this music file type is generally for */ 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 */ 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_emu)(); /* Create new emulator for this type (useful in C++ only) */
Music_Emu* (*new_info)();/* Create new info reader for this type (C++ only) */ Music_Emu* (*new_info)(); /* Create new info reader for this type */
/* internal */ /* internal */
const char* extension_; const char* extension_;
int flags_; int flags_;
}; };
/* Emulator type constants for each supported file type */ struct track_info_t
extern const gme_type_t_ {
gme_ay_type [1], long track_count;
gme_gbs_type [1],
gme_gym_type [1], /* times in milliseconds; -1 if unknown */
gme_hes_type [1], long length;
gme_kss_type [1], long intro_length;
gme_nsf_type [1], long loop_length;
gme_nsfe_type [1], long fade_length;
gme_sap_type [1], long repeat_count;
gme_sfm_type [1],
gme_sgc_type [1], /* Length if available, otherwise intro_length+loop_length*2 if available,
gme_spc_type [1], * otherwise a default of 150000 (2.5 minutes) */
gme_vgm_type [1], long play_length;
gme_vgz_type [1];
/* 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 };
struct Gme_File {
public:
// File loading
// Each loads game music data from a file and returns an error if
// file is wrong type or is seriously corrupt. They also set warning
// string for minor problems.
// Load from file on disk
blargg_err_t load_file( const char* path );
// Load from custom data source (see Data_Reader.h)
blargg_err_t load( Data_Reader& );
// Load from file already read into memory. Keeps pointer to data, so you
// must not free it until you're done with the file.
blargg_err_t load_mem( void const* data, long size );
// Load 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();
// Informational
// Type of emulator. For example if this returns gme_nsfe_type, this object
// is an NSFE emulator, and you can cast to an Nsfe_Emu* if necessary.
gme_type_t type() const;
// Most recent warning string, or NULL if none. Clears current warning after
// returning.
const char* warning();
// Number of tracks or 0 if no file has been loaded
int track_count() const;
// Get 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
// Set/get 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_; }
// Register 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; }
bool is_archive = false;
virtual blargg_err_t load_archive( const char* ) { return gme_wrong_file_type; }
public:
// deprecated
int error_count() const; // use warning()
public:
Gme_File();
virtual ~Gme_File();
BLARGG_DISABLE_NOTHROW
typedef uint8_t byte;
protected:
// Services
void set_track_count( int n ) { track_count_ = raw_track_count_ = n; }
void set_warning( const char* s ) { warning_ = s; }
void set_type( gme_type_t t ) { type_ = t; }
blargg_err_t load_remaining_( void const* header, long header_size, Data_Reader& remaining );
// Overridable
virtual void unload(); // called before loading file and if loading fails
virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
virtual blargg_err_t load_mem_( byte const* data, long size ); // use data in memory
virtual blargg_err_t track_info_( track_info_t* out, int track ) const = 0;
virtual void pre_load();
virtual void post_load_();
virtual void clear_playlist_() { }
public:
blargg_err_t remap_track_( int* track_io ) const; // need by Music_Emu
private:
// noncopyable
Gme_File( const Gme_File& );
Gme_File& operator = ( const Gme_File& );
gme_type_t type_;
int track_count_;
int raw_track_count_;
const char* warning_;
void* user_data_;
gme_user_cleanup_t user_cleanup_;
M3u_Playlist playlist;
char playlist_warning [64];
blargg_vector<byte> file_data; // only if loaded into memory using default load
blargg_err_t load_m3u_( blargg_err_t );
blargg_err_t post_load( blargg_err_t err );
public:
// track_info field copying
enum { max_field_ = 255 };
static void copy_field_( char* out, const char* in );
static void copy_field_( char* out, const char* in, int len );
};
Music_Emu* gme_new_( Music_Emu*, long sample_rate );
#define GME_COPY_FIELD( in, out, name ) \ #define GME_COPY_FIELD( in, out, name ) \
{ Gme_File::copy_field_( out->name, in.name, sizeof in.name ); } { Gme_File::copy_field_( out->name, in.name, sizeof in.name ); }
inline gme_type_t Gme_File::type() const { return type_; } #ifndef GME_FILE_READER
#define GME_FILE_READER Std_File_Reader
#elif defined (GME_FILE_READER_INCLUDE)
#include GME_FILE_READER_INCLUDE
#endif
inline gme_type_t Gme_File::type() const { return type_; }
inline int Gme_File::error_count() const { return warning_ != 0; }
inline int Gme_File::track_count() const { return track_count_; } inline 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; } inline const char* Gme_File::warning()
{
const char* s = warning_;
warning_ = 0;
return s;
}
#endif #endif

View file

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

View file

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

View file

@ -1,10 +1,11 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Gym_Emu.h" #include "Gym_Emu.h"
#include "blargg_endian.h" #include "blargg_endian.h"
#include <string.h>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -17,183 +18,171 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
double const min_tempo = 0.25; double const min_tempo = 0.25;
double const oversample = 5 / 3.0; double const oversample_factor = 5 / 3.0;
double const fm_gain = 3.0; double const fm_gain = 3.0;
int const base_clock = 53700300; const long base_clock = 53700300;
int const clock_rate = base_clock / 15; const long clock_rate = base_clock / 15;
Gym_Emu::Gym_Emu() Gym_Emu::Gym_Emu()
{ {
resampler.set_callback( play_frame_, this ); data = 0;
pos = NULL; pos = 0;
disable_oversampling_ = false;
set_type( gme_gym_type ); set_type( gme_gym_type );
static const char* const names [] = {
"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
};
set_voice_names( names );
set_silence_lookahead( 1 ); // tracks should already be trimmed set_silence_lookahead( 1 ); // tracks should already be trimmed
pcm_buf = stereo_buf.center();
} }
Gym_Emu::~Gym_Emu() { } Gym_Emu::~Gym_Emu() { }
// Track info // Track info
static void get_gym_info( Gym_Emu::header_t const& h, int length, track_info_t* out ) static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out )
{ {
if ( 0 != memcmp( h.tag, "GYMX", 4 ) ) if ( !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; length = length * 50 / 3; // 1000 / 60
out->loop_length = length - out->intro_length; long 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
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 );
} }
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 ) blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const
{ {
out.hash_( &h.loop_start[0], sizeof(h.loop_start) ); get_gym_info( header_, track_length(), out );
out.hash_( &h.packed[0], sizeof(h.packed) ); return 0;
out.hash_( data, data_size );
} }
static int gym_track_length( byte const p [], byte const* end ) static long gym_track_length( byte const* p, byte const* end )
{ {
int time = 0; long time = 0;
while ( p < end ) while ( p < end )
{ {
switch ( *p++ ) switch ( *p++ )
{ {
case 0: case 0:
time++; time++;
break; break;
case 1: case 1:
case 2: case 2:
p += 2; p += 2;
break; break;
case 3: case 3:
p += 1; p += 1;
break; break;
} }
} }
return time; return time;
} }
blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); }
{
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 ) static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 )
{ {
if ( size < 4 ) if ( size < 4 )
return blargg_err_file_type; return gme_wrong_file_type;
if ( memcmp( in, "GYMX", 4 ) == 0 ) if ( memcmp( in, "GYMX", 4 ) == 0 )
{ {
if ( size < Gym_Emu::header_t::size + 1 ) if ( size < Gym_Emu::header_size + 1 )
return blargg_err_file_type; return gme_wrong_file_type;
if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 ) if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 )
return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "packed GYM file" ); return "Packed GYM file not supported";
if ( data_offset ) if ( data_offset )
*data_offset = Gym_Emu::header_t::size; *data_offset = Gym_Emu::header_size;
} }
else if ( *in > 3 ) else if ( *in > 3 )
{ {
return blargg_err_file_type; return gme_wrong_file_type;
} }
return blargg_ok; return 0;
} }
struct Gym_File : Gme_Info_ struct Gym_File : Gme_Info_
{ {
byte const* file_begin;
byte const* file_end;
int data_offset; int data_offset;
Gym_File() { set_type( gme_gym_type ); } Gym_File() { set_type( gme_gym_type ); }
blargg_err_t load_mem_( byte const in [], int size ) blargg_err_t load_mem_( byte const* in, long size )
{ {
file_begin = in;
file_end = in + size;
data_offset = 0; data_offset = 0;
return check_header( in, size, &data_offset ); return check_header( in, size, &data_offset );
} }
blargg_err_t track_info_( track_info_t* out, int ) const blargg_err_t track_info_( track_info_t* out, int ) const
{ {
int length = gym_track_length( &file_begin() [data_offset], file_end() ); long length = gym_track_length( &file_begin [data_offset], file_end );
get_gym_info( *(Gym_Emu::header_t const*) file_begin(), length, out ); get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out );
return blargg_ok; return 0;
}
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_emu () { return BLARGG_NEW Gym_Emu ; }
static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; } 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 }}; static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 };
extern gme_type_t const gme_gym_type = &gme_gym_type_;
// Setup // Setup
blargg_err_t Gym_Emu::set_sample_rate_( int sample_rate ) blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate )
{ {
blip_eq_t eq( -32, 8000, sample_rate ); blip_eq_t eq( -32, 8000, sample_rate );
apu.treble_eq( eq ); apu.treble_eq( eq );
pcm_synth.treble_eq( eq ); dac_synth.treble_eq( eq );
apu.volume( 0.135 * fm_gain * gain() ); apu.volume( 0.135 * fm_gain * gain() );
dac_synth.volume( 0.125 / 256 * fm_gain * gain() );
double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() );
fm_sample_rate = sample_rate * factor;
double factor = oversample; RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) );
if ( disable_oversampling_ ) blip_buf.clock_rate( clock_rate );
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) ) ); RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) );
stereo_buf.clock_rate( clock_rate ); RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) );
RETURN_ERR( fm.set_rate( fm_rate, base_clock / 7.0 ) ); return 0;
RETURN_ERR( resampler.reset( (int) (1.0 / 60 / min_tempo * sample_rate) ) );
return blargg_ok;
} }
void Gym_Emu::set_tempo_( double t ) void Gym_Emu::set_tempo_( double t )
@ -204,11 +193,10 @@ void Gym_Emu::set_tempo_( double t )
return; return;
} }
if ( stereo_buf.sample_rate() ) if ( blip_buf.sample_rate() )
{ {
double denom = tempo() * 60; clocks_per_frame = long (clock_rate / 60 / tempo());
clocks_per_frame = (int) (clock_rate / denom); Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) );
resampler.resize( (int) (sample_rate() / denom) );
} }
} }
@ -216,31 +204,27 @@ void Gym_Emu::mute_voices_( int mask )
{ {
Music_Emu::mute_voices_( mask ); Music_Emu::mute_voices_( mask );
fm.mute_voices( mask ); fm.mute_voices( mask );
apu.set_output( (mask & 0x80) ? 0 : stereo_buf.center() ); dac_muted = (mask & 0x40) != 0;
pcm_synth.volume( (mask & 0x40) ? 0.0 : 0.125 / 256 * fm_gain * gain() ); apu.output( (mask & 0x80) ? 0 : &blip_buf );
} }
blargg_err_t Gym_Emu::load_mem_( byte const in [], int size ) blargg_err_t Gym_Emu::load_mem_( byte const* in, long size )
{ {
assert( offsetof (header_t,packed [4]) == header_t::size ); assert( offsetof (header_t,packed [4]) == header_size );
log_offset = 0; int offset = 0;
RETURN_ERR( check_header( in, size, &log_offset ) ); RETURN_ERR( check_header( in, size, &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 ); set_voice_count( 8 );
if ( log_offset ) data = in + offset;
data_end = in + size;
loop_begin = 0;
if ( offset )
header_ = *(header_t const*) in; header_ = *(header_t const*) in;
else else
memset( &header_, 0, sizeof header_ ); memset( &header_, 0, sizeof header_ );
return blargg_ok; return 0;
} }
// Emulation // Emulation
@ -249,27 +233,26 @@ blargg_err_t Gym_Emu::start_track_( int track )
{ {
RETURN_ERR( Music_Emu::start_track_( track ) ); RETURN_ERR( Music_Emu::start_track_( track ) );
pos = log_begin(); pos = data;
loop_remain = get_le32( header_.loop_start ); loop_remain = get_le32( header_.loop_start );
prev_pcm_count = 0; prev_dac_count = 0;
pcm_enabled = 0; dac_enabled = false;
pcm_amp = -1; dac_amp = -1;
fm.reset(); fm.reset();
apu.reset(); apu.reset();
stereo_buf.clear(); blip_buf.clear();
resampler.clear(); Dual_Resampler::clear();
pcm_buf = stereo_buf.center(); return 0;
return blargg_ok;
} }
void Gym_Emu::run_pcm( byte const pcm_in [], int pcm_count ) void Gym_Emu::run_dac( int dac_count )
{ {
// Guess beginning and end of sample and adjust rate and buffer position accordingly. // Guess beginning and end of sample and adjust rate and buffer position accordingly.
// count dac samples in next frame // count dac samples in next frame
int next_pcm_count = 0; int next_dac_count = 0;
const byte* p = this->pos; const byte* p = this->pos;
int cmd; int cmd;
while ( (cmd = *p++) != 0 ) while ( (cmd = *p++) != 0 )
@ -278,46 +261,45 @@ void Gym_Emu::run_pcm( byte const pcm_in [], int pcm_count )
if ( cmd <= 2 ) if ( cmd <= 2 )
++p; ++p;
if ( cmd == 1 && data == 0x2A ) if ( cmd == 1 && data == 0x2A )
next_pcm_count++; next_dac_count++;
} }
// detect beginning and end of sample // detect beginning and end of sample
int rate_count = pcm_count; int rate_count = dac_count;
int start = 0; int start = 0;
if ( !prev_pcm_count && next_pcm_count && pcm_count < next_pcm_count ) if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count )
{ {
rate_count = next_pcm_count; rate_count = next_dac_count;
start = next_pcm_count - pcm_count; start = next_dac_count - dac_count;
} }
else if ( prev_pcm_count && !next_pcm_count && pcm_count < prev_pcm_count ) else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count )
{ {
rate_count = prev_pcm_count; rate_count = prev_dac_count;
} }
// Evenly space samples within buffer section being used // 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 period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count;
blip_resampled_time_t time = pcm_buf->resampled_time( 0 ) + period * start + (unsigned) period / 2; blip_resampled_time_t time = blip_buf.resampled_time( 0 ) +
period * start + (period >> 1);
int pcm_amp = this->pcm_amp; int dac_amp = this->dac_amp;
if ( pcm_amp < 0 ) if ( dac_amp < 0 )
pcm_amp = pcm_in [0]; dac_amp = dac_buf [0];
for ( int i = 0; i < pcm_count; i++ ) for ( int i = 0; i < dac_count; i++ )
{ {
int delta = pcm_in [i] - pcm_amp; int delta = dac_buf [i] - dac_amp;
pcm_amp += delta; dac_amp += delta;
pcm_synth.offset_resampled( time, delta, pcm_buf ); dac_synth.offset_resampled( time, delta, &blip_buf );
time += period; time += period;
} }
this->pcm_amp = pcm_amp; this->dac_amp = dac_amp;
pcm_buf->set_modified();
} }
void Gym_Emu::parse_frame() void Gym_Emu::parse_frame()
{ {
byte pcm [1024]; // all PCM writes for frame int dac_count = 0;
int pcm_size = 0;
const byte* pos = this->pos; const byte* pos = this->pos;
if ( loop_remain && !--loop_remain ) if ( loop_remain && !--loop_remain )
@ -330,41 +312,22 @@ void Gym_Emu::parse_frame()
if ( cmd == 1 ) if ( cmd == 1 )
{ {
int data2 = *pos++; int data2 = *pos++;
if ( data == 0x2A ) if ( data != 0x2A )
{
pcm [pcm_size] = data2;
if ( pcm_size < (int) sizeof pcm - 1 )
pcm_size += pcm_enabled;
}
else
{ {
if ( data == 0x2B ) if ( data == 0x2B )
pcm_enabled = data2 >> 7 & 1; dac_enabled = (data2 & 0x80) != 0;
fm.write0( data, data2 ); fm.write0( data, data2 );
} }
else if ( dac_count < (int) sizeof dac_buf )
{
dac_buf [dac_count] = data2;
dac_count += dac_enabled;
}
} }
else if ( cmd == 2 ) else if ( cmd == 2 )
{ {
int data2 = *pos++; fm.write1( data, *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 ) else if ( cmd == 3 )
{ {
@ -379,10 +342,10 @@ void Gym_Emu::parse_frame()
} }
} }
if ( pos >= file_end() ) // loop
if ( pos >= data_end )
{ {
// Reached end check( pos == data_end );
check( pos == file_end() );
if ( loop_begin ) if ( loop_begin )
pos = loop_begin; pos = loop_begin;
@ -391,13 +354,13 @@ void Gym_Emu::parse_frame()
} }
this->pos = pos; this->pos = pos;
// PCM // dac
if ( pcm_buf && pcm_size ) if ( dac_count && !dac_muted )
run_pcm( pcm, pcm_size ); run_dac( dac_count );
prev_pcm_count = pcm_size; prev_dac_count = dac_count;
} }
inline int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t buf [] ) int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf )
{ {
if ( !track_ended() ) if ( !track_ended() )
parse_frame(); parse_frame();
@ -410,19 +373,8 @@ inline int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_
return sample_count; return sample_count;
} }
int Gym_Emu::play_frame_( void* p, blip_time_t a, int b, sample_t c [] ) blargg_err_t Gym_Emu::play_( long count, sample_t* out )
{ {
return STATIC_CAST(Gym_Emu*,p)->play_frame( a, b, c ); Dual_Resampler::dual_play( count, out, blip_buf );
} return 0;
blargg_err_t Gym_Emu::play_( int count, sample_t out [] )
{
resampler.dual_play( count, out, stereo_buf );
return blargg_ok;
}
blargg_err_t Gym_Emu::hash_( Hash_Function& out ) const
{
hash_gym_file( header(), log_begin(), file_end() - log_begin(), out );
return blargg_ok;
} }

View file

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

View file

@ -1,8 +1,10 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Hes_Apu.h" #include "Hes_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you #include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -19,15 +21,17 @@ bool const center_waves = true; // reduces asymmetry and clamping when starting
Hes_Apu::Hes_Apu() Hes_Apu::Hes_Apu()
{ {
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) Hes_Osc* osc = &oscs [osc_count];
do
{ {
osc--; osc--;
osc->output [0] = NULL; osc->outputs [0] = 0;
osc->output [1] = NULL; osc->outputs [1] = 0;
osc->outputs [0] = NULL; osc->chans [0] = 0;
osc->outputs [1] = NULL; osc->chans [1] = 0;
osc->outputs [2] = NULL; osc->chans [2] = 0;
} }
while ( osc != oscs );
reset(); reset();
} }
@ -37,192 +41,150 @@ void Hes_Apu::reset()
latch = 0; latch = 0;
balance = 0xFF; balance = 0xFF;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) Hes_Osc* osc = &oscs [osc_count];
do
{ {
osc--; osc--;
memset( osc, 0, offsetof (Osc,output) ); memset( osc, 0, offsetof (Hes_Osc,outputs) );
osc->lfsr = 0; osc->noise_lfsr = 1;
osc->control = 0x40; osc->control = 0x40;
osc->balance = 0xFF; osc->balance = 0xFF;
} }
while ( osc != oscs );
// 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 ) void Hes_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
{ {
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) require( (unsigned) index < osc_count );
require( !center || (center && !left && !right) || (center && left && right) ); oscs [index].chans [0] = center;
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index oscs [index].chans [1] = left;
oscs [index].chans [2] = right;
if ( !center || !left || !right ) Hes_Osc* osc = &oscs [osc_count];
do
{ {
left = center; osc--;
right = center; balance_changed( *osc );
} }
while ( osc != oscs );
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 ) void Hes_Osc::run_until( synth_t& synth_, blip_time_t end_time )
{ {
int vol0 = o.volume [0]; Blip_Buffer* const osc_outputs_0 = outputs [0]; // cache often-used values
int vol1 = o.volume [1]; if ( osc_outputs_0 && control & 0x80 )
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 int dac = this->dac;
if ( out1 )
int const volume_0 = volume [0];
{ {
int delta = dac * vol1 - o.last_amp [1]; int delta = dac * volume_0 - last_amp [0];
if ( delta ) if ( delta )
{ synth_.offset( last_time, delta, osc_outputs_0 );
syn.offset( o.last_time, delta, out1 ); osc_outputs_0->set_modified();
out1->set_modified();
}
} }
int delta = dac * vol0 - o.last_amp [0];
if ( delta ) Blip_Buffer* const osc_outputs_1 = outputs [1];
int const volume_1 = volume [1];
if ( osc_outputs_1 )
{ {
syn.offset( o.last_time, delta, out0 ); int delta = dac * volume_1 - last_amp [1];
out0->set_modified(); if ( delta )
synth_.offset( last_time, delta, osc_outputs_1 );
osc_outputs_1->set_modified();
} }
// Don't generate if silent blip_time_t time = last_time + delay;
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 ) if ( time < end_time )
{ {
int period = (~o.noise & 0x1F) * 128; if ( noise & 0x80 )
if ( !period )
period = 64;
if ( noise && out0 )
{ {
unsigned lfsr = o.lfsr; if ( volume_0 | volume_1 )
do
{ {
int new_dac = -(lfsr & 1); // noise
lfsr = (lfsr >> 1) ^ (0x30061 & new_dac); int const period = (32 - (noise & 0x1F)) * 64; // TODO: correct?
unsigned noise_lfsr = this->noise_lfsr;
int delta = (new_dac &= 0x1F) - dac; do
if ( delta )
{ {
dac = new_dac; int new_dac = 0x1F & -(noise_lfsr >> 1 & 1);
syn.offset( time, delta * vol0, out0 ); // Implemented using "Galios configuration"
if ( out1 ) // TODO: find correct LFSR algorithm
syn.offset( time, delta * vol1, out1 ); noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & -(noise_lfsr & 1));
//noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1));
int delta = new_dac - dac;
if ( delta )
{
dac = new_dac;
synth_.offset( time, delta * volume_0, osc_outputs_0 );
if ( osc_outputs_1 )
synth_.offset( time, delta * volume_1, osc_outputs_1 );
}
time += period;
} }
time += period; while ( time < end_time );
}
while ( time < end_time );
if ( !lfsr ) this->noise_lfsr = noise_lfsr;
{ assert( noise_lfsr );
lfsr = 1;
check( false );
} }
o.lfsr = lfsr;
out0->set_modified();
if ( out1 )
out1->set_modified();
} }
else else if ( !(control & 0x40) )
{ {
// Maintain phase when silent // wave
int count = (end_time - time + period - 1) / period; int phase = (this->phase + 1) & 0x1F; // pre-advance for optimal inner loop
time += count * period; int period = this->period * 2;
if ( period >= 14 && (volume_0 | volume_1) )
// 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; do
syn.offset( time, delta * vol0, out0 ); {
if ( out1 ) int new_dac = wave [phase];
syn.offset( time, delta * vol1, out1 ); phase = (phase + 1) & 0x1F;
int delta = new_dac - dac;
if ( delta )
{
dac = new_dac;
synth_.offset( time, delta * volume_0, osc_outputs_0 );
if ( osc_outputs_1 )
synth_.offset( time, delta * volume_1, osc_outputs_1 );
}
time += period;
}
while ( time < end_time );
} }
time += period; else
{
if ( !period )
{
// TODO: Gekisha Boy assumes that period = 0 silences wave
//period = 0x1000 * 2;
period = 1;
//if ( !(volume_0 | volume_1) )
// debug_printf( "Used period 0\n" );
}
// maintain phase when silent
blargg_long count = (end_time - time + period - 1) / period;
phase += count; // phase will be masked below
time += count * period;
}
this->phase = (phase - 1) & 0x1F; // undo pre-advance
} }
while ( time < end_time );
out0->set_modified();
if ( out1 )
out1->set_modified();
} }
else time -= end_time;
{ if ( time < 0 )
// Maintain phase when silent time = 0;
int count = end_time - time; delay = time;
if ( !period )
period = 1;
count = (count + period - 1) / period;
phase += count; // phase will be masked below this->dac = dac;
time += count * period; last_amp [0] = dac * volume_0;
} last_amp [1] = dac * volume_1;
// 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; last_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 ) void Hes_Apu::balance_changed( Hes_Osc& osc )
{ {
static short const log_table [32] = { // ~1.5 db per step static short const log_table [32] = { // ~1.5 db per step
#define ENTRY( factor ) short (factor * amp_range / 31.0 + 0.5) #define ENTRY( factor ) short (factor * Hes_Osc::amp_range / 31.0 + 0.5)
ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ), ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ),
ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ), ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ),
ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ), ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ),
@ -242,40 +204,27 @@ void Hes_Apu::balance_changed( Osc& osc )
int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E); int right = vol + (osc.balance << 1 & 0x1E) + (balance << 1 & 0x1E);
if ( right < 0 ) right = 0; if ( right < 0 ) right = 0;
left = log_table [left ];
right = log_table [right];
// optimizing for the common case of being centered also allows easy // optimizing for the common case of being centered also allows easy
// panning using Effects_Buffer // panning using Effects_Buffer
osc.outputs [0] = osc.chans [0]; // center
// Separate balance into center volume and additional on either left or right osc.outputs [1] = 0;
osc.output [0] = osc.outputs [0]; // center if ( left != right )
osc.output [1] = osc.outputs [2]; // right
int base = log_table [left ];
int side = log_table [right] - base;
if ( side < 0 )
{ {
base += side; osc.outputs [0] = osc.chans [1]; // left
side = -side; osc.outputs [1] = osc.chans [2]; // right
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 ) if ( center_waves )
{ {
// TODO: this can leave a non-zero level in a buffer (minor) osc.last_amp [0] += (left - osc.volume [0]) * 16;
osc.last_amp [0] += (base - osc.volume [0]) * 16; osc.last_amp [1] += (right - osc.volume [1]) * 16;
osc.last_amp [1] += (side - osc.volume [1]) * 16;
} }
osc.volume [0] = base; osc.volume [0] = left;
osc.volume [1] = side; osc.volume [1] = right;
} }
void Hes_Apu::write_data( blip_time_t time, int addr, int data ) void Hes_Apu::write_data( blip_time_t time, int addr, int data )
@ -290,18 +239,20 @@ void Hes_Apu::write_data( blip_time_t time, int addr, int data )
{ {
balance = data; balance = data;
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) Hes_Osc* osc = &oscs [osc_count];
do
{ {
osc--; osc--;
run_osc( synth, *osc, time ); osc->run_until( synth, time );
balance_changed( *oscs ); balance_changed( *oscs );
} }
while ( osc != oscs );
} }
} }
else if ( latch < osc_count ) else if ( latch < osc_count )
{ {
Osc& osc = oscs [latch]; Hes_Osc& osc = oscs [latch];
run_osc( synth, osc, time ); osc.run_until( synth, time );
switch ( addr ) switch ( addr )
{ {
case 0x802: case 0x802:
@ -338,24 +289,27 @@ void Hes_Apu::write_data( blip_time_t time, int addr, int data )
break; break;
case 0x807: case 0x807:
osc.noise = data; if ( &osc >= &oscs [4] )
osc.noise = data;
break; break;
case 0x809: case 0x809:
if ( !(data & 0x80) && (data & 0x03) != 0 ) if ( !(data & 0x80) && (data & 0x03) != 0 )
dprintf( "HES LFO not supported\n" ); debug_printf( "HES LFO not supported\n" );
} }
} }
} }
void Hes_Apu::end_frame( blip_time_t end_time ) void Hes_Apu::end_frame( blip_time_t end_time )
{ {
for ( Osc* osc = &oscs [osc_count]; osc != oscs; ) Hes_Osc* osc = &oscs [osc_count];
do
{ {
osc--; osc--;
if ( end_time > osc->last_time ) if ( end_time > osc->last_time )
run_osc( synth, *osc, end_time ); osc->run_until( synth, end_time );
assert( osc->last_time >= end_time );
osc->last_time -= end_time; osc->last_time -= end_time;
check( osc->last_time >= 0 );
} }
while ( osc != oscs );
} }

View file

@ -1,87 +1,66 @@
// Turbo Grafx 16 (PC Engine) PSG sound chip emulator // Turbo Grafx 16 (PC Engine) PSG sound chip emulator
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef HES_APU_H #ifndef HES_APU_H
#define HES_APU_H #define HES_APU_H
#include "blargg_common.h" #include "blargg_common.h"
#include "Blip_Buffer.h" #include "Blip_Buffer.h"
class Hes_Apu { struct Hes_Osc
public: {
// Basics unsigned char wave [32];
short volume [2];
int last_amp [2];
int delay;
int period;
unsigned char noise;
unsigned char phase;
unsigned char balance;
unsigned char dac;
blip_time_t last_time;
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, Blip_Buffer* outputs [2];
// output is mono. Blip_Buffer* chans [3];
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); unsigned noise_lfsr;
unsigned char control;
// 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 }; enum { amp_range = 0x8000 };
struct Osc typedef Blip_Synth<blip_med_quality,1> synth_t;
{
byte wave [32];
int delay;
int period;
int phase;
int noise_delay; void run_until( synth_t& synth, blip_time_t );
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 ) class Hes_Apu {
{ public:
for ( int i = osc_count; --i >= 0; ) void treble_eq( blip_eq_t const& );
set_output( i, c, l, r ); void volume( double );
}
enum { osc_count = 6 };
void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right );
void reset();
enum { start_addr = 0x0800 };
enum { end_addr = 0x0809 };
void write_data( blip_time_t, int addr, int data );
void end_frame( blip_time_t );
public:
Hes_Apu();
private:
Hes_Osc oscs [osc_count];
int latch;
int balance;
Hes_Osc::synth_t synth;
void balance_changed( Hes_Osc& );
void recalc_chans();
};
inline void Hes_Apu::volume( double v ) { synth.volume( 1.8 / osc_count / Hes_Osc::amp_range * v ); }
inline void Hes_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
#endif #endif

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Kss_Scc_Apu.h" #include "Kss_Scc_Apu.h"
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you /* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -17,38 +17,10 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
// Tones above this frequency are treated as disabled tone at half volume. // Tones above this frequency are treated as disabled tone at half volume.
// Power of two is more efficient (avoids division). // Power of two is more efficient (avoids division).
int const inaudible_freq = 16384; unsigned const inaudible_freq = 16384;
int const wave_size = 0x20; int const wave_size = 0x20;
void Scc_Apu::set_output( Blip_Buffer* buf )
{
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 ) void Scc_Apu::run_until( blip_time_t end_time )
{ {
for ( int index = 0; index < osc_count; index++ ) for ( int index = 0; index < osc_count; index++ )
@ -58,28 +30,28 @@ void Scc_Apu::run_until( blip_time_t end_time )
Blip_Buffer* const output = osc.output; Blip_Buffer* const output = osc.output;
if ( !output ) if ( !output )
continue; continue;
output->set_modified();
blip_time_t period = (regs [0xA0 + index * 2 + 1] & 0x0F) * 0x100 + blip_time_t period = (regs [0x80 + index * 2 + 1] & 0x0F) * 0x100 +
regs [0xA0 + index * 2] + 1; regs [0x80 + index * 2] + 1;
int volume = 0; int volume = 0;
if ( regs [0xAF] & (1 << index) ) if ( regs [0x8F] & (1 << index) )
{ {
blip_time_t inaudible_period = (unsigned) (output->clock_rate() + blip_time_t inaudible_period = (blargg_ulong) (output->clock_rate() +
inaudible_freq * 32) / (unsigned) (inaudible_freq * 16); inaudible_freq * 32) / (inaudible_freq * 16);
if ( period > inaudible_period ) if ( period > inaudible_period )
volume = (regs [0xAA + index] & 0x0F) * (amp_range / 256 / 15); volume = (regs [0x8A + index] & 0x0F) * (amp_range / 256 / 15);
} }
BOOST::int8_t const* wave = (BOOST::int8_t*) regs + index * wave_size; int8_t const* wave = (int8_t*) regs + index * wave_size;
/*if ( index == osc_count - 1 ) if ( index == osc_count - 1 )
wave -= wave_size; // last two oscs share same wave RAM*/ wave -= wave_size; // last two oscs share wave
{ {
int delta = wave [osc.phase] * volume - osc.last_amp; int amp = wave [osc.phase] * volume;
int delta = amp - osc.last_amp;
if ( delta ) if ( delta )
{ {
osc.last_amp += delta; osc.last_amp = amp;
output->set_modified();
synth.offset( last_time, delta, output ); synth.offset( last_time, delta, output );
} }
} }
@ -87,36 +59,37 @@ void Scc_Apu::run_until( blip_time_t end_time )
blip_time_t time = last_time + osc.delay; blip_time_t time = last_time + osc.delay;
if ( time < end_time ) if ( time < end_time )
{ {
int phase = osc.phase;
if ( !volume ) if ( !volume )
{ {
// maintain phase // maintain phase
int count = (end_time - time + period - 1) / period; blargg_long count = (end_time - time + period - 1) / period;
phase += count; // will be masked below osc.phase = (osc.phase + count) & (wave_size - 1);
time += count * period; time += count * period;
} }
else else
{ {
int phase = osc.phase;
int last_wave = wave [phase]; int last_wave = wave [phase];
phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop phase = (phase + 1) & (wave_size - 1); // pre-advance for optimal inner loop
do do
{ {
int delta = wave [phase] - last_wave; int amp = wave [phase];
phase = (phase + 1) & (wave_size - 1); phase = (phase + 1) & (wave_size - 1);
int delta = amp - last_wave;
if ( delta ) if ( delta )
{ {
last_wave += delta; last_wave = amp;
synth.offset_inline( time, delta * volume, output ); synth.offset( time, delta * volume, output );
} }
time += period; time += period;
} }
while ( time < end_time ); while ( time < end_time );
osc.last_amp = last_wave * volume; osc.phase = phase = (phase - 1) & (wave_size - 1); // undo pre-advance
output->set_modified(); osc.last_amp = wave [phase] * volume;
phase--; // undo pre-advance
} }
osc.phase = phase & (wave_size - 1);
} }
osc.delay = time - end_time; osc.delay = time - end_time;
} }

View file

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

View file

@ -1,8 +1,10 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "M3u_Playlist.h" #include "M3u_Playlist.h"
#include "Music_Emu.h" #include "Music_Emu.h"
#include <string.h>
/* Copyright (C) 2006 Shay Green. This module is free software; you /* Copyright (C) 2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
@ -20,9 +22,10 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
blargg_err_t Gme_File::load_m3u_( blargg_err_t err ) blargg_err_t Gme_File::load_m3u_( blargg_err_t err )
{ {
require( raw_track_count_ ); // file must be loaded first
if ( !err ) if ( !err )
{ {
require( raw_track_count_ ); // file must be loaded first
if ( playlist.size() ) if ( playlist.size() )
track_count_ = playlist.size(); track_count_ = playlist.size();
@ -45,11 +48,11 @@ blargg_err_t Gme_File::load_m3u_( blargg_err_t err )
return err; 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( 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 ) ); } 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( 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 ) gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
{ {
@ -57,9 +60,11 @@ gme_err_t gme_load_m3u_data( Music_Emu* me, const void* data, long size )
return me->load_m3u( in ); return me->load_m3u( in );
} }
static char* skip_white( char* in ) static char* skip_white( char* in )
{ {
while ( unsigned (*in - 1) <= ' ' - 1 ) while ( *in == ' ' )
in++; in++;
return in; return in;
} }
@ -148,6 +153,23 @@ static char* parse_int_( char* in, int* out )
return in; return in;
} }
static char* parse_mil_( char* in, int* out )
{
int n = 0;
int x = 100;
while ( 1 )
{
unsigned d = from_dec( *in );
if ( d > 9 )
break;
in++;
n += d * x;
x /= 10;
*out = n;
}
return in;
}
static char* parse_int( char* in, int* out, int* result ) static char* parse_int( char* in, int* out, int* result )
{ {
return next_field( parse_int_( in, out ), result ); return next_field( parse_int_( in, out ), result );
@ -203,12 +225,13 @@ static char* parse_time_( char* in, int* out )
*out = *out * 60 + n; *out = *out * 60 + n;
} }
*out *= 1000; *out *= 1000;
if ( *in == '.' ) if ( *in == '.' )
{ {
n = -1; n = -1;
in = parse_int_( in + 1, &n ); in = parse_mil_( in + 1, &n );
if ( n >= 0 ) if ( n >= 0 )
*out = *out + n; *out += n;
} }
} }
return in; return in;
@ -285,7 +308,7 @@ static int parse_line( char* in, M3u_Playlist::entry_t& entry )
in = parse_time_( in, &entry.loop ); in = parse_time_( in, &entry.loop );
if ( entry.loop >= 0 ) if ( entry.loop >= 0 )
{ {
entry.intro = entry.length - entry.loop; entry.intro = 0;
if ( *in == '-' ) // trailing '-' means that intro length was specified if ( *in == '-' ) // trailing '-' means that intro length was specified
{ {
in++; in++;
@ -311,9 +334,9 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
{ {
in = skip_white( in + 1 ); in = skip_white( in + 1 );
const char* field = in; const char* field = in;
if ( *field != '@' ) if ( *field != '@' )
while ( *in && *in != ':' ) while ( *in && *in != ':' )
in++; in++;
if ( *in == ':' ) if ( *in == ':' )
{ {
@ -325,9 +348,9 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
else if ( !strcmp( "Engineer" , field ) ) info.engineer = text; else if ( !strcmp( "Engineer" , field ) ) info.engineer = text;
else if ( !strcmp( "Ripping" , field ) ) info.ripping = text; else if ( !strcmp( "Ripping" , field ) ) info.ripping = text;
else if ( !strcmp( "Tagging" , field ) ) info.tagging = text; else if ( !strcmp( "Tagging" , field ) ) info.tagging = text;
else if ( !strcmp( "Game" , field ) ) info.title = text; else if ( !strcmp( "Game" , field ) ) info.title = text;
else if ( !strcmp( "Artist" , field ) ) info.artist = text; else if ( !strcmp( "Artist" , field ) ) info.artist = text;
else if ( !strcmp( "Copyright", field ) ) info.copyright = text; else if ( !strcmp( "Copyright", field ) ) info.copyright = text;
else else
text = 0; text = 0;
if ( text ) if ( text )
@ -335,45 +358,44 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
*in = ':'; *in = ':';
} }
} }
else if ( *field == '@' ) else if ( *field == '@' )
{ {
++field; ++field;
in = (char*)field; in = (char*)field;
while ( *in && *in > ' ' ) while ( *in && *in > ' ' )
in++; in++;
const char* text = skip_white( in ); const char* text = skip_white( in );
if ( *text ) if ( *text )
{ {
char saved = *in; char saved = *in;
*in = 0; *in = 0;
if ( !strcmp( "TITLE" , field ) ) info.title = text; if ( !strcmp( "TITLE" , field ) ) info.title = text;
else if ( !strcmp( "ARTIST", field ) ) info.artist = text; else if ( !strcmp( "ARTIST" , field ) ) info.artist = text;
else if ( !strcmp( "DATE", field ) ) info.date = text; else if ( !strcmp( "DATE" , field ) ) info.date = text;
else if ( !strcmp( "COMPOSER", field ) ) info.composer = text; else if ( !strcmp( "COMPOSER" , field ) ) info.composer = text;
else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text; else if ( !strcmp( "SEQUENCER", field ) ) info.sequencer = text;
else if ( !strcmp( "ENGINEER", field ) ) info.engineer = text; else if ( !strcmp( "ENGINEER" , field ) ) info.engineer = text;
else if ( !strcmp( "RIPPER", field ) ) info.ripping = text; else if ( !strcmp( "RIPPER" , field ) ) info.ripping = text;
else if ( !strcmp( "TAGGER", field ) ) info.tagging = text; else if ( !strcmp( "TAGGER" , field ) ) info.tagging = text;
else else
text = 0; text = 0;
if ( text ) if ( text )
{ {
last_comment_value = (char*)text; last_comment_value = (char*)text;
return; return;
} }
*in = saved; }
} }
} else if ( last_comment_value )
else if ( last_comment_value ) {
{ size_t len = strlen( last_comment_value );
size_t len = strlen( last_comment_value ); last_comment_value[ len ] = ',';
last_comment_value[ len ] = ','; last_comment_value[ len + 1 ] = ' ';
last_comment_value[ len + 1 ] = ' '; size_t field_len = strlen( field );
size_t field_len = strlen( field ); memmove( last_comment_value + len + 2, field, field_len );
memmove( last_comment_value + len + 2, field, field_len ); last_comment_value[ len + 2 + field_len ] = 0;
last_comment_value[ len + 2 + field_len ] = 0; return;
return; }
}
if ( first ) if ( first )
info.title = field; info.title = field;
@ -382,14 +404,14 @@ static void parse_comment( char* in, M3u_Playlist::info_t& info, char *& last_co
blargg_err_t M3u_Playlist::parse_() blargg_err_t M3u_Playlist::parse_()
{ {
info_.title = ""; info_.title = "";
info_.artist = ""; info_.artist = "";
info_.date = ""; info_.date = "";
info_.composer = ""; info_.composer = "";
info_.sequencer = ""; info_.sequencer = "";
info_.engineer = ""; info_.engineer = "";
info_.ripping = ""; info_.ripping = "";
info_.tagging = ""; info_.tagging = "";
info_.copyright = ""; info_.copyright = "";
int const CR = 13; int const CR = 13;
int const LF = 10; int const LF = 10;
@ -401,7 +423,7 @@ blargg_err_t M3u_Playlist::parse_()
int line = 0; int line = 0;
int count = 0; int count = 0;
char* in = data.begin(); char* in = data.begin();
char* last_comment_value = 0; char* last_comment_value = 0;
while ( in < data.end() ) while ( in < data.end() )
{ {
// find end of line and terminate it // find end of line and terminate it
@ -410,7 +432,7 @@ blargg_err_t M3u_Playlist::parse_()
while ( *in != CR && *in != LF ) while ( *in != CR && *in != LF )
{ {
if ( !*in ) if ( !*in )
return blargg_err_file_type; return "Not an m3u playlist";
in++; in++;
} }
if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line
@ -434,13 +456,12 @@ blargg_err_t M3u_Playlist::parse_()
first_error_ = line; first_error_ = line;
first_comment = false; first_comment = false;
} }
else last_comment_value = 0; else last_comment_value = 0;
} }
if ( count <= 0 ) if ( count <= 0 )
return blargg_err_file_type; return "Not an m3u playlist";
// Treat first comment as title only if another field is also specified if ( !(info_.composer [0] | info_.engineer [0] | info_.ripping [0] | info_.tagging [0]) )
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 = ""; info_.title = "";
return entries.resize( count ); return entries.resize( count );
@ -450,7 +471,10 @@ blargg_err_t M3u_Playlist::parse()
{ {
blargg_err_t err = parse_(); blargg_err_t err = parse_();
if ( err ) if ( err )
clear_(); {
entries.clear();
data.clear();
}
return err; return err;
} }
@ -461,7 +485,7 @@ blargg_err_t M3u_Playlist::load( Data_Reader& in )
return parse(); return parse();
} }
blargg_err_t M3u_Playlist::load( const char path [] ) blargg_err_t M3u_Playlist::load( const char* path )
{ {
GME_FILE_READER in; GME_FILE_READER in;
RETURN_ERR( in.open( path ) ); RETURN_ERR( in.open( path ) );

View file

@ -1,6 +1,6 @@
// M3U playlist file parser, with support for subtrack information // M3U playlist file parser, with support for subtrack information
// Game_Music_Emu $vers // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef M3U_PLAYLIST_H #ifndef M3U_PLAYLIST_H
#define M3U_PLAYLIST_H #define M3U_PLAYLIST_H
@ -18,29 +18,28 @@ public:
// errors are ignored. // errors are ignored.
int first_error() const { return first_error_; } int first_error() const { return first_error_; }
// All string pointers point to valid string, or "" if not available
struct info_t struct info_t
{ {
const char* title; const char* title;
const char* artist; const char* artist;
const char* date; const char* date;
const char* composer; const char* composer;
const char* sequencer; const char* sequencer;
const char* engineer; const char* engineer;
const char* ripping; const char* ripping;
const char* tagging; const char* tagging;
const char* copyright; const char* copyright;
}; };
info_t const& info() const { return info_; } info_t const& info() const { return info_; }
struct entry_t struct entry_t
{ {
const char* file; // filename without stupid ::TYPE suffix const char* file; // filename without stupid ::TYPE suffix
const char* type; // if filename has ::TYPE suffix, this is "TYPE", otherwise "" const char* type; // if filename has ::TYPE suffix, this will be "TYPE". "" if none.
const char* name; const char* name;
bool decimal_track; // true if track was specified in decimal bool decimal_track; // true if track was specified in hex
// integers are -1 if not present // integers are -1 if not present
int track; int track; // 1-based
int length; // milliseconds int length; // milliseconds
int intro; int intro;
int loop; int loop;
@ -60,28 +59,13 @@ private:
blargg_err_t parse(); blargg_err_t parse();
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() inline void M3u_Playlist::clear()
{ {
first_error_ = 0; first_error_ = 0;
clear_(); entries.clear();
data.clear();
} }
#endif #endif

View file

@ -1,8 +1,8 @@
// Blip_Buffer $vers. http://www.slack.net/~ant/ // Blip_Buffer 0.4.1. http://www.slack.net/~ant/
#include "Multi_Buffer.h" #include "Multi_Buffer.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -15,31 +15,27 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
#ifdef BLARGG_ENABLE_OPTIMIZER
#include BLARGG_ENABLE_OPTIMIZER
#endif
Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf )
{ {
length_ = 0; length_ = 0;
sample_rate_ = 0; sample_rate_ = 0;
channels_changed_count_ = 1; channels_changed_count_ = 1;
channel_types_ = NULL;
channel_count_ = 0;
immediate_removal_ = true;
} }
Multi_Buffer::channel_t Multi_Buffer::channel( int /*index*/ ) blargg_err_t Multi_Buffer::set_channel_count( int ) { return 0; }
{
channel_t ch;
ch.center = ch.left = ch.right = NULL;
return ch;
}
// Silent_Buffer // Silent_Buffer
Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse 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? // TODO: better to use empty Blip_Buffer so caller never has to check for NULL?
chan.left = NULL; chan.left = 0;
chan.center = NULL; chan.center = 0;
chan.right = NULL; chan.right = 0;
} }
// Mono_Buffer // Mono_Buffer
@ -53,238 +49,184 @@ Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 )
Mono_Buffer::~Mono_Buffer() { } Mono_Buffer::~Mono_Buffer() { }
blargg_err_t Mono_Buffer::set_sample_rate( int rate, int msec ) blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec )
{ {
RETURN_ERR( buf.set_sample_rate( rate, msec ) ); RETURN_ERR( buf.set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); 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 // Stereo_Buffer
int const stereo = 2;
Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 )
{ {
chan.center = mixer.bufs [2] = &bufs [2]; chan.center = &bufs [0];
chan.left = mixer.bufs [0] = &bufs [0]; chan.left = &bufs [1];
chan.right = mixer.bufs [1] = &bufs [1]; chan.right = &bufs [2];
mixer.samples_read = 0;
} }
Stereo_Buffer::~Stereo_Buffer() { } Stereo_Buffer::~Stereo_Buffer() { }
blargg_err_t Stereo_Buffer::set_sample_rate( int rate, int msec ) blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec )
{ {
mixer.samples_read = 0; for ( int i = 0; i < buf_count; i++ )
for ( int i = bufs_size; --i >= 0; )
RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) );
return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() );
} }
void Stereo_Buffer::clock_rate( int rate ) void Stereo_Buffer::clock_rate( long rate )
{ {
for ( int i = bufs_size; --i >= 0; ) for ( int i = 0; i < buf_count; i++ )
bufs [i].clock_rate( rate ); bufs [i].clock_rate( rate );
} }
void Stereo_Buffer::bass_freq( int bass ) void Stereo_Buffer::bass_freq( int bass )
{ {
for ( int i = bufs_size; --i >= 0; ) for ( int i = 0; i < buf_count; i++ )
bufs [i].bass_freq( bass ); bufs [i].bass_freq( bass );
} }
void Stereo_Buffer::clear() void Stereo_Buffer::clear()
{ {
mixer.samples_read = 0; stereo_added = 0;
for ( int i = bufs_size; --i >= 0; ) was_stereo = false;
for ( int i = 0; i < buf_count; i++ )
bufs [i].clear(); bufs [i].clear();
} }
void Stereo_Buffer::end_frame( blip_time_t time ) void Stereo_Buffer::end_frame( blip_time_t clock_count )
{ {
for ( int i = bufs_size; --i >= 0; ) stereo_added = 0;
bufs [i].end_frame( time ); for ( int i = 0; i < buf_count; i++ )
{
stereo_added |= bufs [i].clear_modified() << i;
bufs [i].end_frame( clock_count );
}
} }
int Stereo_Buffer::read_samples( blip_sample_t out [], int out_size ) long Stereo_Buffer::read_samples( blip_sample_t* out, long count )
{ {
require( (out_size & 1) == 0 ); // must read an even number of samples require( !(count & 1) ); // count must be even
out_size = min( out_size, samples_avail() ); count = (unsigned) count / 2;
int pair_count = int (out_size >> 1); long avail = bufs [0].samples_avail();
if ( pair_count ) if ( count > avail )
count = avail;
if ( count )
{ {
mixer.read_pairs( out, pair_count ); int bufs_used = stereo_added | was_stereo;
//debug_printf( "%X\n", bufs_used );
if ( samples_avail() <= 0 || immediate_removal() ) if ( bufs_used <= 1 )
{ {
for ( int i = bufs_size; --i >= 0; ) mix_mono( out, count );
{ bufs [0].remove_samples( count );
buf_t& b = bufs [i]; bufs [1].remove_silence( count );
// TODO: might miss non-silence settling since it checks END of last read bufs [2].remove_silence( count );
if ( !b.non_silent() ) }
b.remove_silence( mixer.samples_read ); else if ( bufs_used & 1 )
else {
b.remove_samples( mixer.samples_read ); mix_stereo( out, count );
} bufs [0].remove_samples( count );
mixer.samples_read = 0; bufs [1].remove_samples( count );
bufs [2].remove_samples( count );
}
else
{
mix_stereo_no_center( out, count );
bufs [0].remove_silence( count );
bufs [1].remove_samples( count );
bufs [2].remove_samples( count );
}
// to do: this might miss opportunities for optimization
if ( !bufs [0].samples_avail() )
{
was_stereo = stereo_added;
stereo_added = 0;
} }
} }
return out_size;
return count * 2;
} }
void Stereo_Buffer::mix_stereo( blip_sample_t* out_, blargg_long count )
// 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 blip_sample_t* BLIP_RESTRICT out = out_;
// except that buffer isn't cleared, so caller can encounter int const bass = BLIP_READER_BASS( bufs [1] );
// subtle problems and not realize the cause. BLIP_READER_BEGIN( left, bufs [1] );
samples_read += count; BLIP_READER_BEGIN( right, bufs [2] );
if ( bufs [0]->non_silent() | bufs [1]->non_silent() ) BLIP_READER_BEGIN( center, bufs [0] );
mix_stereo( out, count );
else
mix_mono( out, count );
}
void Stereo_Mixer::mix_mono( blip_sample_t out_ [], int count ) for ( ; count; --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; int c = BLIP_READER_READ( center );
blargg_long l = c + BLIP_READER_READ( left );
blargg_long r = c + BLIP_READER_READ( right );
if ( (int16_t) l != l )
l = 0x7FFF - (l >> 24);
center_sum -= center_sum >> bass; BLIP_READER_NEXT( center, bass );
center_sum += center [offset]; if ( (int16_t) r != r )
r = 0x7FFF - (r >> 24);
BLIP_CLAMP( s, s ); BLIP_READER_NEXT( left, bass );
BLIP_READER_NEXT( right, bass );
out [offset] [0] = (blip_sample_t) s; out [0] = l;
out [offset] [1] = (blip_sample_t) s; out [1] = r;
out += 2;
} }
while ( ++offset );
bufs [2]->set_integrator( center_sum ); BLIP_READER_END( center, bufs [0] );
BLIP_READER_END( right, bufs [2] );
BLIP_READER_END( left, bufs [1] );
} }
void Stereo_Mixer::mix_stereo( blip_sample_t out_ [], int count ) void Stereo_Buffer::mix_stereo_no_center( blip_sample_t* out_, blargg_long count )
{ {
blip_sample_t* BLARGG_RESTRICT out = out_ + count * stereo; blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [1] );
BLIP_READER_BEGIN( left, bufs [1] );
BLIP_READER_BEGIN( right, bufs [2] );
// do left + center and right + center separately to reduce register load for ( ; count; --count )
Tracked_Blip_Buffer* const* buf = &bufs [2];
while ( true ) // loop runs twice
{ {
--buf; blargg_long l = BLIP_READER_READ( left );
--out; if ( (int16_t) l != l )
l = 0x7FFF - (l >> 24);
int const bass = bufs [2]->highpass_shift(); blargg_long r = BLIP_READER_READ( right );
Blip_Buffer::delta_t const* side = (*buf)->read_pos() + samples_read; if ( (int16_t) r != r )
Blip_Buffer::delta_t const* center = bufs [2]->read_pos() + samples_read; r = 0x7FFF - (r >> 24);
int side_sum = (*buf)->integrator(); BLIP_READER_NEXT( left, bass );
int center_sum = bufs [2]->integrator(); BLIP_READER_NEXT( right, bass );
int offset = -count; out [0] = l;
do out [1] = r;
{ out += 2;
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;
} }
BLIP_READER_END( right, bufs [2] );
BLIP_READER_END( left, bufs [1] );
}
void Stereo_Buffer::mix_mono( blip_sample_t* out_, blargg_long count )
{
blip_sample_t* BLIP_RESTRICT out = out_;
int const bass = BLIP_READER_BASS( bufs [0] );
BLIP_READER_BEGIN( center, bufs [0] );
for ( ; count; --count )
{
blargg_long s = BLIP_READER_READ( center );
if ( (int16_t) s != s )
s = 0x7FFF - (s >> 24);
BLIP_READER_NEXT( center, bass );
out [0] = s;
out [1] = s;
out += 2;
}
BLIP_READER_END( center, bufs [0] );
} }

View file

@ -1,6 +1,6 @@
// Multi-channel sound buffer interface, and basic mono and stereo buffers // Multi-channel sound buffer interface, and basic mono and stereo buffers
// Blip_Buffer $vers // Blip_Buffer 0.4.1
#ifndef MULTI_BUFFER_H #ifndef MULTI_BUFFER_H
#define MULTI_BUFFER_H #define MULTI_BUFFER_H
@ -11,209 +11,148 @@
// consisting of left, center, and right buffers. // consisting of left, center, and right buffers.
class Multi_Buffer { class Multi_Buffer {
public: public:
// 1=mono, 2=stereo
Multi_Buffer( int samples_per_frame ); Multi_Buffer( int samples_per_frame );
virtual ~Multi_Buffer() { } virtual ~Multi_Buffer() { }
// Sets the number of channels available and optionally their types // Set the number of channels available
// (type information used by Effects_Buffer) virtual blargg_err_t set_channel_count( int );
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 // Get indexed channel, from 0 to channel count - 1
struct channel_t { struct channel_t {
Blip_Buffer* center; Blip_Buffer* center;
Blip_Buffer* left; Blip_Buffer* left;
Blip_Buffer* right; Blip_Buffer* right;
}; };
virtual channel_t channel( int index ) BLARGG_PURE( ; ) enum { type_index_mask = 0xFF };
enum { wave_type = 0x100, noise_type = 0x200, mixed_type = wave_type | noise_type };
virtual channel_t channel( int index, int type ) = 0;
// See Blip_Buffer.h
virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0;
virtual void clock_rate( long ) = 0;
virtual void bass_freq( int ) = 0;
virtual void clear() = 0;
long sample_rate() const;
// Length of buffer, in milliseconds
int length() const;
// See Blip_Buffer.h
virtual void end_frame( blip_time_t ) = 0;
// Number of samples per output frame (1 = mono, 2 = stereo) // Number of samples per output frame (1 = mono, 2 = stereo)
int samples_per_frame() const; int samples_per_frame() const;
// Count of changes to channel configuration. Incremented whenever // Count of changes to channel configuration. Incremented whenever
// a change is made to any of the Blip_Buffers for any channel. // a change is made to any of the Blip_Buffers for any channel.
unsigned channels_changed_count() { return channels_changed_count_; } unsigned channels_changed_count() { return channels_changed_count_; }
// See Blip_Buffer.h // See Blip_Buffer.h
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ) BLARGG_PURE( ; ) virtual long read_samples( blip_sample_t*, long ) = 0;
int sample_rate() const; virtual long samples_avail() const = 0;
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( ; )
public:
BLARGG_DISABLE_NOTHROW
protected:
void channels_changed() { channels_changed_count_++; }
private: private:
// noncopyable // noncopyable
Multi_Buffer( const Multi_Buffer& ); Multi_Buffer( const Multi_Buffer& );
Multi_Buffer& operator = ( const Multi_Buffer& ); Multi_Buffer& operator = ( const Multi_Buffer& );
// Implementation
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_; unsigned channels_changed_count_;
int sample_rate_; long sample_rate_;
int length_; int length_;
int channel_count_;
int const samples_per_frame_; int const samples_per_frame_;
int const* channel_types_;
bool immediate_removal_;
}; };
// Uses a single buffer and outputs mono samples. // Uses a single buffer and outputs mono samples.
class Mono_Buffer : public Multi_Buffer { class Mono_Buffer : public Multi_Buffer {
Blip_Buffer buf;
channel_t chan;
public: public:
// Buffer used for all channels // Buffer used for all channels
Blip_Buffer* center() { return &buf; } Blip_Buffer* center() { return &buf; }
// Implementation
public: public:
Mono_Buffer(); Mono_Buffer();
~Mono_Buffer(); ~Mono_Buffer();
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ); blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
virtual void clock_rate( int rate ) { buf.clock_rate( rate ); } void clock_rate( long rate ) { buf.clock_rate( rate ); }
virtual void bass_freq( int freq ) { buf.bass_freq( freq ); } void bass_freq( int freq ) { buf.bass_freq( freq ); }
virtual void clear() { buf.clear(); } void clear() { buf.clear(); }
virtual int samples_avail() const { return buf.samples_avail(); } long samples_avail() const { return buf.samples_avail(); }
virtual int read_samples( blip_sample_t p [], int s ) { return buf.read_samples( p, s ); } long read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); }
virtual channel_t channel( int ) { return chan; } channel_t channel( int, int ) { return chan; }
virtual void end_frame( blip_time_t t ) { buf.end_frame( t ); } 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. // Uses three buffers (one for center) and outputs stereo sample pairs.
class Stereo_Buffer : public Multi_Buffer { class Stereo_Buffer : public Multi_Buffer {
public: public:
// Buffers used for all channels // Buffers used for all channels
Blip_Buffer* center() { return &bufs [2]; } Blip_Buffer* center() { return &bufs [0]; }
Blip_Buffer* left() { return &bufs [0]; } Blip_Buffer* left() { return &bufs [1]; }
Blip_Buffer* right() { return &bufs [1]; } Blip_Buffer* right() { return &bufs [2]; }
// Implementation
public: public:
Stereo_Buffer(); Stereo_Buffer();
~Stereo_Buffer(); ~Stereo_Buffer();
virtual blargg_err_t set_sample_rate( int, int msec = blip_default_length ); blargg_err_t set_sample_rate( long, int msec = blip_default_length );
virtual void clock_rate( int ); void clock_rate( long );
virtual void bass_freq( int ); void bass_freq( int );
virtual void clear(); void clear();
virtual channel_t channel( int ) { return chan; } channel_t channel( int, int ) { return chan; }
virtual void end_frame( blip_time_t ); 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 ); long samples_avail() const { return bufs [0].samples_avail() * 2; }
long read_samples( blip_sample_t*, long );
private: private:
enum { bufs_size = 3 }; enum { buf_count = 3 };
typedef Tracked_Blip_Buffer buf_t; Blip_Buffer bufs [buf_count];
buf_t bufs [bufs_size];
Stereo_Mixer mixer;
channel_t chan; channel_t chan;
int samples_avail_; int stereo_added;
}; int was_stereo;
void mix_stereo_no_center( blip_sample_t*, blargg_long );
void mix_stereo( blip_sample_t*, blargg_long );
void mix_mono( blip_sample_t*, blargg_long );
};
// Silent_Buffer generates no samples, useful where no sound is wanted // Silent_Buffer generates no samples, useful where no sound is wanted
class Silent_Buffer : public Multi_Buffer { class Silent_Buffer : public Multi_Buffer {
channel_t chan; channel_t chan;
public: public:
Silent_Buffer(); Silent_Buffer();
virtual blargg_err_t set_sample_rate( int rate, int msec = blip_default_length ); blargg_err_t set_sample_rate( long rate, int msec = blip_default_length );
virtual void clock_rate( int ) { } void clock_rate( long ) { }
virtual void bass_freq( int ) { } void bass_freq( int ) { }
virtual void clear() { } void clear() { }
virtual channel_t channel( int ) { return chan; } channel_t channel( int, int ) { return chan; }
virtual void end_frame( blip_time_t ) { } void end_frame( blip_time_t ) { }
virtual int samples_avail() const { return 0; } long samples_avail() const { return 0; }
virtual int read_samples( blip_sample_t [], int ) { return 0; } long read_samples( blip_sample_t*, long ) { return 0; }
}; };
inline blargg_err_t Multi_Buffer::set_sample_rate( int rate, int msec ) inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec )
{ {
sample_rate_ = rate; sample_rate_ = rate;
length_ = msec; length_ = msec;
return blargg_ok; return 0;
} }
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec )
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 ); return Multi_Buffer::set_sample_rate( rate, msec );
} }
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
inline long Multi_Buffer::sample_rate() const { return sample_rate_; }
inline int Multi_Buffer::length() const { return length_; }
#endif #endif

View file

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

View file

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

View file

@ -1,8 +1,8 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Apu.h" #include "Nes_Apu.h"
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -23,6 +23,8 @@ Nes_Apu::Nes_Apu() :
{ {
tempo_ = 1.0; tempo_ = 1.0;
dmc.apu = this; dmc.apu = this;
dmc.prg_reader = NULL;
irq_notifier_ = NULL;
oscs [0] = &square1; oscs [0] = &square1;
oscs [1] = &square2; oscs [1] = &square2;
@ -30,28 +32,28 @@ Nes_Apu::Nes_Apu() :
oscs [3] = &noise; oscs [3] = &noise;
oscs [4] = &dmc; oscs [4] = &dmc;
set_output( NULL ); output( NULL );
dmc.nonlinear = false;
volume( 1.0 ); volume( 1.0 );
reset( false ); reset( false );
} }
void Nes_Apu::treble_eq( const blip_eq_t& eq ) void Nes_Apu::treble_eq( const blip_eq_t& eq )
{ {
square_synth .treble_eq( eq ); square_synth.treble_eq( eq );
triangle.synth.treble_eq( eq ); triangle.synth.treble_eq( eq );
noise .synth.treble_eq( eq ); noise.synth.treble_eq( eq );
dmc .synth.treble_eq( eq ); dmc.synth.treble_eq( eq );
} }
void Nes_Apu::enable_nonlinear_( double sq, double tnd ) void Nes_Apu::enable_nonlinear( double v )
{ {
dmc.nonlinear = true; dmc.nonlinear = true;
square_synth.volume( sq ); square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v );
triangle.synth.volume( tnd * 2.752 ); const double tnd = 0.48 / 202 * nonlinear_tnd_gain();
noise .synth.volume( tnd * 1.849 ); triangle.synth.volume( 3.0 * tnd );
dmc .synth.volume( tnd ); noise.synth.volume( 2.0 * tnd );
dmc.synth.volume( tnd );
square1 .last_amp = 0; square1 .last_amp = 0;
square2 .last_amp = 0; square2 .last_amp = 0;
@ -62,20 +64,17 @@ void Nes_Apu::enable_nonlinear_( double sq, double tnd )
void Nes_Apu::volume( double v ) void Nes_Apu::volume( double v )
{ {
if ( !dmc.nonlinear ) dmc.nonlinear = false;
{ square_synth.volume( 0.1128 / amp_range * v );
v *= 1.0 / 1.11; // TODO: merge into values below triangle.synth.volume( 0.12765 / amp_range * v );
square_synth .volume( 0.125 / amp_range * v ); // was 0.1128 1.108 noise.synth.volume( 0.0741 / amp_range * v );
triangle.synth.volume( 0.150 / amp_range * v ); // was 0.12765 1.175 dmc.synth.volume( 0.42545 / 127 * v );
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 ) void Nes_Apu::output( Blip_Buffer* buffer )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; i++ )
set_output( i, buffer ); osc_output( i, buffer );
} }
void Nes_Apu::set_tempo( double t ) void Nes_Apu::set_tempo( double t )
@ -101,13 +100,12 @@ void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
last_dmc_time = 0; last_dmc_time = 0;
osc_enables = 0; osc_enables = 0;
irq_flag = false; irq_flag = false;
enable_w4011 = true;
earliest_irq_ = no_irq; earliest_irq_ = no_irq;
frame_delay = 1; frame_delay = 1;
write_register( 0, 0x4017, 0x00 ); write_register( 0, 0x4017, 0x00 );
write_register( 0, 0x4015, 0x00 ); write_register( 0, 0x4015, 0x00 );
for ( int addr = io_addr; addr <= 0x4013; addr++ ) for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ )
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
dmc.dac = initial_dmc_dac; dmc.dac = initial_dmc_dac;
@ -119,7 +117,7 @@ void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
void Nes_Apu::irq_changed() void Nes_Apu::irq_changed()
{ {
blip_time_t new_irq = dmc.next_irq; nes_time_t new_irq = dmc.next_irq;
if ( dmc.irq_flag | irq_flag ) { if ( dmc.irq_flag | irq_flag ) {
new_irq = 0; new_irq = 0;
} }
@ -129,25 +127,25 @@ void Nes_Apu::irq_changed()
if ( new_irq != earliest_irq_ ) { if ( new_irq != earliest_irq_ ) {
earliest_irq_ = new_irq; earliest_irq_ = new_irq;
if ( irq_notifier.f ) if ( irq_notifier_ )
irq_notifier.f( irq_notifier.data ); irq_notifier_( irq_data );
} }
} }
// frames // frames
void Nes_Apu::run_until( blip_time_t end_time ) void Nes_Apu::run_until( nes_time_t end_time )
{ {
require( end_time >= last_dmc_time ); require( end_time >= last_dmc_time );
if ( end_time > next_dmc_read_time() ) if ( end_time > next_dmc_read_time() )
{ {
blip_time_t start = last_dmc_time; nes_time_t start = last_dmc_time;
last_dmc_time = end_time; last_dmc_time = end_time;
dmc.run( start, end_time ); dmc.run( start, end_time );
} }
} }
void Nes_Apu::run_until_( blip_time_t end_time ) void Nes_Apu::run_until_( nes_time_t end_time )
{ {
require( end_time >= last_time ); require( end_time >= last_time );
@ -156,7 +154,7 @@ void Nes_Apu::run_until_( blip_time_t end_time )
if ( last_dmc_time < end_time ) if ( last_dmc_time < end_time )
{ {
blip_time_t start = last_dmc_time; nes_time_t start = last_dmc_time;
last_dmc_time = end_time; last_dmc_time = end_time;
dmc.run( start, end_time ); dmc.run( start, end_time );
} }
@ -164,7 +162,7 @@ void Nes_Apu::run_until_( blip_time_t end_time )
while ( true ) while ( true )
{ {
// earlier of next frame time or end time // earlier of next frame time or end time
blip_time_t time = last_time + frame_delay; nes_time_t time = last_time + frame_delay;
if ( time > end_time ) if ( time > end_time )
time = end_time; time = end_time;
frame_delay -= time - last_time; frame_delay -= time - last_time;
@ -228,7 +226,7 @@ void Nes_Apu::run_until_( blip_time_t end_time )
} }
template<class T> template<class T>
inline void zero_apu_osc( T* osc, blip_time_t time ) inline void zero_apu_osc( T* osc, nes_time_t time )
{ {
Blip_Buffer* output = osc->output; Blip_Buffer* output = osc->output;
int last_amp = osc->last_amp; int last_amp = osc->last_amp;
@ -237,7 +235,7 @@ inline void zero_apu_osc( T* osc, blip_time_t time )
osc->synth.offset( time, -last_amp, output ); osc->synth.offset( time, -last_amp, output );
} }
void Nes_Apu::end_frame( blip_time_t end_time ) void Nes_Apu::end_frame( nes_time_t end_time )
{ {
if ( end_time > last_time ) if ( end_time > last_time )
run_until_( end_time ); run_until_( end_time );
@ -282,13 +280,13 @@ static const unsigned char length_table [0x20] = {
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
}; };
void Nes_Apu::write_register( blip_time_t time, int addr, int data ) void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data )
{ {
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
require( (unsigned) data <= 0xFF ); require( (unsigned) data <= 0xFF );
// Ignore addresses outside range // Ignore addresses outside range
if ( unsigned (addr - io_addr) >= io_size ) if ( unsigned (addr - start_addr) > end_addr - start_addr )
return; return;
run_until_( time ); run_until_( time );
@ -296,7 +294,7 @@ void Nes_Apu::write_register( blip_time_t time, int addr, int data )
if ( addr < 0x4014 ) if ( addr < 0x4014 )
{ {
// Write to channel // Write to channel
int osc_index = (addr - io_addr) >> 2; int osc_index = (addr - start_addr) >> 2;
Nes_Osc* osc = oscs [osc_index]; Nes_Osc* osc = oscs [osc_index];
int reg = addr & 3; int reg = addr & 3;
@ -306,8 +304,7 @@ void Nes_Apu::write_register( blip_time_t time, int addr, int data )
if ( osc_index == 4 ) if ( osc_index == 4 )
{ {
// handle DMC specially // handle DMC specially
if ( enable_w4011 || reg != 1 ) dmc.write_register( reg, data );
dmc.write_register( reg, data );
} }
else if ( reg == 3 ) else if ( reg == 3 )
{ {
@ -369,7 +366,7 @@ void Nes_Apu::write_register( blip_time_t time, int addr, int data )
} }
} }
int Nes_Apu::read_status( blip_time_t time ) int Nes_Apu::read_status( nes_time_t time )
{ {
run_until_( time - 1 ); run_until_( time - 1 );
@ -388,7 +385,7 @@ int Nes_Apu::read_status( blip_time_t time )
irq_changed(); irq_changed();
} }
//dprintf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result ); //debug_printf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
return result; return result;
} }

View file

@ -1,10 +1,14 @@
// NES 2A03 APU sound chip emulator // NES 2A03 APU sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#ifndef NES_APU_H #ifndef NES_APU_H
#define NES_APU_H #define NES_APU_H
#include "blargg_common.h" #include "blargg_common.h"
typedef blargg_long nes_time_t; // CPU clock cycle count
typedef unsigned nes_addr_t; // 16-bit memory address
#include "Nes_Oscs.h" #include "Nes_Oscs.h"
struct apu_state_t; struct apu_state_t;
@ -12,76 +16,73 @@ class Nes_Buffer;
class Nes_Apu { class Nes_Apu {
public: public:
// Basics // Set buffer to generate all sound into, or disable sound if NULL
void output( Blip_Buffer* );
typedef int nes_time_t; // NES CPU clock cycle count // Set memory reader callback used by DMC oscillator to fetch samples.
// Sets memory reader callback used by DMC oscillator to fetch samples.
// When callback is invoked, 'user_data' is passed unchanged as the // When callback is invoked, 'user_data' is passed unchanged as the
// first parameter. // first parameter.
//void dmc_reader( int (*callback)( void* user_data, int addr ), void* user_data = NULL ); void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), 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 // All time values are the number of CPU clock cycles relative to the
// beginning of the current time frame. Before resetting the CPU clock // beginning of the current time frame. Before resetting the CPU clock
// count, call end_frame( last_cpu_time ). // count, call end_frame( last_cpu_time ).
// Writes to register (0x4000-0x4013, and 0x4015 and 0x4017) // Write to register (0x4000-0x4017, except 0x4014 and 0x4016)
enum { io_addr = 0x4000 }; enum { start_addr = 0x4000 };
enum { io_size = 0x18 }; enum { end_addr = 0x4017 };
void write_register( nes_time_t, int addr, int data ); void write_register( nes_time_t, nes_addr_t, int data );
// Reads from status register (0x4015) // Read from status register at 0x4015
enum { status_addr = 0x4015 }; enum { status_addr = 0x4015 };
int read_status( nes_time_t ); int read_status( nes_time_t );
// Runs all oscillators up to specified time, ends current time frame, then // Run all oscillators up to specified time, end current time frame, then
// starts a new time frame at time 0. Time frames have no effect on emulation // start a new time frame at time 0. Time frames have no effect on emulation
// and each can be whatever length is convenient. // and each can be whatever length is convenient.
void end_frame( nes_time_t ); void end_frame( nes_time_t );
// Optional // Additional optional features (can be ignored without any problem)
// Resets internal frame counter, registers, and all oscillators. // Reset internal frame counter, registers, and all oscillators.
// Uses PAL timing if pal_timing is true, otherwise use NTSC timing. // Use PAL timing if pal_timing is true, otherwise use NTSC timing.
// Sets the DMC oscillator's initial DAC value to initial_dmc_dac without // Set the DMC oscillator's initial DAC value to initial_dmc_dac without
// any audible click. // any audible click.
void reset( bool pal_mode = false, int initial_dmc_dac = 0 ); void reset( bool pal_mode = false, int initial_dmc_dac = 0 );
// Same as set_output(), but for a particular channel // Adjust frame period
// 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 ); void set_tempo( double );
// Saves/loads exact emulation state // Save/load exact emulation state
void save_state( apu_state_t* out ) const; void save_state( apu_state_t* out ) const;
void load_state( apu_state_t const& ); void load_state( apu_state_t const& );
// Sets overall volume (default is 1.0) // Set overall volume (default is 1.0)
void volume( double ); void volume( double );
// Sets treble equalization (see notes.txt) // Set treble equalization (see notes.txt)
void treble_eq( const blip_eq_t& ); void treble_eq( const blip_eq_t& );
// Sets IRQ time callback that is invoked when the time of earliest IRQ // Set sound output of specific oscillator to buffer. If buffer is NULL,
// the specified oscillator is muted and emulation accuracy is reduced.
// The oscillators are indexed as follows: 0) Square 1, 1) Square 2,
// 2) Triangle, 3) Noise, 4) DMC.
enum { osc_count = 5 };
void osc_output( int index, Blip_Buffer* buffer );
// Set IRQ time callback that is invoked when the time of earliest IRQ
// may have changed, or NULL to disable. When callback is invoked, // may have changed, or NULL to disable. When callback is invoked,
// 'user_data' is passed unchanged as the first parameter. // 'user_data' is passed unchanged as the first parameter.
//void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
// Gets time that APU-generated IRQ will occur if no further register reads // Get time that APU-generated IRQ will occur if no further register reads
// or writes occur. If IRQ is already pending, returns irq_waiting. If no // or writes occur. If IRQ is already pending, returns irq_waiting. If no
// IRQ will occur, returns no_irq. // IRQ will occur, returns no_irq.
enum { no_irq = INT_MAX/2 + 1 }; enum { no_irq = INT_MAX / 2 + 1 };
enum { irq_waiting = 0 }; enum { irq_waiting = 0 };
nes_time_t earliest_irq( nes_time_t ) const; nes_time_t earliest_irq( nes_time_t ) const;
// Counts number of DMC reads that would occur if 'run_until( t )' were executed. // Count number of DMC reads that would occur if 'run_until( t )' were executed.
// If last_read is not NULL, set *last_read to the earliest time that // If last_read is not NULL, set *last_read to the earliest time that
// 'count_dmc_reads( time )' would result in the same result. // 'count_dmc_reads( time )' would result in the same result.
int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const;
@ -89,30 +90,17 @@ public:
// Time when next DMC memory read will occur // Time when next DMC memory read will occur
nes_time_t next_dmc_read_time() const; nes_time_t next_dmc_read_time() const;
// Runs DMC until specified time, so that any DMC memory reads can be // Run DMC until specified time, so that any DMC memory reads can be
// accounted for (i.e. inserting CPU wait states). // accounted for (i.e. inserting CPU wait states).
void run_until( nes_time_t ); void run_until( nes_time_t );
// Implementation
public: public:
Nes_Apu(); Nes_Apu();
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
// Use set_output() in place of these private:
BLARGG_DEPRECATED( void output ( Blip_Buffer* c ); ) friend class Nes_Nonlinearizer;
BLARGG_DEPRECATED( void osc_output( int i, Blip_Buffer* c ); ) void enable_nonlinear( double volume );
static double nonlinear_tnd_gain() { return 0.75; }
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: private:
friend struct Nes_Dmc; friend struct Nes_Dmc;
@ -138,7 +126,8 @@ private:
int osc_enables; int osc_enables;
int frame_mode; int frame_mode;
bool irq_flag; bool irq_flag;
bool enable_w4011; void (*irq_notifier_)( void* user_data );
void* irq_data;
Nes_Square::Synth square_synth; // shared by squares Nes_Square::Synth square_synth; // shared by squares
void irq_changed(); void irq_changed();
@ -149,36 +138,42 @@ private:
friend class Nes_Core; friend class Nes_Core;
}; };
inline void Nes_Apu::set_output( int osc, Blip_Buffer* buf ) inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf )
{ {
assert( (unsigned) osc < osc_count ); assert( (unsigned) osc < osc_count );
oscs [osc]->output = buf; oscs [osc]->output = buf;
} }
inline Nes_Apu::nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const
{ {
return earliest_irq_; return earliest_irq_;
} }
inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data )
{
dmc.prg_reader_data = user_data;
dmc.prg_reader = func;
}
inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data )
{
irq_notifier_ = func;
irq_data = user_data;
}
inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const
{ {
return dmc.count_reads( time, last_read ); return dmc.count_reads( time, last_read );
} }
inline Nes_Apu::nes_time_t Nes_Dmc::next_read_time() const inline nes_time_t Nes_Dmc::next_read_time() const
{ {
if ( length_counter == 0 ) if ( length_counter == 0 )
return Nes_Apu::no_irq; // not reading return Nes_Apu::no_irq; // not reading
return apu->last_dmc_time + delay + (bits_remain - 1) * period; return apu->last_dmc_time + delay + long (bits_remain - 1) * period;
} }
inline Nes_Apu::nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); }
BLARGG_DEPRECATED( typedef int nes_time_t; ) // use your own typedef
BLARGG_DEPRECATED( typedef unsigned nes_addr_t; ) // use your own typedef
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 #endif

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -12,7 +12,6 @@ public:
// setup // setup
void set_tempo( double ); void set_tempo( double );
enum { osc_count = 1 }; enum { osc_count = 1 };
void set_output( Blip_Buffer* buf );
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
@ -29,11 +28,6 @@ public:
void write_( unsigned addr, int data ); void write_( unsigned addr, int data );
BLARGG_DISABLE_NOTHROW BLARGG_DISABLE_NOTHROW
void set_output( int index, Blip_Buffer* center,
Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL );
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; )
BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; )
void osc_output( int, Blip_Buffer* ); void osc_output( int, Blip_Buffer* );
private: private:
enum { wave_size = 0x40 }; enum { wave_size = 0x40 };
@ -66,7 +60,7 @@ private:
// synthesis // synthesis
blip_time_t last_time; blip_time_t last_time;
Blip_Buffer* output_; Blip_Buffer* output_;
Blip_Synth_Fast synth; Blip_Synth<blip_med_quality,1> synth;
// allow access to registers by absolute address (i.e. 0x4080) // allow access to registers by absolute address (i.e. 0x4080)
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; } unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; }
@ -79,12 +73,7 @@ inline void Nes_Fds_Apu::volume( double v )
synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v ); synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v );
} }
inline void Nes_Fds_Apu::set_output( Blip_Buffer* b ) inline void Nes_Fds_Apu::osc_output( int i, Blip_Buffer* buf )
{
output_ = b;
}
inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
output_ = buf; output_ = buf;
@ -131,7 +120,7 @@ inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
inline Nes_Fds_Apu::Nes_Fds_Apu() inline Nes_Fds_Apu::Nes_Fds_Apu()
{ {
lfo_tempo = lfo_base_tempo; lfo_tempo = lfo_base_tempo;
set_output( NULL ); osc_output( 0, NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }

View file

@ -1,7 +1,9 @@
// $package. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Nes_Fme7_Apu.h" #include "Nes_Fme7_Apu.h"
#include <string.h>
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
@ -49,11 +51,12 @@ void Nes_Fme7_Apu::run_until( blip_time_t end_time )
Blip_Buffer* const osc_output = oscs [index].output; Blip_Buffer* const osc_output = oscs [index].output;
if ( !osc_output ) if ( !osc_output )
continue; continue;
osc_output->set_modified();
// check for unsupported mode // check for unsupported mode
#ifndef NDEBUG #ifndef NDEBUG
if ( (mode & 011) <= 001 && vol_mode & 0x1F ) if ( (mode & 011) <= 001 && vol_mode & 0x1F )
dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n", debug_printf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n",
mode, vol_mode & 0x1F ); mode, vol_mode & 0x1F );
#endif #endif
@ -75,13 +78,11 @@ void Nes_Fme7_Apu::run_until( blip_time_t end_time )
int amp = volume; int amp = volume;
if ( !phases [index] ) if ( !phases [index] )
amp = 0; amp = 0;
{ {
int delta = amp - oscs [index].last_amp; int delta = amp - oscs [index].last_amp;
if ( delta ) if ( delta )
{ {
oscs [index].last_amp = amp; oscs [index].last_amp = amp;
osc_output->set_modified();
synth.offset( last_time, delta, osc_output ); synth.offset( last_time, delta, osc_output );
} }
} }
@ -90,7 +91,6 @@ void Nes_Fme7_Apu::run_until( blip_time_t end_time )
if ( time < end_time ) if ( time < end_time )
{ {
int delta = amp * 2 - volume; int delta = amp * 2 - volume;
osc_output->set_modified();
if ( volume ) if ( volume )
{ {
do do
@ -109,7 +109,7 @@ void Nes_Fme7_Apu::run_until( blip_time_t end_time )
// maintain phase when silent // maintain phase when silent
int count = (end_time - time + period - 1) / period; int count = (end_time - time + period - 1) / period;
phases [index] ^= count & 1; phases [index] ^= count & 1;
time += count * period; time += (blargg_long) count * period;
} }
} }

View file

@ -1,6 +1,6 @@
// Sunsoft FME-7 sound emulator // Sunsoft FME-7 sound emulator
// $package // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#ifndef NES_FME7_APU_H #ifndef NES_FME7_APU_H
#define NES_FME7_APU_H #define NES_FME7_APU_H
@ -10,10 +10,10 @@
struct fme7_apu_state_t struct fme7_apu_state_t
{ {
enum { reg_count = 14 }; enum { reg_count = 14 };
BOOST::uint8_t regs [reg_count]; uint8_t regs [reg_count];
BOOST::uint8_t phases [3]; // 0 or 1 uint8_t phases [3]; // 0 or 1
BOOST::uint8_t latch; uint8_t latch;
BOOST::uint16_t delays [3]; // a, b, c uint16_t delays [3]; // a, b, c
}; };
class Nes_Fme7_Apu : private fme7_apu_state_t { class Nes_Fme7_Apu : private fme7_apu_state_t {
@ -22,9 +22,9 @@ public:
void reset(); void reset();
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& ); void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* ); void output( Blip_Buffer* );
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int index, Blip_Buffer* ); void osc_output( int index, Blip_Buffer* );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
void save_state( fme7_apu_state_t* ) const; void save_state( fme7_apu_state_t* ) const;
void load_state( fme7_apu_state_t const& ); void load_state( fme7_apu_state_t const& );
@ -57,7 +57,7 @@ private:
blip_time_t last_time; blip_time_t last_time;
enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff
Blip_Synth_Norm synth; Blip_Synth<blip_good_quality,1> synth;
void run_until( blip_time_t ); void run_until( blip_time_t );
}; };
@ -72,21 +72,21 @@ inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq )
synth.treble_eq( eq ); synth.treble_eq( eq );
} }
inline void Nes_Fme7_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;
} }
inline void Nes_Fme7_Apu::set_output( Blip_Buffer* buf ) inline void Nes_Fme7_Apu::output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; i++ )
set_output( i, buf ); osc_output( i, buf );
} }
inline Nes_Fme7_Apu::Nes_Fme7_Apu() inline Nes_Fme7_Apu::Nes_Fme7_Apu()
{ {
set_output( NULL ); output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
@ -97,8 +97,8 @@ inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data )
{ {
if ( (unsigned) latch >= reg_count ) if ( (unsigned) latch >= reg_count )
{ {
#ifdef dprintf #ifdef debug_printf
dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); debug_printf( "FME7 write to %02X (past end of sound registers)\n", (int) latch );
#endif #endif
return; return;
} }

View file

@ -14,30 +14,20 @@ public:
enum { osc_count = 3 }; enum { osc_count = 3 };
void write_register( blip_time_t, unsigned addr, int data ); void write_register( blip_time_t, unsigned addr, int data );
void set_output( Blip_Buffer* ); void osc_output( int i, Blip_Buffer* );
void set_output( int index, Blip_Buffer* );
enum { exram_size = 1024 }; enum { exram_size = 1024 };
unsigned char exram [exram_size]; unsigned char exram [exram_size];
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; )
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; )
}; };
inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b ) inline void Nes_Mmc5_Apu::osc_output( int i, Blip_Buffer* b )
{ {
// in: square 1, square 2, PCM // in: square 1, square 2, PCM
// out: square 1, square 2, skipped, skipped, PCM // out: square 1, square 2, skipped, skipped, PCM
assert( (unsigned) i < osc_count );
if ( i > 1 ) if ( i > 1 )
i += 2; i += 2;
Nes_Apu::set_output( i, b ); Nes_Apu::osc_output( i, b );
}
inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b )
{
set_output( 0, b );
set_output( 1, b );
set_output( 2, b );
} }
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data ) inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )

View file

@ -1,4 +1,4 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Namco_Apu.h" #include "Nes_Namco_Apu.h"
@ -17,7 +17,7 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
Nes_Namco_Apu::Nes_Namco_Apu() Nes_Namco_Apu::Nes_Namco_Apu()
{ {
set_output( NULL ); output( NULL );
volume( 1.0 ); volume( 1.0 );
reset(); reset();
} }
@ -36,13 +36,14 @@ void Nes_Namco_Apu::reset()
Namco_Osc& osc = oscs [i]; Namco_Osc& osc = oscs [i];
osc.delay = 0; osc.delay = 0;
osc.last_amp = 0; osc.last_amp = 0;
osc.wave_pos = 0;
} }
} }
void Nes_Namco_Apu::set_output( Blip_Buffer* buf ) void Nes_Namco_Apu::output( Blip_Buffer* buf )
{ {
for ( int i = 0; i < osc_count; ++i ) for ( int i = 0; i < osc_count; i++ )
set_output( i, buf ); osc_output( i, buf );
} }
/* /*
@ -81,6 +82,7 @@ void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
Blip_Buffer* output = osc.output; Blip_Buffer* output = osc.output;
if ( !output ) if ( !output )
continue; continue;
output->set_modified();
blip_resampled_time_t time = blip_resampled_time_t time =
output->resampled_time( last_time ) + osc.delay; output->resampled_time( last_time ) + osc.delay;
@ -88,7 +90,7 @@ void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
osc.delay = 0; osc.delay = 0;
if ( time < end_time ) if ( time < end_time )
{ {
const BOOST::uint8_t* osc_reg = &reg [i * 8 + 0x40]; const uint8_t* osc_reg = &reg [i * 8 + 0x40];
if ( !(osc_reg [4] & 0xE0) ) if ( !(osc_reg [4] & 0xE0) )
continue; continue;
@ -96,29 +98,23 @@ void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
if ( !volume ) if ( !volume )
continue; continue;
int freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100 + osc_reg [0]; blargg_long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0];
if ( freq < 64 * active_oscs ) if ( freq < 64 * active_oscs )
continue; // prevent low frequencies from excessively delaying freq changes 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 = blip_resampled_time_t period =
output->resampled_duration( lowest_freq_period / 8 ) / freq * 8 * active_oscs; output->resampled_duration( 983040 ) / freq * active_oscs;
int wave_size = 256 - (osc_reg [4] & 0xFC); int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4;
if ( !wave_size )
continue;
int last_amp = osc.last_amp; int last_amp = osc.last_amp;
int wave_pos = osc_reg [5] % wave_size; int wave_pos = osc.wave_pos;
output->set_modified();
do do
{ {
// read wave sample // read wave sample
int addr = (wave_pos + osc_reg [6]) & 0xFF; int addr = wave_pos + osc_reg [6];
int sample = reg [addr >> 1] >> (addr << 2 & 4); int sample = reg [addr >> 1] >> (addr << 2 & 4);
wave_pos++; wave_pos++;
sample = (sample & 15) * volume; sample = (sample & 15) * volume;
@ -138,7 +134,7 @@ void Nes_Namco_Apu::run_until( blip_time_t nes_end_time )
} }
while ( time < end_time ); while ( time < end_time );
((BOOST::uint8_t*)osc_reg)[5] = wave_pos; osc.wave_pos = wave_pos;
osc.last_amp = last_amp; osc.last_amp = last_amp;
} }
osc.delay = time - end_time; osc.delay = time - end_time;

View file

@ -1,6 +1,6 @@
// Namco 106 sound chip emulator // Namco 106 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#ifndef NES_NAMCO_APU_H #ifndef NES_NAMCO_APU_H
#define NES_NAMCO_APU_H #define NES_NAMCO_APU_H
@ -14,9 +14,9 @@ public:
// See Nes_Apu.h for reference. // See Nes_Apu.h for reference.
void volume( double ); void volume( double );
void treble_eq( const blip_eq_t& ); void treble_eq( const blip_eq_t& );
void set_output( Blip_Buffer* ); void output( Blip_Buffer* );
enum { osc_count = 8 }; enum { osc_count = 8 };
void set_output( int index, Blip_Buffer* ); void osc_output( int index, Blip_Buffer* );
void reset(); void reset();
void end_frame( blip_time_t ); void end_frame( blip_time_t );
@ -42,9 +42,10 @@ private:
Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& );
struct Namco_Osc { struct Namco_Osc {
int delay; blargg_long delay;
Blip_Buffer* output; Blip_Buffer* output;
short last_amp; short last_amp;
short wave_pos;
}; };
Namco_Osc oscs [osc_count]; Namco_Osc oscs [osc_count];
@ -53,24 +54,24 @@ private:
int addr_reg; int addr_reg;
enum { reg_count = 0x80 }; enum { reg_count = 0x80 };
BOOST::uint8_t reg [reg_count]; uint8_t reg [reg_count];
Blip_Synth_Norm synth; Blip_Synth<blip_good_quality,15> synth;
BOOST::uint8_t& access(); uint8_t& access();
void run_until( blip_time_t ); void run_until( blip_time_t );
}; };
/* /*
struct namco_state_t struct namco_state_t
{ {
BOOST::uint8_t regs [0x80]; uint8_t regs [0x80];
BOOST::uint8_t addr; uint8_t addr;
BOOST::uint8_t unused; uint8_t unused;
BOOST::uint8_t positions [8]; uint8_t positions [8];
BOOST::uint32_t delays [8]; uint32_t delays [8];
}; };
*/ */
inline BOOST::uint8_t& Nes_Namco_Apu::access() inline uint8_t& Nes_Namco_Apu::access()
{ {
int addr = addr_reg & 0x7F; int addr = addr_reg & 0x7F;
if ( addr_reg & 0x80 ) if ( addr_reg & 0x80 )
@ -78,7 +79,7 @@ inline BOOST::uint8_t& Nes_Namco_Apu::access()
return reg [addr]; return reg [addr];
} }
inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count / 15 * v ); } inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); }
inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
@ -86,7 +87,7 @@ inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; }
inline int Nes_Namco_Apu::read_data() { return access(); } inline int Nes_Namco_Apu::read_data() { return access(); }
inline void Nes_Namco_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;

View file

@ -1,4 +1,4 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Apu.h" #include "Nes_Apu.h"
@ -26,14 +26,12 @@ void Nes_Osc::clock_length( int halt_mask )
void Nes_Envelope::clock_envelope() void Nes_Envelope::clock_envelope()
{ {
int period = regs [0] & 15; int period = regs [0] & 15;
if ( reg_written [3] ) if ( reg_written [3] ) {
{
reg_written [3] = false; reg_written [3] = false;
env_delay = period; env_delay = period;
envelope = 15; envelope = 15;
} }
else if ( --env_delay < 0 ) else if ( --env_delay < 0 ) {
{
env_delay = period; env_delay = period;
if ( envelope | (regs [0] & 0x20) ) if ( envelope | (regs [0] & 0x20) )
envelope = (envelope - 1) & 15; envelope = (envelope - 1) & 15;
@ -74,15 +72,14 @@ void Nes_Square::clock_sweep( int negative_adjust )
} }
} }
if ( reg_written [1] ) if ( reg_written [1] ) {
{
reg_written [1] = false; reg_written [1] = false;
sweep_delay = (sweep >> 4) & 7; sweep_delay = (sweep >> 4) & 7;
} }
} }
// TODO: clean up // TODO: clean up
inline Nes_Square::nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time, inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period ) nes_time_t timer_period )
{ {
nes_time_t remain = end_time - time; nes_time_t remain = end_time - time;
@ -90,7 +87,7 @@ inline Nes_Square::nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_t
{ {
int count = (remain + timer_period - 1) / timer_period; int count = (remain + timer_period - 1) / timer_period;
phase = (phase + count) & (phase_range - 1); phase = (phase + count) & (phase_range - 1);
time += count * timer_period; time += (blargg_long) count * timer_period;
} }
return time; return time;
} }
@ -106,6 +103,8 @@ void Nes_Square::run( nes_time_t time, nes_time_t end_time )
return; return;
} }
output->set_modified();
int offset = period >> (regs [1] & shift_mask); int offset = period >> (regs [1] & shift_mask);
if ( regs [1] & negate_flag ) if ( regs [1] & negate_flag )
offset = 0; offset = 0;
@ -113,9 +112,7 @@ void Nes_Square::run( nes_time_t time, nes_time_t end_time )
const int volume = this->volume(); const int volume = this->volume();
if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) if ( volume == 0 || period < 8 || (period + offset) >= 0x800 )
{ {
if ( last_amp ) if ( last_amp ) {
{
output->set_modified();
synth.offset( time, -last_amp, output ); synth.offset( time, -last_amp, output );
last_amp = 0; last_amp = 0;
} }
@ -129,15 +126,13 @@ void Nes_Square::run( nes_time_t time, nes_time_t end_time )
int duty_select = (regs [0] >> 6) & 3; int duty_select = (regs [0] >> 6) & 3;
int duty = 1 << duty_select; // 1, 2, 4, 2 int duty = 1 << duty_select; // 1, 2, 4, 2
int amp = 0; int amp = 0;
if ( duty_select == 3 ) if ( duty_select == 3 ) {
{
duty = 2; // negated 25% duty = 2; // negated 25%
amp = volume; amp = volume;
} }
if ( phase < duty ) if ( phase < duty )
amp ^= volume; amp ^= volume;
output->set_modified();
{ {
int delta = update_amp( amp ); int delta = update_amp( amp );
if ( delta ) if ( delta )
@ -152,11 +147,9 @@ void Nes_Square::run( nes_time_t time, nes_time_t end_time )
int delta = amp * 2 - volume; int delta = amp * 2 - volume;
int phase = this->phase; int phase = this->phase;
do do {
{
phase = (phase + 1) & (phase_range - 1); phase = (phase + 1) & (phase_range - 1);
if ( phase == 0 || phase == duty ) if ( phase == 0 || phase == duty ) {
{
delta = -delta; delta = -delta;
synth.offset_inline( time, delta, output ); synth.offset_inline( time, delta, output );
} }
@ -194,7 +187,7 @@ inline int Nes_Triangle::calc_amp() const
} }
// TODO: clean up // TODO: clean up
inline Nes_Square::nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time, inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time,
nes_time_t timer_period ) nes_time_t timer_period )
{ {
nes_time_t remain = end_time - time; nes_time_t remain = end_time - time;
@ -203,7 +196,7 @@ inline Nes_Square::nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes
int count = (remain + timer_period - 1) / timer_period; int count = (remain + timer_period - 1) / timer_period;
phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1); phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1);
phase++; phase++;
time += count * timer_period; time += (blargg_long) count * timer_period;
} }
return time; return time;
} }
@ -220,15 +213,14 @@ void Nes_Triangle::run( nes_time_t time, nes_time_t end_time )
return; return;
} }
output->set_modified();
// to do: track phase when period < 3 // to do: track phase when period < 3
// to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks.
int delta = update_amp( calc_amp() ); int delta = update_amp( calc_amp() );
if ( delta ) if ( delta )
{
output->set_modified();
synth.offset( time, delta, output ); synth.offset( time, delta, output );
}
time += delay; time += delay;
if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 )
@ -241,22 +233,17 @@ void Nes_Triangle::run( nes_time_t time, nes_time_t end_time )
int phase = this->phase; int phase = this->phase;
int volume = 1; int volume = 1;
if ( phase > phase_range ) if ( phase > phase_range ) {
{
phase -= phase_range; phase -= phase_range;
volume = -volume; volume = -volume;
} }
output->set_modified();
do do {
{ if ( --phase == 0 ) {
if ( --phase == 0 )
{
phase = phase_range; phase = phase_range;
volume = -volume; volume = -volume;
} }
else else {
{
synth.offset_inline( time, volume, output ); synth.offset_inline( time, volume, output );
} }
@ -297,8 +284,7 @@ void Nes_Dmc::recalc_irq()
if ( irq_enabled && length_counter ) if ( irq_enabled && length_counter )
irq = apu->last_dmc_time + delay + irq = apu->last_dmc_time + delay +
((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1;
if ( irq != next_irq ) if ( irq != next_irq ) {
{
next_irq = irq; next_irq = irq;
apu->irq_changed(); apu->irq_changed();
} }
@ -346,27 +332,18 @@ inline void Nes_Dmc::reload_sample()
length_counter = regs [3] * 0x10 + 1; length_counter = regs [3] * 0x10 + 1;
} }
static int const dmc_table [128] = static byte const dac_table [128] =
{ {
0, 24, 48, 71, 94, 118, 141, 163, 186, 209, 231, 253, 275, 297, 319, 340, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14,
361, 383, 404, 425, 445, 466, 486, 507, 527, 547, 567, 587, 606, 626, 645, 664, 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27,
683, 702, 721, 740, 758, 777, 795, 813, 832, 850, 867, 885, 903, 920, 938, 955, 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38,
972, 989,1006,1023,1040,1056,1073,1089,1105,1122,1138,1154,1170,1185,1201,1217, 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49,
1232,1248,1263,1278,1293,1308,1323,1338,1353,1368,1382,1397,1411,1425,1440,1454, 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59,
1468,1482,1496,1510,1523,1537,1551,1564,1578,1591,1604,1618,1631,1644,1657,1670, 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67,
1683,1695,1708,1721,1733,1746,1758,1771,1783,1795,1807,1819,1831,1843,1855,1867, 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75,
1879,1890,1902,1914,1925,1937,1948,1959,1971,1982,1993,2004,2015,2026,2037,2048, 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83,
}; };
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 ) void Nes_Dmc::write_register( int addr, int data )
{ {
if ( addr == 0 ) if ( addr == 0 )
@ -378,7 +355,14 @@ void Nes_Dmc::write_register( int addr, int data )
} }
else if ( addr == 1 ) else if ( addr == 1 )
{ {
int old_dac = dac;
dac = data & 0x7F; dac = data & 0x7F;
// adjust last_amp so that "pop" amplitude will be properly non-linear
// with respect to change in dac
int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]);
if ( !nonlinear )
last_amp = faked_nonlinear;
} }
} }
@ -393,18 +377,16 @@ void Nes_Dmc::fill_buffer()
{ {
if ( !buf_full && length_counter ) if ( !buf_full && length_counter )
{ {
require( apu->dmc_reader.f ); // dmc_reader must be set require( prg_reader ); // prg_reader must be set
buf = apu->dmc_reader.f( apu->dmc_reader.data, 0x8000u + address ); buf = prg_reader( prg_reader_data, 0x8000u + address );
address = (address + 1) & 0x7FFF; address = (address + 1) & 0x7FFF;
buf_full = true; buf_full = true;
if ( --length_counter == 0 ) if ( --length_counter == 0 )
{ {
if ( regs [0] & loop_flag ) if ( regs [0] & loop_flag ) {
{
reload_sample(); reload_sample();
} }
else else {
{
apu->osc_enables &= ~0x10; apu->osc_enables &= ~0x10;
irq_flag = irq_enabled; irq_flag = irq_enabled;
next_irq = Nes_Apu::no_irq; next_irq = Nes_Apu::no_irq;
@ -416,15 +398,16 @@ void Nes_Dmc::fill_buffer()
void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
{ {
int delta = update_amp_nonlinear( dac ); int delta = update_amp( dac );
if ( !output ) if ( !output )
{ {
silence = true; silence = true;
} }
else if ( delta ) else
{ {
output->set_modified(); output->set_modified();
synth.offset( time, delta, output ); if ( delta )
synth.offset( time, delta, output );
} }
time += delay; time += delay;
@ -443,8 +426,6 @@ void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
const int period = this->period; const int period = this->period;
int bits = this->bits; int bits = this->bits;
int dac = this->dac; int dac = this->dac;
if ( output )
output->set_modified();
do do
{ {
@ -452,10 +433,9 @@ void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
{ {
int step = (bits & 1) * 4 - 2; int step = (bits & 1) * 4 - 2;
bits >>= 1; bits >>= 1;
if ( unsigned (dac + step) <= 0x7F ) if ( unsigned (dac + step) <= 0x7F ) {
{
dac += step; dac += step;
synth.offset_inline( time, update_amp_nonlinear( dac ), output ); synth.offset_inline( time, step, output );
} }
} }
@ -464,12 +444,10 @@ void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
if ( --bits_remain == 0 ) if ( --bits_remain == 0 )
{ {
bits_remain = 8; bits_remain = 8;
if ( !buf_full ) if ( !buf_full ) {
{
silence = true; silence = true;
} }
else else {
{
silence = false; silence = false;
bits = buf; bits = buf;
buf_full = false; buf_full = false;
@ -482,6 +460,7 @@ void Nes_Dmc::run( nes_time_t time, nes_time_t end_time )
while ( time < end_time ); while ( time < end_time );
this->dac = dac; this->dac = dac;
this->last_amp = dac;
this->bits = bits; this->bits = bits;
} }
this->bits_remain = bits_remain; this->bits_remain = bits_remain;
@ -508,16 +487,14 @@ void Nes_Noise::run( nes_time_t time, nes_time_t end_time )
return; return;
} }
output->set_modified();
const int volume = this->volume(); const int volume = this->volume();
int amp = (noise & 1) ? volume : 0; int amp = (noise & 1) ? volume : 0;
{ {
int delta = update_amp( amp ); int delta = update_amp( amp );
if ( delta ) if ( delta )
{
output->set_modified();
synth.offset( time, delta, output ); synth.offset( time, delta, output );
}
} }
time += delay; time += delay;
@ -532,8 +509,7 @@ void Nes_Noise::run( nes_time_t time, nes_time_t end_time )
// approximate noise cycling while muted, by shuffling up noise register // approximate noise cycling while muted, by shuffling up noise register
// to do: precise muted noise cycling? // to do: precise muted noise cycling?
if ( !(regs [2] & mode_flag) ) if ( !(regs [2] & mode_flag) ) {
{
int feedback = (noise << 13) ^ (noise << 14); int feedback = (noise << 13) ^ (noise << 14);
noise = (feedback & 0x4000) | (noise >> 1); noise = (feedback & 0x4000) | (noise >> 1);
} }
@ -549,15 +525,12 @@ void Nes_Noise::run( nes_time_t time, nes_time_t end_time )
int noise = this->noise; int noise = this->noise;
int delta = amp * 2 - volume; int delta = amp * 2 - volume;
const int tap = (regs [2] & mode_flag ? 8 : 13); const int tap = (regs [2] & mode_flag ? 8 : 13);
output->set_modified();
do do {
{
int feedback = (noise << tap) ^ (noise << 14); int feedback = (noise << tap) ^ (noise << 14);
time += period; time += period;
if ( (noise + 1) & 2 ) if ( (noise + 1) & 2 ) {
{
// bits 0 and 1 of noise differ // bits 0 and 1 of noise differ
delta = -delta; delta = -delta;
synth.offset_resampled( rtime, delta, output ); synth.offset_resampled( rtime, delta, output );

View file

@ -1,6 +1,6 @@
// Private oscillators used by Nes_Apu // Private oscillators used by Nes_Apu
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#ifndef NES_OSCS_H #ifndef NES_OSCS_H
#define NES_OSCS_H #define NES_OSCS_H
@ -11,8 +11,6 @@ class Nes_Apu;
struct Nes_Osc struct Nes_Osc
{ {
typedef int nes_time_t;
unsigned char regs [4]; unsigned char regs [4];
bool reg_written [4]; bool reg_written [4];
Blip_Buffer* output; Blip_Buffer* output;
@ -58,7 +56,7 @@ struct Nes_Square : Nes_Envelope
int phase; int phase;
int sweep_delay; int sweep_delay;
typedef Blip_Synth_Norm Synth; typedef Blip_Synth<blip_good_quality,1> Synth;
Synth const& synth; // shared between squares Synth const& synth; // shared between squares
Nes_Square( Synth const* s ) : synth( *s ) { } Nes_Square( Synth const* s ) : synth( *s ) { }
@ -79,7 +77,7 @@ struct Nes_Triangle : Nes_Osc
enum { phase_range = 16 }; enum { phase_range = 16 };
int phase; int phase;
int linear_counter; int linear_counter;
Blip_Synth_Fast synth; Blip_Synth<blip_med_quality,1> synth;
int calc_amp() const; int calc_amp() const;
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );
@ -97,7 +95,7 @@ struct Nes_Triangle : Nes_Osc
struct Nes_Noise : Nes_Envelope struct Nes_Noise : Nes_Envelope
{ {
int noise; int noise;
Blip_Synth_Fast synth; Blip_Synth<blip_med_quality,1> synth;
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );
void reset() { void reset() {
@ -128,11 +126,13 @@ struct Nes_Dmc : Nes_Osc
bool pal_mode; bool pal_mode;
bool nonlinear; bool nonlinear;
int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function
void* prg_reader_data;
Nes_Apu* apu; Nes_Apu* apu;
Blip_Synth_Fast synth; Blip_Synth<blip_med_quality,1> synth;
int update_amp_nonlinear( int dac_in );
void start(); void start();
void write_register( int, int ); void write_register( int, int );
void run( nes_time_t, nes_time_t ); void run( nes_time_t, nes_time_t );

View file

@ -1,4 +1,4 @@
// Nes_Snd_Emu $vers. http://www.slack.net/~ant/ // Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
#include "Nes_Vrc6_Apu.h" #include "Nes_Vrc6_Apu.h"
@ -15,10 +15,11 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
void Nes_Vrc6_Apu::set_output( Blip_Buffer* buf ) Nes_Vrc6_Apu::Nes_Vrc6_Apu()
{ {
for ( int i = 0; i < osc_count; ++i ) output( NULL );
set_output( i, buf ); volume( 1.0 );
reset();
} }
void Nes_Vrc6_Apu::reset() void Nes_Vrc6_Apu::reset()
@ -36,11 +37,10 @@ void Nes_Vrc6_Apu::reset()
} }
} }
Nes_Vrc6_Apu::Nes_Vrc6_Apu() void Nes_Vrc6_Apu::output( Blip_Buffer* buf )
{ {
set_output( NULL ); for ( int i = 0; i < osc_count; i++ )
volume( 1.0 ); osc_output( i, buf );
reset();
} }
void Nes_Vrc6_Apu::run_until( blip_time_t time ) void Nes_Vrc6_Apu::run_until( blip_time_t time )
@ -107,6 +107,7 @@ void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
Blip_Buffer* output = osc.output; Blip_Buffer* output = osc.output;
if ( !output ) if ( !output )
return; return;
output->set_modified();
int volume = osc.regs [0] & 15; int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) ) if ( !(osc.regs [2] & 0x80) )
@ -119,7 +120,6 @@ void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
if ( delta ) if ( delta )
{ {
osc.last_amp += delta; osc.last_amp += delta;
output->set_modified();
square_synth.offset( time, delta, output ); square_synth.offset( time, delta, output );
} }
@ -131,7 +131,6 @@ void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, blip_time_t end_time )
if ( time < end_time ) if ( time < end_time )
{ {
int phase = osc.phase; int phase = osc.phase;
output->set_modified();
do do
{ {

View file

@ -1,6 +1,6 @@
// Konami VRC6 sound chip emulator // Konami VRC6 sound chip emulator
// Nes_Snd_Emu $vers // Nes_Snd_Emu 0.1.8
#ifndef NES_VRC6_APU_H #ifndef NES_VRC6_APU_H
#define NES_VRC6_APU_H #define NES_VRC6_APU_H
@ -15,9 +15,9 @@ public:
void reset(); void reset();
void volume( double ); void volume( double );
void treble_eq( blip_eq_t const& ); void treble_eq( blip_eq_t const& );
void set_output( Blip_Buffer* ); void output( Blip_Buffer* );
enum { osc_count = 3 }; enum { osc_count = 3 };
void set_output( int index, Blip_Buffer* ); void osc_output( int index, Blip_Buffer* );
void end_frame( blip_time_t ); void end_frame( blip_time_t );
void save_state( vrc6_apu_state_t* ) const; void save_state( vrc6_apu_state_t* ) const;
void load_state( vrc6_apu_state_t const& ); void load_state( vrc6_apu_state_t const& );
@ -40,7 +40,7 @@ private:
struct Vrc6_Osc struct Vrc6_Osc
{ {
BOOST::uint8_t regs [3]; uint8_t regs [3];
Blip_Buffer* output; Blip_Buffer* output;
int delay; int delay;
int last_amp; int last_amp;
@ -49,15 +49,15 @@ private:
int period() const int period() const
{ {
return (regs [2] & 0x0F) * 0x100 + regs [1] + 1; return (regs [2] & 0x0F) * 0x100L + regs [1] + 1;
} }
}; };
Vrc6_Osc oscs [osc_count]; Vrc6_Osc oscs [osc_count];
blip_time_t last_time; blip_time_t last_time;
Blip_Synth_Fast saw_synth; Blip_Synth<blip_med_quality,1> saw_synth;
Blip_Synth_Norm square_synth; Blip_Synth<blip_good_quality,1> square_synth;
void run_until( blip_time_t ); void run_until( blip_time_t );
void run_square( Vrc6_Osc& osc, blip_time_t ); void run_square( Vrc6_Osc& osc, blip_time_t );
@ -66,14 +66,14 @@ private:
struct vrc6_apu_state_t struct vrc6_apu_state_t
{ {
BOOST::uint8_t regs [3] [3]; uint8_t regs [3] [3];
BOOST::uint8_t saw_amp; uint8_t saw_amp;
BOOST::uint16_t delays [3]; uint16_t delays [3];
BOOST::uint8_t phases [3]; uint8_t phases [3];
BOOST::uint8_t unused; uint8_t unused;
}; };
inline void Nes_Vrc6_Apu::set_output( int i, Blip_Buffer* buf ) inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf )
{ {
assert( (unsigned) i < osc_count ); assert( (unsigned) i < osc_count );
oscs [i].output = buf; oscs [i].output = buf;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,22 @@
// Game_Music_Emu $vers. http://www.slack.net/~ant/ // Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
#include "Nsf_Emu.h" #include "Nsf_Emu.h"
#include "blargg_endian.h"
#include <string.h>
#include <stdio.h>
#include <algorithm>
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
#include "Nes_Namco_Apu.h" #include "Nes_Namco_Apu.h"
#include "Nes_Vrc6_Apu.h" #include "Nes_Vrc6_Apu.h"
#include "Nes_Fme7_Apu.h" #include "Nes_Fme7_Apu.h"
#include "Nes_Fds_Apu.h" #include "Nes_Fds_Apu.h"
#include "Nes_Mmc5_Apu.h" #include "Nes_Mmc5_Apu.h"
#include "Nes_Vrc7_Apu.h" #include "Nes_Vrc7_Apu.h"
#endif #endif
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you /* Copyright (C) 2003-2006 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This version 2.1 of the License, or (at your option) any later version. This
@ -24,25 +29,79 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "blargg_source.h" #include "blargg_source.h"
Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq = { -1.0, 80, 0,0,0,0,0,0,0,0 }; int const vrc6_flag = 0x01;
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq = { -15.0, 80, 0,0,0,0,0,0,0,0 }; int const vrc7_flag = 0x02;
int const fds_flag = 0x04;
int const mmc5_flag = 0x08;
int const namco_flag = 0x10;
int const fme7_flag = 0x20;
long const clock_divisor = 12;
using std::min;
using std::max;
Nsf_Emu::equalizer_t const Nsf_Emu::nes_eq =
Music_Emu::make_equalizer( -1.0, 80 );
Nsf_Emu::equalizer_t const Nsf_Emu::famicom_eq =
Music_Emu::make_equalizer( -15.0, 80 );
int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr )
{
return *((Nsf_Emu*) emu)->cpu::get_code( addr );
}
Nsf_Emu::Nsf_Emu() Nsf_Emu::Nsf_Emu()
{ {
vrc6 = 0;
namco = 0;
fme7 = 0;
fds = 0;
mmc5 = 0;
vrc7 = 0;
apu_names = 0;
set_type( gme_nsf_type ); set_type( gme_nsf_type );
set_silence_lookahead( 6 ); set_silence_lookahead( 6 );
apu.dmc_reader( pcm_read, this );
Music_Emu::set_equalizer( nes_eq );
set_gain( 1.4 ); set_gain( 1.4 );
set_equalizer( nes_eq ); memset( unmapped_code, Nes_Cpu::bad_opcode, sizeof unmapped_code );
} }
Nsf_Emu::~Nsf_Emu() Nsf_Emu::~Nsf_Emu() { unload(); }
{
unload();
}
void Nsf_Emu::unload() void Nsf_Emu::unload()
{ {
core_.unload(); #if !NSF_EMU_APU_ONLY
{
delete vrc6;
vrc6 = 0;
delete namco;
namco = 0;
delete fme7;
fme7 = 0;
delete fds;
fds = 0;
delete mmc5;
mmc5 = 0;
delete vrc7;
vrc7 = 0;
}
#endif
{
delete [] apu_names;
apu_names = 0;
}
rom.clear();
Music_Emu::unload(); Music_Emu::unload();
} }
@ -54,268 +113,498 @@ static void copy_nsf_fields( Nsf_Emu::header_t const& h, track_info_t* out )
GME_COPY_FIELD( h, out, author ); GME_COPY_FIELD( h, out, author );
GME_COPY_FIELD( h, out, copyright ); GME_COPY_FIELD( h, out, copyright );
if ( h.chip_flags ) if ( h.chip_flags )
Music_Emu::copy_field_( out->system, "Famicom" ); Gme_File::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 blargg_err_t Nsf_Emu::track_info_( track_info_t* out, int ) const
{ {
copy_nsf_fields( header(), out ); copy_nsf_fields( header_, out );
return blargg_ok; return 0;
} }
static blargg_err_t check_nsf_header( Nsf_Emu::header_t const& h ) static blargg_err_t check_nsf_header( void const* header )
{ {
if ( !h.valid_tag() ) if ( memcmp( header, "NESM\x1A", 5 ) )
return blargg_err_file_type; return gme_wrong_file_type;
return blargg_ok; return 0;
} }
struct Nsf_File : Gme_Info_ struct Nsf_File : Gme_Info_
{ {
Nsf_Emu::header_t const* h; Nsf_Emu::header_t h;
Nsf_File() { set_type( gme_nsf_type ); } Nsf_File() { set_type( gme_nsf_type ); }
blargg_err_t load_mem_( byte const begin [], int size ) blargg_err_t load_( Data_Reader& in )
{ {
h = ( Nsf_Emu::header_t const* ) begin; blargg_err_t err = in.read( &h, Nsf_Emu::header_size );
if ( err )
return (err == in.eof_error ? gme_wrong_file_type : err);
if ( h->vers != 1 ) if ( h.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag | fds_flag | mmc5_flag | vrc7_flag) )
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_warning( "Uses unsupported audio expansion hardware" );
set_track_count( h->track_count ); set_track_count( h.track_count );
return check_nsf_header( *h ); return check_nsf_header( &h );
} }
blargg_err_t track_info_( track_info_t* out, int ) const blargg_err_t track_info_( track_info_t* out, int ) const
{ {
copy_nsf_fields( *h, out ); copy_nsf_fields( h, out );
return blargg_ok; return 0;
}
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_emu () { return BLARGG_NEW Nsf_Emu ; }
static Music_Emu* new_nsf_file() { return BLARGG_NEW Nsf_File; } 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 }}; static gme_type_t_ const gme_nsf_type_ = { "Nintendo NES", 0, &new_nsf_emu, &new_nsf_file, "NSF", 1 };
extern gme_type_t const gme_nsf_type = &gme_nsf_type_;
// Setup // Setup
void Nsf_Emu::set_tempo_( double t ) void Nsf_Emu::set_tempo_( double t )
{ {
core_.set_tempo( t ); unsigned playback_rate = get_le16( header_.ntsc_speed );
} unsigned standard_rate = 0x411A;
clock_rate_ = 1789772.72727;
play_period = 262 * 341L * 4 - 2; // two fewer PPU clocks every four frames
void Nsf_Emu::append_voices( const char* const names [], int const types [], int count ) if ( pal_only )
{
assert( voice_count_ + count < max_voices );
for ( int i = 0; i < count; i++ )
{ {
voice_names_ [voice_count_ + i] = names [i]; play_period = 33247 * clock_divisor;
voice_types_ [voice_count_ + i] = types [i]; clock_rate_ = 1662607.125;
standard_rate = 0x4E20;
playback_rate = get_le16( header_.pal_speed );
} }
voice_count_ += count;
set_voice_count( voice_count_ ); if ( !playback_rate )
set_voice_types( voice_types_ ); playback_rate = standard_rate;
if ( playback_rate != standard_rate || t != 1.0 )
play_period = long (playback_rate * clock_rate_ / (1000000.0 / clock_divisor * t));
apu.set_tempo( t );
} }
blargg_err_t Nsf_Emu::init_sound() blargg_err_t Nsf_Emu::init_sound()
{ {
voice_count_ = 0; if ( header_.chip_flags & ~(namco_flag | vrc6_flag | fme7_flag | fds_flag | mmc5_flag | vrc7_flag) )
set_voice_names( voice_names_ ); set_warning( "Uses unsupported audio expansion hardware" );
{ #ifdef NSF_EMU_APU_ONLY
int const count = Nes_Apu::osc_count; int const count_total = Nes_Apu::osc_count;
static const char* const names [Nes_Apu::osc_count] = { #else
"Square 1", "Square 2", "Triangle", "Noise", "DMC" int const count_total = Nes_Apu::osc_count + Nes_Namco_Apu::osc_count +
}; Nes_Vrc6_Apu::osc_count + Nes_Fme7_Apu::osc_count +
static int const types [count] = { Nes_Fds_Apu::osc_count + Nes_Mmc5_Apu::osc_count +
wave_type+1, wave_type+2, mixed_type+1, noise_type+0, mixed_type+1 Nes_Vrc7_Apu::osc_count;
};
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 #endif
if ( adjusted_gain > gain() ) if ( apu_names )
adjusted_gain = gain(); // only occurs if no other sound chips {
delete [] apu_names;
}
core_.nes_apu()->volume( adjusted_gain ); apu_names = new char* [count_total];
return blargg_ok; int count = 0;
{
apu_names[count + 0] = "Square 1";
apu_names[count + 1] = "Square 2";
apu_names[count + 2] = "Triangle";
apu_names[count + 3] = "Noise";
apu_names[count + 4] = "DMC";
count += Nes_Apu::osc_count;
}
static int const types [] = {
wave_type | 1, wave_type | 2, wave_type | 0,
noise_type | 0, mixed_type | 1,
wave_type | 3, wave_type | 4, wave_type | 5,
wave_type | 6, wave_type | 7, wave_type | 8, wave_type | 9,
wave_type |10, wave_type |11, wave_type |12, wave_type |13
};
set_voice_types( types ); // common to all sound chip configurations
double adjusted_gain = gain();
#if NSF_EMU_APU_ONLY
{
if ( header_.chip_flags )
set_warning( "Uses unsupported audio expansion hardware" );
}
#else
{
if ( header_.chip_flags & vrc6_flag )
{
vrc6 = BLARGG_NEW Nes_Vrc6_Apu;
CHECK_ALLOC( vrc6 );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Saw Wave";
apu_names[count + 1] = "Square 3";
apu_names[count + 2] = "Square 4";
count += Nes_Vrc6_Apu::osc_count;
}
if ( header_.chip_flags & namco_flag )
{
namco = BLARGG_NEW Nes_Namco_Apu;
CHECK_ALLOC( namco );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Wave 1";
apu_names[count + 1] = "Wave 2";
apu_names[count + 2] = "Wave 3";
apu_names[count + 3] = "Wave 4";
apu_names[count + 4] = "Wave 5";
apu_names[count + 5] = "Wave 6";
apu_names[count + 6] = "Wave 7";
apu_names[count + 7] = "Wave 8";
count += Nes_Namco_Apu::osc_count;
}
if ( header_.chip_flags & fme7_flag )
{
fme7 = BLARGG_NEW Nes_Fme7_Apu;
CHECK_ALLOC( fme7 );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "Square 5";
count += Nes_Fme7_Apu::osc_count;
}
if ( header_.chip_flags & fds_flag )
{
fds = BLARGG_NEW Nes_Fds_Apu;
CHECK_ALLOC( fds );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Wave";
count += Nes_Fds_Apu::osc_count;
}
if ( header_.chip_flags & mmc5_flag )
{
mmc5 = BLARGG_NEW Nes_Mmc5_Apu;
CHECK_ALLOC( mmc5 );
adjusted_gain *= 0.75;
apu_names[count + 0] = "Square 3";
apu_names[count + 1] = "Square 4";
apu_names[count + 2] = "PCM";
count += Nes_Mmc5_Apu::osc_count;
}
if ( header_.chip_flags & vrc7_flag )
{
vrc7 = BLARGG_NEW Nes_Vrc7_Apu;
CHECK_ALLOC( vrc7 );
RETURN_ERR( vrc7->init() );
adjusted_gain *= 0.75;
apu_names[count + 0] = "FM 1";
apu_names[count + 1] = "FM 2";
apu_names[count + 2] = "FM 3";
apu_names[count + 3] = "FM 4";
apu_names[count + 4] = "FM 5";
apu_names[count + 5] = "FM 6";
count += Nes_Vrc7_Apu::osc_count;
}
set_voice_count( count );
set_voice_names( apu_names );
if ( namco ) namco->volume( adjusted_gain );
if ( vrc6 ) vrc6 ->volume( adjusted_gain );
if ( fme7 ) fme7 ->volume( adjusted_gain );
if ( fds ) fds ->volume( adjusted_gain );
if ( mmc5 ) mmc5 ->volume( adjusted_gain );
if ( vrc7 ) vrc7 ->volume( adjusted_gain );
}
#endif
apu.volume( adjusted_gain );
return 0;
} }
blargg_err_t Nsf_Emu::load_( Data_Reader& in ) blargg_err_t Nsf_Emu::load_( Data_Reader& in )
{ {
RETURN_ERR( core_.load( in ) ); assert( offsetof (header_t,unused [4]) == header_size );
set_track_count( header().track_count ); RETURN_ERR( rom.load( in, header_size, &header_, 0 ) );
RETURN_ERR( check_nsf_header( header() ) );
set_warning( core_.warning() ); set_track_count( header_.track_count );
RETURN_ERR( init_sound() ); RETURN_ERR( check_nsf_header( &header_ ) );
if ( header_.vers != 1 )
set_warning( "Unknown file version" );
// sound and memory
blargg_err_t err = init_sound();
if ( err )
return err;
// set up data
nes_addr_t load_addr = get_le16( header_.load_addr );
init_addr = get_le16( header_.init_addr );
play_addr = get_le16( header_.play_addr );
if ( !load_addr ) load_addr = rom_begin;
if ( !init_addr ) init_addr = rom_begin;
if ( !play_addr ) play_addr = rom_begin;
if ( load_addr < rom_begin || init_addr < rom_begin )
{
const char* w = warning();
if ( !w )
w = "Corrupt file (invalid load/init/play address)";
return w;
}
rom.set_addr( load_addr % bank_size );
int total_banks = rom.size() / bank_size;
// bank switching
int first_bank = (load_addr - rom_begin) / bank_size;
for ( int i = 0; i < bank_count; i++ )
{
unsigned bank = i - first_bank;
if ( bank >= (unsigned) total_banks )
bank = 0;
initial_banks [i] = bank;
if ( header_.banks [i] )
{
// bank-switched
memcpy( initial_banks, header_.banks, sizeof initial_banks );
break;
}
}
pal_only = (header_.speed_flags & 3) == 1;
#if !NSF_EMU_EXTRA_FLAGS
header_.speed_flags = 0;
#endif
set_tempo( tempo() ); set_tempo( tempo() );
return setup_buffer( (int) (header().clock_rate() + 0.5) );
return setup_buffer( (long) (clock_rate_ + 0.5) );
} }
void Nsf_Emu::update_eq( blip_eq_t const& eq ) void Nsf_Emu::update_eq( blip_eq_t const& eq )
{ {
core_.nes_apu()->treble_eq( eq ); apu.treble_eq( eq );
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
{ {
if ( core_.namco_apu() ) core_.namco_apu()->treble_eq( eq ); if ( namco ) namco->treble_eq( eq );
if ( core_.vrc6_apu() ) core_.vrc6_apu() ->treble_eq( eq ); if ( vrc6 ) vrc6 ->treble_eq( eq );
if ( core_.fme7_apu() ) core_.fme7_apu() ->treble_eq( eq ); if ( fme7 ) fme7 ->treble_eq( eq );
if ( core_.mmc5_apu() ) core_.mmc5_apu() ->treble_eq( eq ); if ( fds ) fds ->treble_eq( eq );
if ( core_.fds_apu() ) core_.fds_apu() ->treble_eq( eq ); if ( mmc5 ) mmc5 ->treble_eq( eq );
if ( core_.vrc7_apu() ) core_.vrc7_apu() ->treble_eq( eq ); if ( vrc7 ) vrc7 ->treble_eq( eq );
} }
#endif #endif
} }
void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
{ {
#define HANDLE_CHIP( chip ) \ if ( i < Nes_Apu::osc_count )
if ( chip && (i -= chip->osc_count) < 0 )\ {
{\ apu.osc_output( i, buf );
chip->set_output( i + chip->osc_count, buf );\ return;
return;\ }
}\ i -= Nes_Apu::osc_count;
HANDLE_CHIP( core_.nes_apu() );
#if !NSF_EMU_APU_ONLY #if !NSF_EMU_APU_ONLY
{ {
// TODO: order of chips here must match that in init_sound() if ( vrc6 )
HANDLE_CHIP( core_.vrc6_apu() ); {
HANDLE_CHIP( core_.fme7_apu() ); if ( i < Nes_Vrc6_Apu::osc_count )
HANDLE_CHIP( core_.mmc5_apu() ); {
HANDLE_CHIP( core_.fds_apu() ); // put saw first
HANDLE_CHIP( core_.namco_apu() ); if ( --i < 0 )
HANDLE_CHIP( core_.vrc7_apu() ); i = 2;
vrc6->osc_output( i, buf );
return;
}
i -= Nes_Vrc6_Apu::osc_count;
}
if ( namco )
{
if ( i < Nes_Namco_Apu::osc_count )
{
namco->osc_output( i, buf );
return;
}
i -= Nes_Namco_Apu::osc_count;
}
if ( fme7 )
{
if ( i < Nes_Fme7_Apu::osc_count )
{
fme7->osc_output( i, buf );
return;
}
i -= Nes_Fme7_Apu::osc_count;
}
if ( fds )
{
if ( i < Nes_Fds_Apu::osc_count )
{
fds->osc_output( i, buf );
return;
}
i -= Nes_Fds_Apu::osc_count;
}
if ( mmc5 )
{
if ( i < Nes_Mmc5_Apu::osc_count )
{
mmc5->osc_output( i, buf );
return;
}
i -= Nes_Mmc5_Apu::osc_count;
}
if ( vrc7 )
{
if ( i < Nes_Vrc7_Apu::osc_count )
{
vrc7->osc_output( i, buf );
return;
}
i -= Nes_Vrc7_Apu::osc_count;
}
}
#endif
}
// Emulation
// see nes_cpu_io.h for read/write functions
void Nsf_Emu::cpu_write_misc( nes_addr_t addr, int data )
{
#if !NSF_EMU_APU_ONLY
{
if ( fds )
{
if ( (unsigned) (addr - fds->io_addr) < fds->io_size )
{
fds->write( time(), addr, data);
return;
}
}
if ( namco )
{
switch ( addr )
{
case Nes_Namco_Apu::data_reg_addr:
namco->write_data( time(), data );
return;
case Nes_Namco_Apu::addr_reg_addr:
namco->write_addr( data );
return;
}
}
if ( addr >= Nes_Fme7_Apu::latch_addr && fme7 )
{
switch ( addr & Nes_Fme7_Apu::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 ( vrc6 )
{
unsigned reg = addr & (Nes_Vrc6_Apu::addr_step - 1);
unsigned osc = unsigned (addr - Nes_Vrc6_Apu::base_addr) / Nes_Vrc6_Apu::addr_step;
if ( osc < Nes_Vrc6_Apu::osc_count && reg < Nes_Vrc6_Apu::reg_count )
{
vrc6->write_osc( time(), osc, reg, 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
// unmapped write
#ifndef NDEBUG
{
// some games write to $8000 and $8001 repeatedly
if ( addr == 0x8000 || addr == 0x8001 ) return;
// probably namco sound mistakenly turned on in mck
if ( addr == 0x4800 || addr == 0xF800 ) return;
// memory mapper?
if ( addr == 0xFFF8 ) return;
debug_printf( "write_unmapped( 0x%04X, 0x%02X )\n", (unsigned) addr, (unsigned) data );
} }
#endif #endif
} }
@ -323,20 +612,123 @@ void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
blargg_err_t Nsf_Emu::start_track_( int track ) blargg_err_t Nsf_Emu::start_track_( int track )
{ {
RETURN_ERR( Classic_Emu::start_track_( track ) ); RETURN_ERR( Classic_Emu::start_track_( track ) );
return core_.start_track( track );
memset( low_mem, 0, sizeof low_mem );
memset( sram, 0, sizeof sram );
cpu::reset( unmapped_code ); // also maps low_mem
cpu::map_code( sram_addr, sizeof sram, sram );
for ( int i = 0; i < bank_count; ++i )
cpu_write( bank_select_addr + i, initial_banks [i] );
apu.reset( pal_only, (header_.speed_flags & 0x20) ? 0x3F : 0 );
apu.write_register( 0, 0x4015, 0x0F );
apu.write_register( 0, 0x4017, (header_.speed_flags & 0x10) ? 0x80 : 0 );
#if !NSF_EMU_APU_ONLY
if ( mmc5 )
{
mmc5_mul [0] = 0;
mmc5_mul [1] = 0;
memset( mmc5->exram, 0, mmc5->exram_size );
}
{
if ( namco ) namco->reset();
if ( vrc6 ) vrc6 ->reset();
if ( fme7 ) fme7 ->reset();
if ( fds ) fds ->reset();
if ( mmc5 ) mmc5 ->reset();
if ( vrc7 ) vrc7 ->reset();
}
#endif
play_ready = 4;
play_extra = 0;
next_play = play_period / clock_divisor;
saved_state.pc = badop_addr;
low_mem [0x1FF] = (badop_addr - 1) >> 8;
low_mem [0x1FE] = (badop_addr - 1) & 0xFF;
r.sp = 0xFD;
r.pc = init_addr;
r.a = track;
r.x = pal_only;
return 0;
} }
blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int ) blargg_err_t Nsf_Emu::run_clocks( blip_time_t& duration, int )
{ {
core_.end_frame( duration ); set_time( 0 );
const char* w = core_.warning(); while ( time() < duration )
if ( w ) {
set_warning( w ); nes_time_t end = min( (blip_time_t) next_play, duration );
return blargg_ok; end = min( end, time() + 32767 ); // allows CPU to use 16-bit time delta
} if ( cpu::run( end ) )
{
if ( r.pc != badop_addr )
{
set_warning( "Emulation error (illegal instruction)" );
r.pc++;
}
else
{
play_ready = 1;
if ( saved_state.pc != badop_addr )
{
cpu::r = saved_state;
saved_state.pc = badop_addr;
}
else
{
set_time( end );
}
}
}
blargg_err_t Nsf_Emu::hash_( Hash_Function& out ) const if ( time() >= next_play )
{ {
hash_nsf_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out ); nes_time_t period = (play_period + play_extra) / clock_divisor;
return blargg_ok; play_extra = play_period - period * clock_divisor;
next_play += period;
if ( play_ready && !--play_ready )
{
check( saved_state.pc == badop_addr );
if ( r.pc != badop_addr )
saved_state = cpu::r;
r.pc = play_addr;
low_mem [0x100 + r.sp--] = (badop_addr - 1) >> 8;
low_mem [0x100 + r.sp--] = (badop_addr - 1) & 0xFF;
GME_FRAME_HOOK( this );
}
}
}
if ( cpu::error_count() )
{
cpu::clear_error_count();
set_warning( "Emulation error (illegal instruction)" );
}
duration = time();
next_play -= duration;
check( next_play >= 0 );
if ( next_play < 0 )
next_play = 0;
apu.end_frame( duration );
#if !NSF_EMU_APU_ONLY
{
if ( namco ) namco->end_frame( duration );
if ( vrc6 ) vrc6 ->end_frame( duration );
if ( fme7 ) fme7 ->end_frame( duration );
if ( fds ) fds ->end_frame( duration );
if ( mmc5 ) mmc5 ->end_frame( duration );
if ( vrc7 ) vrc7 ->end_frame( duration );
}
#endif
return 0;
} }

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