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:
parent
888ee2fb38
commit
fc38295d02
350 changed files with 29995 additions and 242970 deletions
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 );
|
|
||||||
}
|
|
|
@ -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
89
Frameworks/GME/gme/Ay_Cpu.h
Normal file
89
Frameworks/GME/gme/Ay_Cpu.h
Normal 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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
227
Frameworks/GME/gme/CMakeLists.txt
Normal file
227
Frameworks/GME/gme/CMakeLists.txt
Normal 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)
|
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
342
Frameworks/GME/gme/Data_Reader.cpp
Executable file → Normal 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
48
Frameworks/GME/gme/Data_Reader.h
Executable file → Normal 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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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() );
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = ®s [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 = ®s [i * 5];
|
osc.regs = ®s [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;
|
||||||
|
|
|
@ -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
|
@ -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
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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"
|
|
||||||
}
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ) );
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
|
@ -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
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
120
Frameworks/GME/gme/Kss_Cpu.h
Normal file
120
Frameworks/GME/gme/Kss_Cpu.h
Normal 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
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ) );
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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] );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"; }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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
|
@ -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()
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
|
@ -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 = ® [i * 8 + 0x40];
|
const uint8_t* osc_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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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
Loading…
Reference in a new issue