Updated Game_Music_Emu
parent
17682d4397
commit
5c0cf35a1a
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0460"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8DC2EF4F0486A6940098B216"
|
||||
BuildableName = "GME.framework"
|
||||
BlueprintName = "GME Framework"
|
||||
ReferencedContainer = "container:GME.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>GME Framework.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>12</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>8DC2EF4F0486A6940098B216</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,190 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ay_Core.h"
|
||||
|
||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
inline void Ay_Core::disable_beeper()
|
||||
{
|
||||
beeper_mask = 0;
|
||||
last_beeper = 0;
|
||||
}
|
||||
|
||||
Ay_Core::Ay_Core()
|
||||
{
|
||||
beeper_output = NULL;
|
||||
disable_beeper();
|
||||
}
|
||||
|
||||
Ay_Core::~Ay_Core() { }
|
||||
|
||||
void Ay_Core::set_beeper_output( Blip_Buffer* b )
|
||||
{
|
||||
beeper_output = b;
|
||||
if ( b && !cpc_mode )
|
||||
beeper_mask = 0x10;
|
||||
else
|
||||
disable_beeper();
|
||||
}
|
||||
|
||||
void Ay_Core::start_track( registers_t const& r, addr_t play )
|
||||
{
|
||||
play_addr = play;
|
||||
|
||||
memset( mem_.padding1, 0xFF, sizeof mem_.padding1 );
|
||||
|
||||
int const mirrored = 0x80; // this much is mirrored after end of memory
|
||||
memset( mem_.ram + mem_size + mirrored, 0xFF, sizeof mem_.ram - mem_size - mirrored );
|
||||
memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh)
|
||||
|
||||
cpu.reset( mem_.padding1, mem_.padding1 );
|
||||
cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram );
|
||||
cpu.r = r;
|
||||
|
||||
beeper_delta = (int) (apu_.amp_range * 0.8);
|
||||
last_beeper = 0;
|
||||
next_play = play_period;
|
||||
spectrum_mode = false;
|
||||
cpc_mode = false;
|
||||
cpc_latch = 0;
|
||||
set_beeper_output( beeper_output );
|
||||
apu_.reset();
|
||||
|
||||
// a few tunes rely on channels having tone enabled at the beginning
|
||||
apu_.write_addr( 7 );
|
||||
apu_.write_data( 0, 0x38 );
|
||||
|
||||
}
|
||||
|
||||
// Emulation
|
||||
|
||||
void Ay_Core::cpu_out_( time_t time, addr_t addr, int data )
|
||||
{
|
||||
// Spectrum
|
||||
if ( !cpc_mode )
|
||||
{
|
||||
switch ( addr & 0xFEFF )
|
||||
{
|
||||
case 0xFEFD:
|
||||
spectrum_mode = true;
|
||||
apu_.write_addr( data );
|
||||
return;
|
||||
|
||||
case 0xBEFD:
|
||||
spectrum_mode = true;
|
||||
apu_.write_data( time, data );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// CPC
|
||||
if ( !spectrum_mode )
|
||||
{
|
||||
switch ( addr >> 8 )
|
||||
{
|
||||
case 0xF6:
|
||||
switch ( data & 0xC0 )
|
||||
{
|
||||
case 0xC0:
|
||||
apu_.write_addr( cpc_latch );
|
||||
goto enable_cpc;
|
||||
|
||||
case 0x80:
|
||||
apu_.write_data( time, cpc_latch );
|
||||
goto enable_cpc;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xF4:
|
||||
cpc_latch = data;
|
||||
goto enable_cpc;
|
||||
}
|
||||
}
|
||||
|
||||
dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
|
||||
return;
|
||||
|
||||
enable_cpc:
|
||||
if ( !cpc_mode )
|
||||
{
|
||||
cpc_mode = true;
|
||||
disable_beeper();
|
||||
set_cpc_callback.f( set_cpc_callback.data );
|
||||
}
|
||||
}
|
||||
|
||||
int Ay_Core::cpu_in( addr_t addr )
|
||||
{
|
||||
// keyboard read and other things
|
||||
if ( (addr & 0xFF) == 0xFE )
|
||||
return 0xFF; // other values break some beeper tunes
|
||||
|
||||
dprintf( "Unmapped IN : $%04X\n", addr );
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void Ay_Core::end_frame( time_t* end )
|
||||
{
|
||||
cpu.set_time( 0 );
|
||||
|
||||
// Since detection of CPC mode will halve clock rate during the frame
|
||||
// and thus generate up to twice as much sound, we must generate half
|
||||
// as much until mode is known.
|
||||
if ( !(spectrum_mode | cpc_mode) )
|
||||
*end /= 2;
|
||||
|
||||
while ( cpu.time() < *end )
|
||||
{
|
||||
run_cpu( min( *end, next_play ) );
|
||||
|
||||
if ( cpu.time() >= next_play )
|
||||
{
|
||||
// next frame
|
||||
next_play += play_period;
|
||||
|
||||
if ( cpu.r.iff1 )
|
||||
{
|
||||
// interrupt enabled
|
||||
|
||||
if ( mem_.ram [cpu.r.pc] == 0x76 )
|
||||
cpu.r.pc++; // advance past HALT instruction
|
||||
|
||||
cpu.r.iff1 = 0;
|
||||
cpu.r.iff2 = 0;
|
||||
|
||||
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8);
|
||||
mem_.ram [--cpu.r.sp] = byte (cpu.r.pc);
|
||||
|
||||
// fixed interrupt
|
||||
cpu.r.pc = 0x38;
|
||||
cpu.adjust_time( 12 );
|
||||
|
||||
if ( cpu.r.im == 2 )
|
||||
{
|
||||
// vectored interrupt
|
||||
addr_t addr = cpu.r.i * 0x100 + 0xFF;
|
||||
cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr];
|
||||
cpu.adjust_time( 6 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End time frame
|
||||
*end = cpu.time();
|
||||
next_play -= *end;
|
||||
check( next_play >= 0 );
|
||||
cpu.adjust_time( -*end );
|
||||
apu_.end_frame( *end );
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Sinclair Spectrum AY music emulator core
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef AY_CORE_H
|
||||
#define AY_CORE_H
|
||||
|
||||
#include "Z80_Cpu.h"
|
||||
#include "Ay_Apu.h"
|
||||
|
||||
class Ay_Core {
|
||||
public:
|
||||
|
||||
// Clock count
|
||||
typedef int time_t;
|
||||
|
||||
// Sound chip access, to assign it to Blip_Buffer etc.
|
||||
Ay_Apu& apu() { return apu_; }
|
||||
|
||||
// Sets beeper sound buffer, or NULL to mute it. Volume and treble EQ of
|
||||
// beeper are set by APU.
|
||||
void set_beeper_output( Blip_Buffer* );
|
||||
|
||||
// Sets time between calls to play routine. Can be changed while playing.
|
||||
void set_play_period( time_t p ) { play_period = p; }
|
||||
|
||||
// 64K memory to load code and data into before starting track. Caller
|
||||
// must parse the AY file.
|
||||
BOOST::uint8_t* mem() { return mem_.ram; }
|
||||
enum { mem_size = 0x10000 };
|
||||
enum { ram_addr = 0x4000 }; // where official RAM starts
|
||||
|
||||
// Starts track using specified register values, and sets play routine that
|
||||
// is called periodically
|
||||
typedef Z80_Cpu::registers_t registers_t;
|
||||
typedef int addr_t;
|
||||
void start_track( registers_t const&, addr_t play );
|
||||
|
||||
// Ends time frame of at most *end clocks and sets *end to number of clocks
|
||||
// emulated. Until Spectrum/CPC mode is determined, *end is HALVED.
|
||||
void end_frame( time_t* end );
|
||||
|
||||
// Called when CPC hardware is first accessed. AY file format doesn't specify
|
||||
// which sound hardware is used, so it must be determined during playback
|
||||
// based on which sound port is first used.
|
||||
blargg_callback<void (*)( void* )> set_cpc_callback;
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Ay_Core();
|
||||
~Ay_Core();
|
||||
|
||||
private:
|
||||
Blip_Buffer* beeper_output;
|
||||
int beeper_delta;
|
||||
int last_beeper;
|
||||
int beeper_mask;
|
||||
|
||||
addr_t play_addr;
|
||||
time_t play_period;
|
||||
time_t next_play;
|
||||
|
||||
int cpc_latch;
|
||||
bool spectrum_mode;
|
||||
bool cpc_mode;
|
||||
|
||||
// large items
|
||||
Z80_Cpu cpu;
|
||||
struct {
|
||||
BOOST::uint8_t padding1 [0x100];
|
||||
BOOST::uint8_t ram [mem_size + 0x100];
|
||||
} mem_;
|
||||
Ay_Apu apu_;
|
||||
|
||||
int cpu_in( addr_t );
|
||||
void cpu_out( time_t, addr_t, int data );
|
||||
void cpu_out_( time_t, addr_t, int data );
|
||||
bool run_cpu( time_t end );
|
||||
void disable_beeper();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,135 @@
|
|||
// Internal stuff here to keep public header uncluttered
|
||||
|
||||
// Blip_Buffer $vers
|
||||
#ifndef BLIP_BUFFER_IMPL_H
|
||||
#define BLIP_BUFFER_IMPL_H
|
||||
|
||||
typedef unsigned blip_resampled_time_t;
|
||||
|
||||
#ifndef BLIP_MAX_QUALITY
|
||||
#define BLIP_MAX_QUALITY 32
|
||||
#endif
|
||||
|
||||
#ifndef BLIP_BUFFER_ACCURACY
|
||||
#define BLIP_BUFFER_ACCURACY 16
|
||||
#endif
|
||||
|
||||
#ifndef BLIP_PHASE_BITS
|
||||
#define BLIP_PHASE_BITS 6
|
||||
#endif
|
||||
|
||||
class blip_eq_t;
|
||||
class Blip_Buffer;
|
||||
|
||||
#if BLIP_BUFFER_FAST
|
||||
// linear interpolation needs 8 bits
|
||||
#undef BLIP_PHASE_BITS
|
||||
#define BLIP_PHASE_BITS 8
|
||||
|
||||
#undef BLIP_MAX_QUALITY
|
||||
#define BLIP_MAX_QUALITY 2
|
||||
#endif
|
||||
|
||||
int const blip_res = 1 << BLIP_PHASE_BITS;
|
||||
int const blip_buffer_extra_ = BLIP_MAX_QUALITY + 2;
|
||||
|
||||
class Blip_Buffer_ {
|
||||
public:
|
||||
// Writer
|
||||
|
||||
typedef int clocks_t;
|
||||
|
||||
// Properties of fixed-point sample position
|
||||
typedef unsigned fixed_t; // unsigned for more range, optimized shifts
|
||||
enum { fixed_bits = BLIP_BUFFER_ACCURACY }; // bits in fraction
|
||||
enum { fixed_unit = 1 << fixed_bits }; // 1.0 samples
|
||||
|
||||
// Converts clock count to fixed-point sample position
|
||||
fixed_t to_fixed( clocks_t t ) const { return t * factor_ + offset_; }
|
||||
|
||||
// Deltas in buffer are fixed-point with this many fraction bits.
|
||||
// Less than 16 for extra range.
|
||||
enum { delta_bits = 14 };
|
||||
|
||||
// Pointer to first committed delta sample
|
||||
typedef int delta_t;
|
||||
|
||||
// Pointer to delta corresponding to fixed-point sample position
|
||||
delta_t* delta_at( fixed_t );
|
||||
|
||||
// Reader
|
||||
|
||||
delta_t* read_pos() { return buffer_; }
|
||||
|
||||
void clear_modified() { modified_ = false; }
|
||||
int highpass_shift() const { return bass_shift_; }
|
||||
int integrator() const { return reader_accum_; }
|
||||
void set_integrator( int n ) { reader_accum_ = n; }
|
||||
|
||||
public: //friend class Tracked_Blip_Buffer; private:
|
||||
bool modified() const { return modified_; }
|
||||
void remove_silence( int count );
|
||||
|
||||
private:
|
||||
unsigned factor_;
|
||||
fixed_t offset_;
|
||||
delta_t* buffer_center_;
|
||||
int buffer_size_;
|
||||
int reader_accum_;
|
||||
int bass_shift_;
|
||||
delta_t* buffer_;
|
||||
int sample_rate_;
|
||||
int clock_rate_;
|
||||
int bass_freq_;
|
||||
int length_;
|
||||
bool modified_;
|
||||
|
||||
friend class Blip_Buffer;
|
||||
};
|
||||
|
||||
class Blip_Synth_Fast_ {
|
||||
public:
|
||||
int delta_factor;
|
||||
int last_amp;
|
||||
Blip_Buffer* buf;
|
||||
|
||||
void volume_unit( double );
|
||||
void treble_eq( blip_eq_t const& ) { }
|
||||
Blip_Synth_Fast_();
|
||||
};
|
||||
|
||||
class Blip_Synth_ {
|
||||
public:
|
||||
int delta_factor;
|
||||
int last_amp;
|
||||
Blip_Buffer* buf;
|
||||
|
||||
void volume_unit( double );
|
||||
void treble_eq( blip_eq_t const& );
|
||||
Blip_Synth_( short phases [], int width );
|
||||
private:
|
||||
double volume_unit_;
|
||||
short* const phases;
|
||||
int const width;
|
||||
int kernel_unit;
|
||||
|
||||
void adjust_impulse();
|
||||
void rescale_kernel( int shift );
|
||||
int impulses_size() const { return blip_res / 2 * width; }
|
||||
};
|
||||
|
||||
class blip_buffer_state_t
|
||||
{
|
||||
blip_resampled_time_t offset_;
|
||||
int reader_accum_;
|
||||
int buf [blip_buffer_extra_];
|
||||
friend class Blip_Buffer;
|
||||
};
|
||||
|
||||
inline Blip_Buffer_::delta_t* Blip_Buffer_::delta_at( fixed_t f )
|
||||
{
|
||||
assert( (f >> fixed_bits) < (unsigned) buffer_size_ );
|
||||
return buffer_center_ + (f >> fixed_bits);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,282 @@
|
|||
// Internal stuff here to keep public header uncluttered
|
||||
|
||||
// Blip_Buffer $vers
|
||||
#ifndef BLIP_BUFFER_IMPL2_H
|
||||
#define BLIP_BUFFER_IMPL2_H
|
||||
|
||||
//// Compatibility
|
||||
|
||||
BLARGG_DEPRECATED( int const blip_low_quality = 8; )
|
||||
BLARGG_DEPRECATED( int const blip_med_quality = 8; )
|
||||
BLARGG_DEPRECATED( int const blip_good_quality = 12; )
|
||||
BLARGG_DEPRECATED( int const blip_high_quality = 16; )
|
||||
|
||||
BLARGG_DEPRECATED( int const blip_sample_max = 32767; )
|
||||
|
||||
// Number of bits in raw sample that covers normal output range. Less than 32 bits to give
|
||||
// extra amplitude range. That is,
|
||||
// +1 << (blip_sample_bits-1) = +1.0
|
||||
// -1 << (blip_sample_bits-1) = -1.0
|
||||
int const blip_sample_bits = 30;
|
||||
|
||||
//// BLIP_READER_
|
||||
|
||||
//// Optimized reading from Blip_Buffer, for use in custom sample buffer or mixer
|
||||
|
||||
// Begins reading from buffer. Name should be unique to the current {} block.
|
||||
#define BLIP_READER_BEGIN( name, blip_buffer ) \
|
||||
const Blip_Buffer::delta_t* BLARGG_RESTRICT name##_reader_buf = (blip_buffer).read_pos();\
|
||||
int name##_reader_accum = (blip_buffer).integrator()
|
||||
|
||||
// Gets value to pass to BLIP_READER_NEXT()
|
||||
#define BLIP_READER_BASS( blip_buffer ) (blip_buffer).highpass_shift()
|
||||
|
||||
// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal
|
||||
// code at the cost of having no bass_freq() functionality
|
||||
int const blip_reader_default_bass = 9;
|
||||
|
||||
// Current sample as 16-bit signed value
|
||||
#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16))
|
||||
|
||||
// Current raw sample in full internal resolution
|
||||
#define BLIP_READER_READ_RAW( name ) (name##_reader_accum)
|
||||
|
||||
// Advances to next sample
|
||||
#define BLIP_READER_NEXT( name, bass ) \
|
||||
(void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass)))
|
||||
|
||||
// Ends reading samples from buffer. The number of samples read must now be removed
|
||||
// using Blip_Buffer::remove_samples().
|
||||
#define BLIP_READER_END( name, blip_buffer ) \
|
||||
(void) ((blip_buffer).set_integrator( name##_reader_accum ))
|
||||
|
||||
#define BLIP_READER_ADJ_( name, offset ) (name##_reader_buf += offset)
|
||||
|
||||
int const blip_reader_idx_factor = sizeof (Blip_Buffer::delta_t);
|
||||
|
||||
#define BLIP_READER_NEXT_IDX_( name, bass, idx ) {\
|
||||
name##_reader_accum -= name##_reader_accum >> (bass);\
|
||||
name##_reader_accum += name##_reader_buf [(idx)];\
|
||||
}
|
||||
|
||||
#define BLIP_READER_NEXT_RAW_IDX_( name, bass, idx ) {\
|
||||
name##_reader_accum -= name##_reader_accum >> (bass);\
|
||||
name##_reader_accum +=\
|
||||
*(Blip_Buffer::delta_t const*) ((char const*) name##_reader_buf + (idx));\
|
||||
}
|
||||
|
||||
//// BLIP_CLAMP
|
||||
|
||||
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
|
||||
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
|
||||
#define BLIP_X86 1
|
||||
#define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in
|
||||
#else
|
||||
#define BLIP_CLAMP_( in ) (blip_sample_t) in != in
|
||||
#endif
|
||||
|
||||
// Clamp sample to blip_sample_t range
|
||||
#define BLIP_CLAMP( sample, out )\
|
||||
{ if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 31) ^ 0x7FFF; }
|
||||
|
||||
|
||||
//// Blip_Synth
|
||||
|
||||
// (in >> sh & mask) * mul
|
||||
#define BLIP_SH_AND_MUL( in, sh, mask, mul ) \
|
||||
((int) (in) / ((1U << (sh)) / (mul)) & (unsigned) ((mask) * (mul)))
|
||||
|
||||
// (T*) ptr + (off >> sh)
|
||||
#define BLIP_PTR_OFF_SH( T, ptr, off, sh ) \
|
||||
((T*) (BLIP_SH_AND_MUL( off, sh, -1, sizeof (T) ) + (char*) (ptr)))
|
||||
|
||||
template<int quality,int range>
|
||||
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
|
||||
int delta, Blip_Buffer* blip_buf ) const
|
||||
{
|
||||
#if BLIP_BUFFER_FAST
|
||||
int const half_width = 1;
|
||||
#else
|
||||
int const half_width = quality / 2;
|
||||
#endif
|
||||
|
||||
Blip_Buffer::delta_t* BLARGG_RESTRICT buf = blip_buf->delta_at( time );
|
||||
|
||||
delta *= impl.delta_factor;
|
||||
|
||||
int const phase_shift = BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS;
|
||||
int const phase = (half_width & (half_width - 1)) ?
|
||||
(int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) ) * half_width :
|
||||
(int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) * half_width );
|
||||
|
||||
#if BLIP_BUFFER_FAST
|
||||
int left = buf [0] + delta;
|
||||
|
||||
// Kind of crappy, but doing shift after multiply results in overflow.
|
||||
// Alternate way of delaying multiply by delta_factor results in worse
|
||||
// sub-sample resolution.
|
||||
int right = (delta >> BLIP_PHASE_BITS) * phase;
|
||||
#if BLIP_BUFFER_NOINTERP
|
||||
// TODO: remove? (just a hack to see how it sounds)
|
||||
right = 0;
|
||||
#endif
|
||||
left -= right;
|
||||
right += buf [1];
|
||||
|
||||
buf [0] = left;
|
||||
buf [1] = right;
|
||||
#else
|
||||
|
||||
int const fwd = -quality / 2;
|
||||
int const rev = fwd + quality - 2;
|
||||
|
||||
coeff_t const* BLARGG_RESTRICT imp = (coeff_t const*) ((char const*) phases + phase);
|
||||
int const phase2 = phase + phase - (blip_res - 1) * half_width * sizeof (coeff_t);
|
||||
|
||||
#define BLIP_MID_IMP imp = (coeff_t const*) ((char const*) imp - phase2);
|
||||
|
||||
#if BLIP_MAX_QUALITY > 16
|
||||
// General version for any quality
|
||||
if ( quality != 8 && quality != 12 && quality != 16 )
|
||||
{
|
||||
buf += fwd;
|
||||
|
||||
// left half
|
||||
for ( int n = half_width / 2; --n >= 0; )
|
||||
{
|
||||
buf [0] += imp [0] * delta;
|
||||
buf [1] += imp [1] * delta;
|
||||
imp += 2;
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
// mirrored right half
|
||||
BLIP_MID_IMP
|
||||
for ( int n = half_width / 2; --n >= 0; )
|
||||
{
|
||||
buf [0] += imp [-1] * delta;
|
||||
buf [1] += *(imp -= 2) * delta;
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Unrolled versions for qualities 8, 12, and 16
|
||||
|
||||
#if BLIP_X86
|
||||
// This gives better code for x86
|
||||
#define BLIP_ADD( out, in ) \
|
||||
buf [out] += imp [in] * delta
|
||||
|
||||
#define BLIP_FWD( i ) {\
|
||||
BLIP_ADD( fwd + i, i );\
|
||||
BLIP_ADD( fwd + 1 + i, i + 1 );\
|
||||
}
|
||||
|
||||
#define BLIP_REV( r ) {\
|
||||
BLIP_ADD( rev - r, r + 1 );\
|
||||
BLIP_ADD( rev + 1 - r, r );\
|
||||
}
|
||||
|
||||
BLIP_FWD( 0 )
|
||||
BLIP_FWD( 2 )
|
||||
if ( quality > 8 ) BLIP_FWD( 4 )
|
||||
if ( quality > 12 ) BLIP_FWD( 6 )
|
||||
BLIP_MID_IMP
|
||||
if ( quality > 12 ) BLIP_REV( 6 )
|
||||
if ( quality > 8 ) BLIP_REV( 4 )
|
||||
BLIP_REV( 2 )
|
||||
BLIP_REV( 0 )
|
||||
|
||||
#else
|
||||
// Help RISC processors and simplistic compilers by reading ahead of writes
|
||||
#define BLIP_FWD( i ) {\
|
||||
int t0 = i0 * delta + buf [fwd + i];\
|
||||
int t1 = imp [i + 1] * delta + buf [fwd + 1 + i];\
|
||||
i0 = imp [i + 2];\
|
||||
buf [fwd + i] = t0;\
|
||||
buf [fwd + 1 + i] = t1;\
|
||||
}
|
||||
|
||||
#define BLIP_REV( r ) {\
|
||||
int t0 = i0 * delta + buf [rev - r];\
|
||||
int t1 = imp [r] * delta + buf [rev + 1 - r];\
|
||||
i0 = imp [r - 1];\
|
||||
buf [rev - r] = t0;\
|
||||
buf [rev + 1 - r] = t1;\
|
||||
}
|
||||
|
||||
int i0 = *imp;
|
||||
BLIP_FWD( 0 )
|
||||
if ( quality > 8 ) BLIP_FWD( 2 )
|
||||
if ( quality > 12 ) BLIP_FWD( 4 )
|
||||
{
|
||||
int const mid = half_width - 1;
|
||||
int t0 = i0 * delta + buf [fwd + mid - 1];
|
||||
int t1 = imp [mid] * delta + buf [fwd + mid ];
|
||||
BLIP_MID_IMP
|
||||
i0 = imp [mid];
|
||||
buf [fwd + mid - 1] = t0;
|
||||
buf [fwd + mid ] = t1;
|
||||
}
|
||||
if ( quality > 12 ) BLIP_REV( 6 )
|
||||
if ( quality > 8 ) BLIP_REV( 4 )
|
||||
BLIP_REV( 2 )
|
||||
|
||||
int t0 = i0 * delta + buf [rev ];
|
||||
int t1 = *imp * delta + buf [rev + 1];
|
||||
buf [rev ] = t0;
|
||||
buf [rev + 1] = t1;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template<int quality,int range>
|
||||
#if BLIP_BUFFER_FAST
|
||||
inline
|
||||
#endif
|
||||
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
|
||||
{
|
||||
offset_resampled( buf->to_fixed( t ), delta, buf );
|
||||
}
|
||||
|
||||
template<int quality,int range>
|
||||
#if BLIP_BUFFER_FAST
|
||||
inline
|
||||
#endif
|
||||
void Blip_Synth<quality,range>::update( blip_time_t t, int amp )
|
||||
{
|
||||
int delta = amp - impl.last_amp;
|
||||
impl.last_amp = amp;
|
||||
offset_resampled( impl.buf->to_fixed( t ), delta, impl.buf );
|
||||
}
|
||||
|
||||
|
||||
//// blip_eq_t
|
||||
|
||||
inline blip_eq_t::blip_eq_t( double t ) :
|
||||
treble( t ), kaiser( 5.2 ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { }
|
||||
inline blip_eq_t::blip_eq_t( double t, int rf, int sr, int cf, double k ) :
|
||||
treble( t ), kaiser( k ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { }
|
||||
|
||||
|
||||
//// Blip_Buffer
|
||||
|
||||
inline int Blip_Buffer::length() const { return length_; }
|
||||
inline int Blip_Buffer::samples_avail() const { return (int) (offset_ >> BLIP_BUFFER_ACCURACY); }
|
||||
inline int Blip_Buffer::sample_rate() const { return sample_rate_; }
|
||||
inline int Blip_Buffer::output_latency() const { return BLIP_MAX_QUALITY / 2; }
|
||||
inline int Blip_Buffer::clock_rate() const { return clock_rate_; }
|
||||
inline void Blip_Buffer::clock_rate( int cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); }
|
||||
|
||||
inline void Blip_Buffer::remove_silence( int count )
|
||||
{
|
||||
// fails if you try to remove more samples than available
|
||||
assert( count <= samples_avail() );
|
||||
offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,268 @@
|
|||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#include "Bml_Parser.h"
|
||||
|
||||
Bml_Node Bml_Node::emptyNode;
|
||||
|
||||
Bml_Node::Bml_Node()
|
||||
{
|
||||
name = 0;
|
||||
value = 0;
|
||||
}
|
||||
|
||||
Bml_Node::Bml_Node(const Bml_Node &in)
|
||||
{
|
||||
size_t length;
|
||||
name = 0;
|
||||
if (in.name)
|
||||
{
|
||||
length = strlen(in.name);
|
||||
name = new char[length + 1];
|
||||
memcpy(name, in.name, length + 1);
|
||||
}
|
||||
value = 0;
|
||||
if (in.value)
|
||||
{
|
||||
length = strlen(in.value);
|
||||
value = new char[length + 1];
|
||||
memcpy(value, in.value, length + 1);
|
||||
}
|
||||
children = in.children;
|
||||
}
|
||||
|
||||
Bml_Node::~Bml_Node()
|
||||
{
|
||||
delete [] name;
|
||||
delete [] value;
|
||||
}
|
||||
|
||||
void Bml_Node::clear()
|
||||
{
|
||||
delete [] name;
|
||||
delete [] value;
|
||||
|
||||
name = 0;
|
||||
value = 0;
|
||||
children.resize( 0 );
|
||||
}
|
||||
|
||||
void Bml_Node::setLine(const char *line)
|
||||
{
|
||||
delete [] name;
|
||||
delete [] value;
|
||||
|
||||
name = 0;
|
||||
value = 0;
|
||||
|
||||
const char * line_end = strchr(line, '\n');
|
||||
if ( !line_end ) line_end = line + strlen(line);
|
||||
|
||||
const char * first_letter = line;
|
||||
while ( first_letter < line_end && *first_letter <= 0x20 ) first_letter++;
|
||||
|
||||
const char * colon = strchr(first_letter, ':');
|
||||
if (colon >= line_end) colon = 0;
|
||||
const char * last_letter = line_end - 1;
|
||||
|
||||
if (colon)
|
||||
{
|
||||
const char * first_value_letter = colon + 1;
|
||||
while (first_value_letter < line_end && *first_value_letter <= 0x20) first_value_letter++;
|
||||
last_letter = line_end - 1;
|
||||
while (last_letter > first_value_letter && *last_letter <= 0x20) last_letter--;
|
||||
|
||||
value = new char[last_letter - first_value_letter + 2];
|
||||
memcpy(value, first_value_letter, last_letter - first_value_letter + 1);
|
||||
value[last_letter - first_value_letter + 1] = '\0';
|
||||
|
||||
last_letter = colon - 1;
|
||||
}
|
||||
|
||||
while (last_letter > first_letter && *last_letter <= 0x20) last_letter--;
|
||||
|
||||
name = new char[last_letter - first_letter + 2];
|
||||
memcpy(name, first_letter, last_letter - first_letter + 1);
|
||||
name[last_letter - first_letter + 1] = '\0';
|
||||
}
|
||||
|
||||
void Bml_Node::addChild(const Bml_Node &child)
|
||||
{
|
||||
children.push_back(child);
|
||||
}
|
||||
|
||||
const char * Bml_Node::getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
const char * Bml_Node::getValue() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
size_t Bml_Node::getChildCount() const
|
||||
{
|
||||
return children.size();
|
||||
}
|
||||
|
||||
Bml_Node const& Bml_Node::getChild(size_t index) const
|
||||
{
|
||||
return children[index];
|
||||
}
|
||||
|
||||
Bml_Node & Bml_Node::walkToNode(const char *path)
|
||||
{
|
||||
Bml_Node * node = this;
|
||||
while ( *path )
|
||||
{
|
||||
bool item_found = false;
|
||||
const char * next_separator = strchr( path, ':' );
|
||||
if ( !next_separator ) next_separator = path + strlen(path);
|
||||
for ( std::vector<Bml_Node>::iterator it = node->children.end(); it != node->children.begin(); )
|
||||
{
|
||||
--it;
|
||||
if ( next_separator - path == strlen(it->name) &&
|
||||
strncmp( it->name, path, next_separator - path ) == 0 )
|
||||
{
|
||||
node = &(*it);
|
||||
item_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !item_found ) return emptyNode;
|
||||
if ( *next_separator )
|
||||
{
|
||||
path = next_separator + 1;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
return *node;
|
||||
}
|
||||
|
||||
Bml_Node const& Bml_Node::walkToNode(const char *path) const
|
||||
{
|
||||
Bml_Node const* next_node;
|
||||
Bml_Node const* node = this;
|
||||
while ( *path )
|
||||
{
|
||||
bool item_found = false;
|
||||
size_t array_index = ~0;
|
||||
const char * array_index_start = strchr( path, '[' );
|
||||
const char * next_separator = strchr( path, ':' );
|
||||
if ( !next_separator ) next_separator = path + strlen(path);
|
||||
if ( array_index_start && array_index_start < next_separator )
|
||||
{
|
||||
char * temp;
|
||||
array_index = strtoul( array_index_start + 1, &temp, 10 );
|
||||
}
|
||||
else
|
||||
{
|
||||
array_index_start = next_separator;
|
||||
}
|
||||
for ( std::vector<Bml_Node>::const_iterator it = node->children.begin(), ite = node->children.end(); it != ite; ++it )
|
||||
{
|
||||
if ( array_index_start - path == strlen(it->name) &&
|
||||
strncmp( it->name, path, array_index_start - path ) == 0 )
|
||||
{
|
||||
next_node = &(*it);
|
||||
item_found = true;
|
||||
if ( array_index == 0 ) break;
|
||||
--array_index;
|
||||
}
|
||||
}
|
||||
if ( !item_found ) return emptyNode;
|
||||
node = next_node;
|
||||
if ( *next_separator )
|
||||
{
|
||||
path = next_separator + 1;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
return *node;
|
||||
}
|
||||
|
||||
void Bml_Parser::parseDocument( const char * source )
|
||||
{
|
||||
std::vector<size_t> indents;
|
||||
std::string last_name;
|
||||
std::string current_path;
|
||||
|
||||
document.clear();
|
||||
|
||||
size_t last_indent = ~0;
|
||||
|
||||
Bml_Node node;
|
||||
|
||||
while ( *source )
|
||||
{
|
||||
const char * line_end = strchr( source, '\n' );
|
||||
if ( !line_end ) line_end = source + strlen( source );
|
||||
|
||||
if ( node.getName() ) last_name = node.getName();
|
||||
|
||||
node.setLine( source );
|
||||
|
||||
size_t indent = 0;
|
||||
while ( source < line_end && *source <= 0x20 )
|
||||
{
|
||||
source++;
|
||||
indent++;
|
||||
}
|
||||
|
||||
if ( last_indent == ~0 ) last_indent = indent;
|
||||
|
||||
if ( indent > last_indent )
|
||||
{
|
||||
indents.push_back( last_indent );
|
||||
last_indent = indent;
|
||||
if ( current_path.length() ) current_path += ":";
|
||||
current_path += last_name;
|
||||
}
|
||||
else if ( indent < last_indent )
|
||||
{
|
||||
while ( last_indent > indent && indents.size() )
|
||||
{
|
||||
last_indent = *(indents.end() - 1);
|
||||
indents.pop_back();
|
||||
size_t colon = current_path.find_last_of( ':' );
|
||||
if ( colon != ~0 ) current_path.resize( colon );
|
||||
else current_path.resize( 0 );
|
||||
}
|
||||
last_indent = indent;
|
||||
}
|
||||
|
||||
document.walkToNode( current_path.c_str() ).addChild( node );
|
||||
|
||||
source = line_end;
|
||||
while ( *source && *source == '\n' ) source++;
|
||||
}
|
||||
}
|
||||
|
||||
const char * Bml_Parser::enumValue(const char *path) const
|
||||
{
|
||||
return document.walkToNode(path).getValue();
|
||||
}
|
||||
|
||||
#if 0
|
||||
void Bml_Parser::print(Bml_Node const* node, unsigned int indent) const
|
||||
{
|
||||
if (node == 0) node = &document;
|
||||
|
||||
for (unsigned i = 0; i < indent; ++i) printf(" ");
|
||||
|
||||
printf("%s", node->getName());
|
||||
if (node->getValue()) printf(":%s", node->getValue());
|
||||
printf("\n");
|
||||
|
||||
indent++;
|
||||
|
||||
for (unsigned i = 0, j = node->getChildCount(); i < j; ++i)
|
||||
{
|
||||
Bml_Node const& child = node->getChild(i);
|
||||
print( &child, indent );
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef BML_PARSER_H
|
||||
#define BML_PARSER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
class Bml_Node
|
||||
{
|
||||
char * name;
|
||||
char * value;
|
||||
|
||||
std::vector<Bml_Node> children;
|
||||
|
||||
static Bml_Node emptyNode;
|
||||
|
||||
public:
|
||||
Bml_Node();
|
||||
Bml_Node(Bml_Node const& in);
|
||||
|
||||
~Bml_Node();
|
||||
|
||||
void clear();
|
||||
|
||||
void setLine(const char * line);
|
||||
void addChild(Bml_Node const& child);
|
||||
|
||||
const char * getName() const;
|
||||
const char * getValue() const;
|
||||
|
||||
size_t getChildCount() const;
|
||||
Bml_Node const& getChild(size_t index) const;
|
||||
|
||||
Bml_Node & walkToNode( const char * path );
|
||||
Bml_Node const& walkToNode( const char * path ) const;
|
||||
};
|
||||
|
||||
class Bml_Parser
|
||||
{
|
||||
Bml_Node document;
|
||||
|
||||
public:
|
||||
Bml_Parser() { }
|
||||
|
||||
void parseDocument(const char * document);
|
||||
|
||||
const char * enumValue(const char * path) const;
|
||||
|
||||
#if 0
|
||||
void print(Bml_Node const* node = 0, unsigned int indent = 0) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // BML_PARSER_H
|
|
@ -0,0 +1,77 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "C140_Emu.h"
|
||||
#include "c140.h"
|
||||
|
||||
C140_Emu::C140_Emu() { chip = 0; }
|
||||
|
||||
C140_Emu::~C140_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_c140( chip );
|
||||
}
|
||||
|
||||
int C140_Emu::set_rate( int type, double sample_rate, double clock_rate )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_c140( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_c140( sample_rate, clock_rate, type );
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void C140_Emu::reset()
|
||||
{
|
||||
device_reset_c140( chip );
|
||||
c140_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void C140_Emu::write( int addr, int data )
|
||||
{
|
||||
c140_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void C140_Emu::write_rom( int size, int start, int length, void * data )
|
||||
{
|
||||
c140_write_rom( chip, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void C140_Emu::mute_voices( int mask )
|
||||
{
|
||||
c140_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void C140_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
c140_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// C140 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef C140_EMU_H
|
||||
#define C140_EMU_H
|
||||
|
||||
class C140_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
C140_Emu();
|
||||
~C140_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int type, double sample_rate, double clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 24 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,147 @@
|
|||
// Fir_Resampler chip emulator container that mixes into the output buffer
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef CHIP_RESAMPLER_H
|
||||
#define CHIP_RESAMPLER_H
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#include "Fir_Resampler.h"
|
||||
typedef Fir_Resampler_Norm Chip_Resampler_Downsampler;
|
||||
|
||||
int const resampler_extra = 0; //34;
|
||||
|
||||
template<class Emu>
|
||||
class Chip_Resampler_Emu : public Emu {
|
||||
int last_time;
|
||||
short* out;
|
||||
typedef short dsample_t;
|
||||
enum { disabled_time = -1 };
|
||||
enum { gain_bits = 14 };
|
||||
blargg_vector<dsample_t> sample_buf;
|
||||
int sample_buf_size;
|
||||
int oversamples_per_frame;
|
||||
int buf_pos;
|
||||
int buffered;
|
||||
int resampler_size;
|
||||
int gain_;
|
||||
|
||||
Chip_Resampler_Downsampler resampler;
|
||||
|
||||
void mix_samples( short * buf, int count )
|
||||
{
|
||||
dsample_t * inptr = sample_buf.begin();
|
||||
for ( unsigned i = 0; i < count * 2; i++ )
|
||||
{
|
||||
int sample = inptr[i];
|
||||
sample += buf[i];
|
||||
if ((short)sample != sample) sample = 0x7FFF ^ (sample >> 31);
|
||||
buf[i] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Chip_Resampler_Emu() { last_time = disabled_time; out = NULL; }
|
||||
blargg_err_t setup( double oversample, double rolloff, double gain )
|
||||
{
|
||||
gain_ = (int) ((1 << gain_bits) * gain);
|
||||
RETURN_ERR( resampler.set_rate( oversample ) );
|
||||
return reset_resampler();
|
||||
}
|
||||
|
||||
blargg_err_t reset()
|
||||
{
|
||||
Emu::reset();
|
||||
resampler.clear();
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t reset_resampler()
|
||||
{
|
||||
unsigned int pairs;
|
||||
double rate = resampler.rate();
|
||||
if ( rate >= 1.0 ) pairs = 64.0 * rate;
|
||||
else pairs = 64.0 / rate;
|
||||
RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) );
|
||||
resize( pairs );
|
||||
resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2);
|
||||
RETURN_ERR( resampler.resize_buffer( resampler_size ) );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void resize( int pairs )
|
||||
{
|
||||
int new_sample_buf_size = pairs * 2;
|
||||
//new_sample_buf_size = new_sample_buf_size / 4 * 4; // TODO: needed only for 3:2 downsampler
|
||||
if ( sample_buf_size != new_sample_buf_size )
|
||||
{
|
||||
if ( (unsigned) new_sample_buf_size > sample_buf.size() )
|
||||
{
|
||||
check( false );
|
||||
return;
|
||||
}
|
||||
sample_buf_size = new_sample_buf_size;
|
||||
oversamples_per_frame = int (pairs * resampler.rate()) * 2 + 2;
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
buf_pos = buffered = 0;
|
||||
resampler.clear();
|
||||
}
|
||||
|
||||
void enable( bool b = true ) { last_time = b ? 0 : disabled_time; }
|
||||
bool enabled() const { return last_time != disabled_time; }
|
||||
void begin_frame( short* buf ) { out = buf; last_time = 0; }
|
||||
|
||||
int run_until( int time )
|
||||
{
|
||||
int count = time - last_time;
|
||||
while ( count > 0 )
|
||||
{
|
||||
if ( last_time < 0 )
|
||||
return false;
|
||||
last_time = time;
|
||||
if ( buffered )
|
||||
{
|
||||
int samples_to_copy = buffered;
|
||||
if ( samples_to_copy > count ) samples_to_copy = count;
|
||||
memcpy( out, sample_buf.begin(), samples_to_copy * sizeof(short) * 2 );
|
||||
memcpy( sample_buf.begin(), sample_buf.begin() + samples_to_copy * 2, ( buffered - samples_to_copy ) * 2 * sizeof(short) );
|
||||
buffered -= samples_to_copy;
|
||||
count -= samples_to_copy;
|
||||
continue;
|
||||
}
|
||||
int sample_count = oversamples_per_frame - resampler.written() + resampler_extra;
|
||||
memset( resampler.buffer(), 0, sample_count * sizeof(*resampler.buffer()) );
|
||||
Emu::run( sample_count >> 1, resampler.buffer() );
|
||||
for ( unsigned i = 0; i < sample_count; i++ )
|
||||
{
|
||||
dsample_t * ptr = resampler.buffer() + i;
|
||||
*ptr = ( *ptr * gain_ ) >> gain_bits;
|
||||
}
|
||||
short* p = out;
|
||||
resampler.write( sample_count );
|
||||
sample_count = resampler.read( sample_buf.begin(), count * 2 > sample_buf_size ? sample_buf_size : count * 2 ) >> 1;
|
||||
if ( sample_count > count )
|
||||
{
|
||||
out += count * Emu::out_chan_count;
|
||||
mix_samples( p, count );
|
||||
memmove( sample_buf.begin(), sample_buf.begin() + count * 2, (sample_count - count) * 2 * sizeof(short) );
|
||||
buffered = sample_count - count;
|
||||
return true;
|
||||
}
|
||||
else if (!sample_count) return true;
|
||||
out += sample_count * Emu::out_chan_count;
|
||||
mix_samples( p, sample_count );
|
||||
count -= sample_count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,74 @@
|
|||
// $package. http://www.slack.net/~ant/
|
||||
|
||||
#include "Downsampler.h"
|
||||
|
||||
/* Copyright (C) 2004-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const shift = 14;
|
||||
int const unit = 1 << shift;
|
||||
|
||||
void Downsampler::clear_()
|
||||
{
|
||||
pos = 0;
|
||||
Resampler::clear_();
|
||||
}
|
||||
|
||||
Downsampler::Downsampler()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
blargg_err_t Downsampler::set_rate_( double new_factor )
|
||||
{
|
||||
step = (int) (new_factor * unit + 0.5);
|
||||
return Resampler::set_rate_( 1.0 / unit * step );
|
||||
}
|
||||
|
||||
Resampler::sample_t const* Downsampler::resample_( sample_t** out_,
|
||||
sample_t const* out_end, sample_t const in [], int in_size )
|
||||
{
|
||||
in_size -= write_offset;
|
||||
if ( in_size > 0 )
|
||||
{
|
||||
sample_t* BLARGG_RESTRICT out = *out_;
|
||||
sample_t const* const in_end = in + in_size;
|
||||
|
||||
int const step = this->step;
|
||||
int pos = this->pos;
|
||||
|
||||
// TODO: IIR filter, then linear resample
|
||||
// TODO: detect skipped sample, allowing merging of IIR and resample?
|
||||
|
||||
do
|
||||
{
|
||||
#define INTERP( i, out )\
|
||||
out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\
|
||||
in [8 + i] * pos) >> (shift + 2);
|
||||
|
||||
int out_0;
|
||||
INTERP( 0, out_0 )
|
||||
INTERP( 1, out [0] = out_0; out [1] )
|
||||
out += stereo;
|
||||
|
||||
pos += step;
|
||||
in += ((unsigned) pos >> shift) * stereo;
|
||||
pos &= unit - 1;
|
||||
}
|
||||
while ( in < in_end && out < out_end );
|
||||
|
||||
this->pos = pos;
|
||||
*out_ = out;
|
||||
}
|
||||
return in;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Linear downsampler with pre-low-pass
|
||||
|
||||
// $package
|
||||
#ifndef DOWNSAMPLER_H
|
||||
#define DOWNSAMPLER_H
|
||||
|
||||
#include "Resampler.h"
|
||||
|
||||
class Downsampler : public Resampler {
|
||||
public:
|
||||
Downsampler();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t set_rate_( double );
|
||||
virtual void clear_();
|
||||
virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int );
|
||||
|
||||
private:
|
||||
enum { stereo = 2 };
|
||||
enum { write_offset = 8 * stereo };
|
||||
int pos;
|
||||
int step;
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,208 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Gbs_Core.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const tempo_unit = 16;
|
||||
int const idle_addr = 0xF00D;
|
||||
int const bank_size = 0x4000;
|
||||
|
||||
Gbs_Core::Gbs_Core() : rom( bank_size )
|
||||
{
|
||||
tempo = tempo_unit;
|
||||
assert( offsetof (header_t,copyright [32]) == header_t::size );
|
||||
}
|
||||
|
||||
Gbs_Core::~Gbs_Core() { }
|
||||
|
||||
void Gbs_Core::unload()
|
||||
{
|
||||
header_.timer_mode = 0; // set_tempo() reads this
|
||||
rom.clear();
|
||||
Gme_Loader::unload();
|
||||
}
|
||||
|
||||
bool Gbs_Core::header_t::valid_tag() const
|
||||
{
|
||||
return 0 == memcmp( tag, "GBS", 3 );
|
||||
}
|
||||
|
||||
blargg_err_t Gbs_Core::load_( Data_Reader& in )
|
||||
{
|
||||
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
|
||||
|
||||
if ( !header_.valid_tag() )
|
||||
return blargg_err_file_type;
|
||||
|
||||
if ( header_.vers != 1 )
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// Nintendo Game Boy GBS music file emulator core
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef GBS_CORE_H
|
||||
#define GBS_CORE_H
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
#include "Rom_Data.h"
|
||||
#include "Gb_Cpu.h"
|
||||
#include "Gb_Apu.h"
|
||||
|
||||
class Gbs_Core : public Gme_Loader {
|
||||
public:
|
||||
|
||||
// GBS file header
|
||||
struct header_t
|
||||
{
|
||||
enum { size = 112 };
|
||||
|
||||
char tag [ 3];
|
||||
byte vers;
|
||||
byte track_count;
|
||||
byte first_track;
|
||||
byte load_addr [ 2];
|
||||
byte init_addr [ 2];
|
||||
byte play_addr [ 2];
|
||||
byte stack_ptr [ 2];
|
||||
byte timer_modulo;
|
||||
byte timer_mode;
|
||||
char game [32]; // strings can be 32 chars, NOT terminated
|
||||
char author [32];
|
||||
char copyright [32];
|
||||
|
||||
// True if header has valid file signature
|
||||
bool valid_tag() const;
|
||||
};
|
||||
|
||||
// Header for currently loaded file
|
||||
header_t const& header() const { return header_; }
|
||||
|
||||
// Sound chip
|
||||
Gb_Apu& apu() { return apu_; }
|
||||
|
||||
// ROM data
|
||||
Rom_Data const& rom_() const { return rom; }
|
||||
|
||||
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
||||
void set_tempo( double );
|
||||
|
||||
// Starts track, where 0 is the first. Uses specified APU mode.
|
||||
blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb );
|
||||
|
||||
// Ends time frame at time t
|
||||
typedef int time_t; // clock count
|
||||
blargg_err_t end_frame( time_t t );
|
||||
|
||||
// Clocks between calls to play routine
|
||||
time_t play_period() const { return play_period_; }
|
||||
|
||||
protected:
|
||||
typedef int addr_t;
|
||||
|
||||
// Current time
|
||||
time_t time() const { return cpu.time() + end_time; }
|
||||
|
||||
// Runs emulator to time t
|
||||
blargg_err_t run_until( time_t t );
|
||||
|
||||
// Runs CPU until time becomes >= 0
|
||||
void run_cpu();
|
||||
|
||||
// Reads/writes memory and I/O
|
||||
int read_mem( addr_t );
|
||||
void write_mem( addr_t, int );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Gbs_Core();
|
||||
~Gbs_Core();
|
||||
virtual void unload();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
|
||||
private:
|
||||
enum { ram_addr = 0xA000 };
|
||||
enum { io_base = 0xFF00 };
|
||||
enum { hi_page = io_base - ram_addr };
|
||||
|
||||
Rom_Data rom;
|
||||
int tempo;
|
||||
time_t end_time;
|
||||
time_t play_period_;
|
||||
time_t next_play;
|
||||
header_t header_;
|
||||
Gb_Cpu cpu;
|
||||
Gb_Apu apu_;
|
||||
byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding];
|
||||
|
||||
void update_timer();
|
||||
void jsr_then_stop( byte const [] );
|
||||
void set_bank( int n );
|
||||
void write_io_inline( int offset, int data, int base );
|
||||
void write_io_( int offset, int data );
|
||||
int read_io( int offset );
|
||||
void write_io( int offset, int data );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,134 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Gbs_Core.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
//#include "gb_cpu_log.h"
|
||||
|
||||
/* Copyright (C) 2003-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifndef LOG_MEM
|
||||
#define LOG_MEM( addr, str, data ) data
|
||||
#endif
|
||||
|
||||
int Gbs_Core::read_mem( addr_t addr )
|
||||
{
|
||||
int result = *cpu.get_code( addr );
|
||||
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
|
||||
result = apu_.read_register( time(), addr );
|
||||
|
||||
#ifndef NDEBUG
|
||||
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
|
||||
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
|
||||
else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 )
|
||||
dprintf( "Unmapped read $%04X\n", (unsigned) addr );
|
||||
#endif
|
||||
|
||||
return LOG_MEM( addr, ">", result );
|
||||
}
|
||||
|
||||
inline void Gbs_Core::write_io_inline( int offset, int data, int base )
|
||||
{
|
||||
if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size )
|
||||
apu_.write_register( time(), offset + base, data & 0xFF );
|
||||
else if ( (unsigned) (offset - (0xFF06 - base)) < 2 )
|
||||
update_timer();
|
||||
else if ( offset == io_base - base )
|
||||
ram [base - ram_addr + offset] = 0; // keep joypad return value 0
|
||||
else
|
||||
ram [base - ram_addr + offset] = 0xFF;
|
||||
|
||||
//if ( offset == 0xFFFF - base )
|
||||
// dprintf( "Wrote interrupt mask\n" );
|
||||
}
|
||||
|
||||
void Gbs_Core::write_mem( addr_t addr, int data )
|
||||
{
|
||||
(void) LOG_MEM( addr, "<", data );
|
||||
|
||||
int offset = addr - ram_addr;
|
||||
if ( (unsigned) offset < 0x10000 - ram_addr )
|
||||
{
|
||||
ram [offset] = data;
|
||||
|
||||
offset -= 0xE000 - ram_addr;
|
||||
if ( (unsigned) offset < 0x1F80 )
|
||||
write_io_inline( offset, data, 0xE000 );
|
||||
}
|
||||
else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 )
|
||||
{
|
||||
set_bank( data & 0xFF );
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 )
|
||||
{
|
||||
dprintf( "Unmapped write $%04X\n", (unsigned) addr );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Gbs_Core::write_io_( int offset, int data )
|
||||
{
|
||||
write_io_inline( offset, data, io_base );
|
||||
}
|
||||
|
||||
inline void Gbs_Core::write_io( int offset, int data )
|
||||
{
|
||||
(void) LOG_MEM( offset + io_base, "<", data );
|
||||
|
||||
ram [io_base - ram_addr + offset] = data;
|
||||
if ( (unsigned) offset < 0x80 )
|
||||
write_io_( offset, data );
|
||||
}
|
||||
|
||||
int Gbs_Core::read_io( int offset )
|
||||
{
|
||||
int const io_base = 0xFF00;
|
||||
int result = ram [io_base - ram_addr + offset];
|
||||
|
||||
if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size )
|
||||
{
|
||||
result = apu_.read_register( time(), offset + io_base );
|
||||
(void) LOG_MEM( offset + io_base, ">", result );
|
||||
}
|
||||
else
|
||||
{
|
||||
check( result == read_mem( offset + io_base ) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#define READ_FAST( addr, out ) \
|
||||
{\
|
||||
out = READ_CODE( addr );\
|
||||
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\
|
||||
out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\
|
||||
else\
|
||||
check( out == read_mem( addr ) );\
|
||||
}
|
||||
|
||||
#define READ_MEM( addr ) read_mem( addr )
|
||||
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
||||
|
||||
#define WRITE_IO( addr, data ) write_io( addr, data )
|
||||
#define READ_IO( addr, out ) out = read_io( addr )
|
||||
|
||||
#define CPU cpu
|
||||
|
||||
#define CPU_BEGIN \
|
||||
void Gbs_Core::run_cpu()\
|
||||
{
|
||||
#include "Gb_Cpu_run.h"
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void Gme_Loader::unload()
|
||||
{
|
||||
file_begin_ = NULL;
|
||||
file_end_ = NULL;
|
||||
file_data.clear();
|
||||
}
|
||||
|
||||
Gme_Loader::Gme_Loader()
|
||||
{
|
||||
warning_ = NULL;
|
||||
Gme_Loader::unload();
|
||||
blargg_verify_byte_order(); // used by most emulator types, so save them the trouble
|
||||
}
|
||||
|
||||
Gme_Loader::~Gme_Loader() { }
|
||||
|
||||
blargg_err_t Gme_Loader::load_mem_( byte const data [], int size )
|
||||
{
|
||||
require( data != file_data.begin() ); // load_mem_() or load_() must be overridden
|
||||
Mem_File_Reader in( data, size );
|
||||
return load_( in );
|
||||
}
|
||||
|
||||
inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size )
|
||||
{
|
||||
file_begin_ = data;
|
||||
file_end_ = data + size;
|
||||
return load_mem_( data, size );
|
||||
}
|
||||
|
||||
blargg_err_t Gme_Loader::load_( Data_Reader& in )
|
||||
{
|
||||
RETURN_ERR( file_data.resize( in.remain() ) );
|
||||
RETURN_ERR( in.read( file_data.begin(), file_data.size() ) );
|
||||
return load_mem_wrapper( file_data.begin(), file_data.size() );
|
||||
}
|
||||
|
||||
blargg_err_t Gme_Loader::post_load_( blargg_err_t err )
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
unload();
|
||||
return err;
|
||||
}
|
||||
|
||||
return post_load();
|
||||
}
|
||||
|
||||
blargg_err_t Gme_Loader::load_mem( void const* in, long size )
|
||||
{
|
||||
pre_load();
|
||||
return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) );
|
||||
}
|
||||
|
||||
blargg_err_t Gme_Loader::load( Data_Reader& in )
|
||||
{
|
||||
pre_load();
|
||||
return post_load_( load_( in ) );
|
||||
}
|
||||
|
||||
blargg_err_t Gme_Loader::load_file( const char path [] )
|
||||
{
|
||||
pre_load();
|
||||
GME_FILE_READER in;
|
||||
RETURN_ERR( in.open( path ) );
|
||||
return post_load_( load_( in ) );
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// Common interface for loading file data from various sources
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef GME_LOADER_H
|
||||
#define GME_LOADER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Data_Reader.h"
|
||||
|
||||
class Gme_Loader {
|
||||
public:
|
||||
|
||||
// Each loads game music data from a file and returns an error if
|
||||
// file is wrong type or is seriously corrupt. Minor problems are
|
||||
// reported using warning().
|
||||
|
||||
// Loads from file on disk
|
||||
blargg_err_t load_file( const char path [] );
|
||||
|
||||
// Loads from custom data source (see Data_Reader.h)
|
||||
blargg_err_t load( Data_Reader& );
|
||||
|
||||
// Loads from file already read into memory. Object might keep pointer to
|
||||
// data; if it does, you MUST NOT free it until you're done with the file.
|
||||
blargg_err_t load_mem( void const* data, long size );
|
||||
|
||||
// Most recent warning string, or NULL if none. Clears current warning after
|
||||
// returning.
|
||||
const char* warning();
|
||||
|
||||
// Unloads file from memory
|
||||
virtual void unload();
|
||||
|
||||
virtual ~Gme_Loader();
|
||||
|
||||
protected:
|
||||
typedef BOOST::uint8_t byte;
|
||||
|
||||
// File data in memory, or 0 if data was loaded with load_()
|
||||
byte const* file_begin() const { return file_begin_; }
|
||||
byte const* file_end() const { return file_end_; }
|
||||
int file_size() const { return (int) (file_end_ - file_begin_); }
|
||||
|
||||
// Sets warning string
|
||||
void set_warning( const char s [] ) { warning_ = s; }
|
||||
|
||||
// At least one must be overridden
|
||||
virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_()
|
||||
virtual blargg_err_t load_mem_( byte const data [], int size ); // use data in memory
|
||||
|
||||
// Optionally overridden
|
||||
virtual void pre_load() { unload(); } // called before load_()/load_mem_()
|
||||
virtual blargg_err_t post_load() { return blargg_ok; } // called after load_()/load_mem_() succeeds
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Gme_Loader( const Gme_Loader& );
|
||||
Gme_Loader& operator = ( const Gme_Loader& );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Gme_Loader();
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
blargg_vector<byte> file_data; // used only when loading from file to load_mem_()
|
||||
byte const* file_begin_;
|
||||
byte const* file_end_;
|
||||
const char* warning_;
|
||||
|
||||
blargg_err_t load_mem_wrapper( byte const [], int );
|
||||
blargg_err_t post_load_( blargg_err_t err );
|
||||
};
|
||||
|
||||
// Files are read with GME_FILE_READER. Default supports gzip if zlib is available.
|
||||
#ifndef GME_FILE_READER
|
||||
#ifdef HAVE_ZLIB_H
|
||||
#define GME_FILE_READER Gzip_File_Reader
|
||||
#else
|
||||
#define GME_FILE_READER Std_File_Reader
|
||||
#endif
|
||||
#elif defined (GME_FILE_READER_INCLUDE)
|
||||
#include GME_FILE_READER_INCLUDE
|
||||
#endif
|
||||
|
||||
inline const char* Gme_Loader::warning()
|
||||
{
|
||||
const char* s = warning_;
|
||||
warning_ = NULL;
|
||||
return s;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,309 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Hes_Apu_Adpcm.h"
|
||||
|
||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Hes_Apu_Adpcm::Hes_Apu_Adpcm()
|
||||
{
|
||||
output = NULL;
|
||||
|
||||
memset( &state, 0, sizeof( state ) );
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Hes_Apu_Adpcm::reset()
|
||||
{
|
||||
last_time = 0;
|
||||
next_timer = 0;
|
||||
last_amp = 0;
|
||||
|
||||
memset( &state.pcmbuf, 0, sizeof(state.pcmbuf) );
|
||||
memset( &state.port, 0, sizeof(state.port) );
|
||||
|
||||
state.ad_sample = 0;
|
||||
state.ad_ref_index = 0;
|
||||
|
||||
state.addr = 0;
|
||||
state.freq = 0;
|
||||
state.writeptr = 0;
|
||||
state.readptr = 0;
|
||||
state.playflag = 0;
|
||||
state.repeatflag = 0;
|
||||
state.length = 0;
|
||||
state.volume = 0xFF;
|
||||
state.fadetimer = 0;
|
||||
state.fadecount = 0;
|
||||
}
|
||||
|
||||
void Hes_Apu_Adpcm::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
||||
{
|
||||
// Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL)
|
||||
require( !center || (center && !left && !right) || (center && left && right) );
|
||||
require( (unsigned) i < osc_count ); // fails if you pass invalid osc index
|
||||
|
||||
if ( !center || !left || !right )
|
||||
{
|
||||
left = center;
|
||||
right = center;
|
||||
}
|
||||
|
||||
output = center;
|
||||
}
|
||||
|
||||
void Hes_Apu_Adpcm::run_until( blip_time_t end_time )
|
||||
{
|
||||
int volume = state.volume;
|
||||
int fadetimer = state.fadetimer;
|
||||
int fadecount = state.fadecount;
|
||||
int last_time = this->last_time;
|
||||
double next_timer = this->next_timer;
|
||||
int last_amp = this->last_amp;
|
||||
|
||||
Blip_Buffer* output = this->output; // cache often-used values
|
||||
|
||||
while ( state.playflag && last_time < end_time )
|
||||
{
|
||||
while ( last_time >= next_timer )
|
||||
{
|
||||
if ( fadetimer )
|
||||
{
|
||||
if ( fadecount > 0 )
|
||||
{
|
||||
fadecount--;
|
||||
volume = 0xFF * fadecount / fadetimer;
|
||||
}
|
||||
else if ( fadecount < 0 )
|
||||
{
|
||||
fadecount++;
|
||||
volume = 0xFF - ( 0xFF * fadecount / fadetimer );
|
||||
}
|
||||
}
|
||||
next_timer += 7159.091;
|
||||
}
|
||||
int amp;
|
||||
if ( state.ad_low_nibble )
|
||||
{
|
||||
amp = adpcm_decode( state.pcmbuf[ state.playptr ] & 0x0F );
|
||||
state.ad_low_nibble = false;
|
||||
state.playptr++;
|
||||
state.playedsamplecount++;
|
||||
if ( state.playedsamplecount == state.playlength )
|
||||
{
|
||||
state.playflag = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
amp = adpcm_decode( state.pcmbuf[ state.playptr ] >> 4 );
|
||||
state.ad_low_nibble = true;
|
||||
}
|
||||
amp = amp * volume / 0xFF;
|
||||
int delta = amp - last_amp;
|
||||
if ( output && delta )
|
||||
{
|
||||
last_amp = amp;
|
||||
synth.offset_inline( last_time, delta, output );
|
||||
}
|
||||
last_time += state.freq;
|
||||
}
|
||||
|
||||
if ( !state.playflag )
|
||||
{
|
||||
while ( next_timer <= end_time ) next_timer += 7159.091;
|
||||
last_time = end_time;
|
||||
}
|
||||
|
||||
this->last_time = last_time;
|
||||
this->next_timer = next_timer;
|
||||
this->last_amp = last_amp;
|
||||
state.volume = volume;
|
||||
state.fadetimer = fadetimer;
|
||||
state.fadecount = fadecount;
|
||||
}
|
||||
|
||||
void Hes_Apu_Adpcm::write_data( blip_time_t time, int addr, int data )
|
||||
{
|
||||
if ( time > last_time ) run_until( time );
|
||||
|
||||
data &= 0xFF;
|
||||
state.port[ addr & 15 ] = data;
|
||||
switch ( addr & 15 )
|
||||
{
|
||||
case 8:
|
||||
state.addr &= 0xFF00;
|
||||
state.addr |= data;
|
||||
break;
|
||||
case 9:
|
||||
state.addr &= 0xFF;
|
||||
state.addr |= data << 8;
|
||||
break;
|
||||
case 10:
|
||||
state.pcmbuf[ state.writeptr++ ] = data;
|
||||
state.playlength ++;
|
||||
break;
|
||||
case 11:
|
||||
dprintf("ADPCM DMA 0x%02X", data);
|
||||
break;
|
||||
case 13:
|
||||
if ( data & 0x80 )
|
||||
{
|
||||
state.addr = 0;
|
||||
state.freq = 0;
|
||||
state.writeptr = 0;
|
||||
state.readptr = 0;
|
||||
state.playflag = 0;
|
||||
state.repeatflag = 0;
|
||||
state.length = 0;
|
||||
state.volume = 0xFF;
|
||||
}
|
||||
if ( ( data & 3 ) == 3 )
|
||||
{
|
||||
state.writeptr = state.addr;
|
||||
}
|
||||
if ( data & 8 )
|
||||
{
|
||||
state.readptr = state.addr ? state.addr - 1 : state.addr;
|
||||
}
|
||||
if ( data & 0x10 )
|
||||
{
|
||||
state.length = state.addr;
|
||||
}
|
||||
state.repeatflag = data & 0x20;
|
||||
state.playflag = data & 0x40;
|
||||
if ( state.playflag )
|
||||
{
|
||||
state.playptr = state.readptr;
|
||||
state.playlength = state.length + 1;
|
||||
state.playedsamplecount = 0;
|
||||
state.ad_sample = 0;
|
||||
state.ad_low_nibble = false;
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
state.freq = 7159091 / ( 32000 / ( 16 - ( data & 15 ) ) );
|
||||
break;
|
||||
case 15:
|
||||
switch ( data & 15 )
|
||||
{
|
||||
case 0:
|
||||
case 8:
|
||||
case 12:
|
||||
state.fadetimer = -100;
|
||||
state.fadecount = state.fadetimer;
|
||||
break;
|
||||
case 10:
|
||||
state.fadetimer = 5000;
|
||||
state.fadecount = state.fadetimer;
|
||||
break;
|
||||
case 14:
|
||||
state.fadetimer = 1500;
|
||||
state.fadecount = state.fadetimer;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int Hes_Apu_Adpcm::read_data( blip_time_t time, int addr )
|
||||
{
|
||||
if ( time > last_time ) run_until( time );
|
||||
|
||||
switch ( addr & 15 )
|
||||
{
|
||||
case 10:
|
||||
return state.pcmbuf [state.readptr++];
|
||||
case 11:
|
||||
return state.port [11] & ~1;
|
||||
case 12:
|
||||
if (!state.playflag)
|
||||
{
|
||||
state.port [12] |= 1;
|
||||
state.port [12] &= ~8;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.port [12] &= ~1;
|
||||
state.port [12] |= 8;
|
||||
}
|
||||
return state.port [12];
|
||||
case 13:
|
||||
return state.port [13];
|
||||
}
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
void Hes_Apu_Adpcm::end_frame( blip_time_t end_time )
|
||||
{
|
||||
run_until( end_time );
|
||||
last_time -= end_time;
|
||||
next_timer -= (double)end_time;
|
||||
check( last_time >= 0 );
|
||||
if ( output )
|
||||
output->set_modified();
|
||||
}
|
||||
|
||||
static short stepsize[49] = {
|
||||
16, 17, 19, 21, 23, 25, 28,
|
||||
31, 34, 37, 41, 45, 50, 55,
|
||||
60, 66, 73, 80, 88, 97, 107,
|
||||
118, 130, 143, 157, 173, 190, 209,
|
||||
230, 253, 279, 307, 337, 371, 408,
|
||||
449, 494, 544, 598, 658, 724, 796,
|
||||
876, 963,1060,1166,1282,1411,1552
|
||||
};
|
||||
|
||||
int Hes_Apu_Adpcm::adpcm_decode( int code )
|
||||
{
|
||||
int step = stepsize[state.ad_ref_index];
|
||||
int delta;
|
||||
int c = code & 7;
|
||||
#if 1
|
||||
delta = 0;
|
||||
if ( c & 4 ) delta += step;
|
||||
step >>= 1;
|
||||
if ( c & 2 ) delta += step;
|
||||
step >>= 1;
|
||||
if ( c & 1 ) delta += step;
|
||||
step >>= 1;
|
||||
delta += step;
|
||||
#else
|
||||
delta = ( ( c + c + 1 ) * step ) / 8; // maybe faster, but introduces rounding
|
||||
#endif
|
||||
if ( c != code )
|
||||
{
|
||||
state.ad_sample -= delta;
|
||||
if ( state.ad_sample < -2048 )
|
||||
state.ad_sample = -2048;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.ad_sample += delta;
|
||||
if ( state.ad_sample > 2047 )
|
||||
state.ad_sample = 2047;
|
||||
}
|
||||
|
||||
static int const steps [8] = {
|
||||
-1, -1, -1, -1, 2, 4, 6, 8
|
||||
};
|
||||
state.ad_ref_index += steps [c];
|
||||
if ( state.ad_ref_index < 0 )
|
||||
state.ad_ref_index = 0;
|
||||
else if ( state.ad_ref_index > 48 )
|
||||
state.ad_ref_index = 48;
|
||||
|
||||
return state.ad_sample;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef HES_APU_ADPCM_H
|
||||
#define HES_APU_ADPCM_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
class Hes_Apu_Adpcm {
|
||||
public:
|
||||
// Basics
|
||||
|
||||
// Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0,
|
||||
// output is mono.
|
||||
void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||
|
||||
// Emulates to time t, then writes data to addr
|
||||
void write_data( blip_time_t t, int addr, int data );
|
||||
|
||||
// Emulates to time t, then reads from addr
|
||||
int read_data( blip_time_t t, int addr );
|
||||
|
||||
// Emulates to time t, then subtracts t from the current time.
|
||||
// OK if previous write call had time slightly after t.
|
||||
void end_frame( blip_time_t t );
|
||||
|
||||
// More features
|
||||
|
||||
// Resets sound chip
|
||||
void reset();
|
||||
|
||||
// Same as set_output(), but for a particular channel
|
||||
enum { osc_count = 1 }; // 0 <= chan < osc_count
|
||||
void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL );
|
||||
|
||||
// Sets treble equalization
|
||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||
|
||||
// Sets overall volume, where 1.0 is normal
|
||||
void volume( double v ) { synth.volume( 0.6 / osc_count / amp_range * v ); }
|
||||
|
||||
// Registers are at io_addr to io_addr+io_size-1
|
||||
enum { io_addr = 0x1800 };
|
||||
enum { io_size = 0x400 };
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Hes_Apu_Adpcm();
|
||||
typedef BOOST::uint8_t byte;
|
||||
|
||||
private:
|
||||
enum { amp_range = 2048 };
|
||||
|
||||
struct State
|
||||
{
|
||||
byte pcmbuf [0x10000];
|
||||
byte port [0x10];
|
||||
int ad_sample;
|
||||
int ad_ref_index;
|
||||
bool ad_low_nibble;
|
||||
int freq;
|
||||
unsigned short addr;
|
||||
unsigned short writeptr;
|
||||
unsigned short readptr;
|
||||
unsigned short playptr;
|
||||
byte playflag;
|
||||
byte repeatflag;
|
||||
int length;
|
||||
int playlength;
|
||||
int playedsamplecount;
|
||||
int volume;
|
||||
int fadetimer;
|
||||
int fadecount;
|
||||
};
|
||||
State state;
|
||||
Blip_Synth_Fast synth;
|
||||
|
||||
Blip_Buffer* output;
|
||||
blip_time_t last_time;
|
||||
double next_timer;
|
||||
int last_amp;
|
||||
|
||||
void run_until( blip_time_t );
|
||||
|
||||
int adpcm_decode( int );
|
||||
};
|
||||
|
||||
inline void Hes_Apu_Adpcm::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
||||
{
|
||||
set_output( 0, c, l, r );
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,408 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Hes_Core.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const timer_mask = 0x04;
|
||||
int const vdp_mask = 0x02;
|
||||
int const i_flag_mask = 0x04;
|
||||
int const unmapped = 0xFF;
|
||||
|
||||
int const period_60hz = 262 * 455; // scanlines * clocks per scanline
|
||||
|
||||
Hes_Core::Hes_Core() : rom( Hes_Cpu::page_size )
|
||||
{
|
||||
timer.raw_load = 0;
|
||||
}
|
||||
|
||||
Hes_Core::~Hes_Core() { }
|
||||
|
||||
void Hes_Core::unload()
|
||||
{
|
||||
rom.clear();
|
||||
Gme_Loader::unload();
|
||||
}
|
||||
|
||||
bool Hes_Core::header_t::valid_tag() const
|
||||
{
|
||||
return 0 == memcmp( tag, "HESM", 4 );
|
||||
}
|
||||
|
||||
blargg_err_t Hes_Core::load_( Data_Reader& in )
|
||||
{
|
||||
assert( offsetof (header_t,unused [4]) == header_t::size );
|
||||
RETURN_ERR( rom.load( in, header_t::size, &header_, unmapped ) );
|
||||
|
||||
if ( !header_.valid_tag() )
|
||||
return blargg_err_file_type;
|
||||
|
||||
if ( header_.vers != 0 )
|
||||
set_warning( "Unknown file version" );
|
||||
|
||||
if ( memcmp( header_.data_tag, "DATA", 4 ) )
|
||||
set_warning( "Data header missing" );
|
||||
|
||||
if ( memcmp( header_.unused, "\0\0\0\0", 4 ) )
|
||||
set_warning( "Unknown header data" );
|
||||
|
||||
// File spec supports multiple blocks, but I haven't found any, and
|
||||
// many files have bad sizes in the only block, so it's simpler to
|
||||
// just try to load the damn data as best as possible.
|
||||
|
||||
int addr = get_le32( header_.addr );
|
||||
int size = get_le32( header_.data_size );
|
||||
int const rom_max = 0x100000;
|
||||
if ( (unsigned) addr >= (unsigned) rom_max )
|
||||
{
|
||||
set_warning( "Invalid address" );
|
||||
addr &= rom_max - 1;
|
||||
}
|
||||
if ( (unsigned) (addr + size) > (unsigned) rom_max )
|
||||
set_warning( "Invalid size" );
|
||||
|
||||
if ( size != rom.file_size() )
|
||||
{
|
||||
if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) )
|
||||
set_warning( "Multiple DATA not supported" );
|
||||
else if ( size < rom.file_size() )
|
||||
set_warning( "Extra file data" );
|
||||
else
|
||||
set_warning( "Missing file data" );
|
||||
}
|
||||
|
||||
rom.set_addr( addr );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Hes_Core::recalc_timer_load()
|
||||
{
|
||||
timer.load = timer.raw_load * timer_base + 1;
|
||||
}
|
||||
|
||||
void Hes_Core::set_tempo( double t )
|
||||
{
|
||||
play_period = (time_t) (period_60hz / t);
|
||||
timer_base = (int) (1024 / t);
|
||||
recalc_timer_load();
|
||||
}
|
||||
|
||||
blargg_err_t Hes_Core::start_track( int track )
|
||||
{
|
||||
memset( ram, 0, sizeof ram ); // some HES music relies on zero fill
|
||||
memset( sgx, 0, sizeof sgx );
|
||||
|
||||
apu_.reset();
|
||||
adpcm_.reset();
|
||||
cpu.reset();
|
||||
|
||||
for ( int i = 0; i < (int) sizeof header_.banks; i++ )
|
||||
set_mmr( i, header_.banks [i] );
|
||||
set_mmr( cpu.page_count, 0xFF ); // unmapped beyond end of address space
|
||||
|
||||
irq.disables = timer_mask | vdp_mask;
|
||||
irq.timer = cpu.future_time;
|
||||
irq.vdp = cpu.future_time;
|
||||
|
||||
timer.enabled = false;
|
||||
timer.raw_load = 0x80;
|
||||
timer.count = timer.load;
|
||||
timer.fired = false;
|
||||
timer.last_time = 0;
|
||||
|
||||
vdp.latch = 0;
|
||||
vdp.control = 0;
|
||||
vdp.next_vbl = 0;
|
||||
|
||||
ram [0x1FF] = (idle_addr - 1) >> 8;
|
||||
ram [0x1FE] = (idle_addr - 1) & 0xFF;
|
||||
cpu.r.sp = 0xFD;
|
||||
cpu.r.pc = get_le16( header_.init_addr );
|
||||
cpu.r.a = track;
|
||||
|
||||
recalc_timer_load();
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
// Hardware
|
||||
|
||||
void Hes_Core::run_until( time_t present )
|
||||
{
|
||||
while ( vdp.next_vbl < present )
|
||||
vdp.next_vbl += play_period;
|
||||
|
||||
time_t elapsed = present - timer.last_time;
|
||||
if ( elapsed > 0 )
|
||||
{
|
||||
if ( timer.enabled )
|
||||
{
|
||||
timer.count -= elapsed;
|
||||
if ( timer.count <= 0 )
|
||||
timer.count += timer.load;
|
||||
}
|
||||
timer.last_time = present;
|
||||
}
|
||||
}
|
||||
|
||||
void Hes_Core::write_vdp( int addr, int data )
|
||||
{
|
||||
switch ( addr )
|
||||
{
|
||||
case 0:
|
||||
vdp.latch = data & 0x1F;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if ( vdp.latch == 5 )
|
||||
{
|
||||
if ( data & 0x04 )
|
||||
set_warning( "Scanline interrupt unsupported" );
|
||||
run_until( cpu.time() );
|
||||
vdp.control = data;
|
||||
irq_changed();
|
||||
}
|
||||
else
|
||||
{
|
||||
dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data );
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Hes_Core::write_mem_( addr_t addr, int data )
|
||||
{
|
||||
time_t time = cpu.time();
|
||||
if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )
|
||||
{
|
||||
// Avoid going way past end when a long block xfer is writing to I/O space.
|
||||
// Not a problem for other registers below because they don't write to
|
||||
// Blip_Buffer.
|
||||
time_t t = min( time, cpu.end_time() + 8 );
|
||||
apu_.write_data( t, addr, data );
|
||||
return;
|
||||
}
|
||||
if ( (unsigned) (addr - adpcm_.io_addr) < adpcm_.io_size )
|
||||
{
|
||||
time_t t = min( time, cpu.end_time() + 6 );
|
||||
adpcm_.write_data( t, addr, data );
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x0000:
|
||||
case 0x0002:
|
||||
case 0x0003:
|
||||
write_vdp( addr, data );
|
||||
return;
|
||||
|
||||
case 0x0C00: {
|
||||
run_until( time );
|
||||
timer.raw_load = (data & 0x7F) + 1;
|
||||
recalc_timer_load();
|
||||
timer.count = timer.load;
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0C01:
|
||||
data &= 1;
|
||||
if ( timer.enabled == data )
|
||||
return;
|
||||
run_until( time );
|
||||
timer.enabled = data;
|
||||
if ( data )
|
||||
timer.count = timer.load;
|
||||
break;
|
||||
|
||||
case 0x1402:
|
||||
run_until( time );
|
||||
irq.disables = data;
|
||||
if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values
|
||||
dprintf( "Int mask: $%02X\n", data );
|
||||
break;
|
||||
|
||||
case 0x1403:
|
||||
run_until( time );
|
||||
if ( timer.enabled )
|
||||
timer.count = timer.load;
|
||||
timer.fired = false;
|
||||
break;
|
||||
|
||||
#ifndef NDEBUG
|
||||
case 0x1000: // I/O port
|
||||
case 0x0402: // palette
|
||||
case 0x0403:
|
||||
case 0x0404:
|
||||
case 0x0405:
|
||||
return;
|
||||
|
||||
default:
|
||||
dprintf( "unmapped write $%04X <- $%02X\n", addr, data );
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
irq_changed();
|
||||
}
|
||||
|
||||
int Hes_Core::read_mem_( addr_t addr )
|
||||
{
|
||||
time_t time = cpu.time();
|
||||
addr &= cpu.page_size - 1;
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x0000:
|
||||
if ( irq.vdp > time )
|
||||
return 0;
|
||||
irq.vdp = cpu.future_time;
|
||||
run_until( time );
|
||||
irq_changed();
|
||||
return 0x20;
|
||||
|
||||
case 0x0002:
|
||||
case 0x0003:
|
||||
dprintf( "VDP read not supported: %d\n", addr );
|
||||
return 0;
|
||||
|
||||
case 0x0C01:
|
||||
//return timer.enabled; // TODO: remove?
|
||||
case 0x0C00:
|
||||
run_until( time );
|
||||
dprintf( "Timer count read\n" );
|
||||
return (unsigned) (timer.count - 1) / timer_base;
|
||||
|
||||
case 0x1402:
|
||||
return irq.disables;
|
||||
|
||||
case 0x1403:
|
||||
{
|
||||
int status = 0;
|
||||
if ( irq.timer <= time ) status |= timer_mask;
|
||||
if ( irq.vdp <= time ) status |= vdp_mask;
|
||||
return status;
|
||||
}
|
||||
|
||||
case 0x180A:
|
||||
case 0x180B:
|
||||
case 0x180C:
|
||||
case 0x180D:
|
||||
return adpcm_.read_data( time, addr );
|
||||
|
||||
#ifndef NDEBUG
|
||||
case 0x1000: // I/O port
|
||||
//case 0x180C: // CD-ROM
|
||||
//case 0x180D:
|
||||
break;
|
||||
|
||||
default:
|
||||
dprintf( "unmapped read $%04X\n", addr );
|
||||
#endif
|
||||
}
|
||||
|
||||
return unmapped;
|
||||
}
|
||||
|
||||
void Hes_Core::irq_changed()
|
||||
{
|
||||
time_t present = cpu.time();
|
||||
|
||||
if ( irq.timer > present )
|
||||
{
|
||||
irq.timer = cpu.future_time;
|
||||
if ( timer.enabled && !timer.fired )
|
||||
irq.timer = present + timer.count;
|
||||
}
|
||||
|
||||
if ( irq.vdp > present )
|
||||
{
|
||||
irq.vdp = cpu.future_time;
|
||||
if ( vdp.control & 0x08 )
|
||||
irq.vdp = vdp.next_vbl;
|
||||
}
|
||||
|
||||
time_t time = cpu.future_time;
|
||||
if ( !(irq.disables & timer_mask) ) time = irq.timer;
|
||||
if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp );
|
||||
|
||||
cpu.set_irq_time( time );
|
||||
}
|
||||
|
||||
int Hes_Core::cpu_done()
|
||||
{
|
||||
check( cpu.time() >= cpu.end_time() ||
|
||||
(!(cpu.r.flags & i_flag_mask) && cpu.time() >= cpu.irq_time()) );
|
||||
|
||||
if ( !(cpu.r.flags & i_flag_mask) )
|
||||
{
|
||||
time_t present = cpu.time();
|
||||
|
||||
if ( irq.timer <= present && !(irq.disables & timer_mask) )
|
||||
{
|
||||
timer.fired = true;
|
||||
irq.timer = cpu.future_time;
|
||||
irq_changed(); // overkill, but not worth writing custom code
|
||||
return 0x0A;
|
||||
}
|
||||
|
||||
if ( irq.vdp <= present && !(irq.disables & vdp_mask) )
|
||||
{
|
||||
// work around for bugs with music not acknowledging VDP
|
||||
//run_until( present );
|
||||
//irq.vdp = cpu.future_time;
|
||||
//irq_changed();
|
||||
return 0x08;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void adjust_time( Hes_Core::time_t& time, Hes_Core::time_t delta )
|
||||
{
|
||||
if ( time < Hes_Cpu::future_time )
|
||||
{
|
||||
time -= delta;
|
||||
if ( time < 0 )
|
||||
time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
blargg_err_t Hes_Core::end_frame( time_t duration )
|
||||
{
|
||||
if ( run_cpu( duration ) )
|
||||
set_warning( "Emulation error (illegal instruction)" );
|
||||
|
||||
check( cpu.time() >= duration );
|
||||
//check( time() - duration < 20 ); // Txx instruction could cause going way over
|
||||
|
||||
run_until( duration );
|
||||
|
||||
// end time frame
|
||||
timer.last_time -= duration;
|
||||
vdp.next_vbl -= duration;
|
||||
cpu.end_frame( duration );
|
||||
::adjust_time( irq.timer, duration );
|
||||
::adjust_time( irq.vdp, duration );
|
||||
apu_.end_frame( duration );
|
||||
adpcm_.end_frame( duration );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// TurboGrafx-16/PC Engine HES music file emulator core
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef HES_CORE_H
|
||||
#define HES_CORE_H
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
#include "Rom_Data.h"
|
||||
#include "Hes_Apu.h"
|
||||
#include "Hes_Apu_Adpcm.h"
|
||||
#include "Hes_Cpu.h"
|
||||
|
||||
class Hes_Core : public Gme_Loader {
|
||||
public:
|
||||
|
||||
// HES file header
|
||||
enum { info_offset = 0x20 };
|
||||
struct header_t
|
||||
{
|
||||
enum { size = 0x20 };
|
||||
|
||||
byte tag [4];
|
||||
byte vers;
|
||||
byte first_track;
|
||||
byte init_addr [2];
|
||||
byte banks [8];
|
||||
byte data_tag [4];
|
||||
byte data_size [4];
|
||||
byte addr [4];
|
||||
byte unused [4];
|
||||
|
||||
// True if header has valid file signature
|
||||
bool valid_tag() const;
|
||||
};
|
||||
|
||||
// Header for currently loaded file
|
||||
header_t const& header() const { return header_; }
|
||||
|
||||
// Pointer to ROM data, for getting track information from
|
||||
byte const* data() const { return rom.begin(); }
|
||||
int data_size() const { return rom.file_size(); }
|
||||
|
||||
// Adjusts rate play routine is called at, where 1.0 is normal.
|
||||
// Can be changed while track is playing.
|
||||
void set_tempo( double );
|
||||
|
||||
// Sound chip
|
||||
Hes_Apu& apu() { return apu_; }
|
||||
|
||||
Hes_Apu_Adpcm& adpcm() { return adpcm_; }
|
||||
|
||||
// Starts track
|
||||
blargg_err_t start_track( int );
|
||||
|
||||
// Ends time frame at time t
|
||||
typedef int time_t;
|
||||
blargg_err_t end_frame( time_t );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Hes_Core();
|
||||
~Hes_Core();
|
||||
virtual void unload();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
|
||||
private:
|
||||
enum { idle_addr = 0x1FFF };
|
||||
|
||||
typedef int addr_t;
|
||||
Hes_Cpu cpu;
|
||||
Rom_Data rom;
|
||||
header_t header_;
|
||||
time_t play_period;
|
||||
int timer_base;
|
||||
|
||||
struct {
|
||||
time_t last_time;
|
||||
int count;
|
||||
int load;
|
||||
int raw_load;
|
||||
byte enabled;
|
||||
byte fired;
|
||||
} timer;
|
||||
|
||||
struct {
|
||||
time_t next_vbl;
|
||||
byte latch;
|
||||
byte control;
|
||||
} vdp;
|
||||
|
||||
struct {
|
||||
time_t timer;
|
||||
time_t vdp;
|
||||
byte disables;
|
||||
} irq;
|
||||
|
||||
void recalc_timer_load();
|
||||
|
||||
// large items
|
||||
byte* write_pages [Hes_Cpu::page_count + 1]; // 0 if unmapped or I/O space
|
||||
Hes_Apu apu_;
|
||||
Hes_Apu_Adpcm adpcm_;
|
||||
byte ram [Hes_Cpu::page_size];
|
||||
byte sgx [3 * Hes_Cpu::page_size + Hes_Cpu::cpu_padding];
|
||||
|
||||
void irq_changed();
|
||||
void run_until( time_t );
|
||||
bool run_cpu( time_t end );
|
||||
int read_mem_( addr_t );
|
||||
int read_mem( addr_t );
|
||||
void write_mem_( addr_t, int data );
|
||||
void write_mem( addr_t, int );
|
||||
void write_vdp( int addr, int data );
|
||||
void set_mmr( int reg, int bank );
|
||||
int cpu_done();
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,73 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "K051649_Emu.h"
|
||||
#include "k051649.h"
|
||||
|
||||
K051649_Emu::K051649_Emu() { SCC = 0; }
|
||||
|
||||
K051649_Emu::~K051649_Emu()
|
||||
{
|
||||
if ( SCC ) device_stop_k051649( SCC );
|
||||
}
|
||||
|
||||
int K051649_Emu::set_rate( int clock_rate )
|
||||
{
|
||||
if ( SCC )
|
||||
{
|
||||
device_stop_k051649( SCC );
|
||||
SCC = 0;
|
||||
}
|
||||
|
||||
SCC = device_start_k051649( clock_rate );
|
||||
if ( !SCC )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void K051649_Emu::reset()
|
||||
{
|
||||
device_reset_k051649( SCC );
|
||||
k051649_set_mute_mask( SCC, 0 );
|
||||
}
|
||||
|
||||
void K051649_Emu::write( int port, int offset, int data )
|
||||
{
|
||||
k051649_w( SCC, (port << 1) | 0x00, offset);
|
||||
k051649_w( SCC, (port << 1) | 0x01, data);
|
||||
}
|
||||
|
||||
void K051649_Emu::mute_voices( int mask )
|
||||
{
|
||||
k051649_set_mute_mask( SCC, mask );
|
||||
}
|
||||
|
||||
void K051649_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
k051649_update( SCC, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// K051649 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef K051649_EMU_H
|
||||
#define K051649_EMU_H
|
||||
|
||||
class K051649_Emu {
|
||||
void* SCC;
|
||||
public:
|
||||
K051649_Emu();
|
||||
~K051649_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 5 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int port, int offset, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "K053260_Emu.h"
|
||||
#include "k053260.h"
|
||||
|
||||
K053260_Emu::K053260_Emu() { chip = 0; }
|
||||
|
||||
K053260_Emu::~K053260_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_k053260( chip );
|
||||
}
|
||||
|
||||
int K053260_Emu::set_rate( int clock_rate )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_k053260( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_k053260( clock_rate );
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void K053260_Emu::reset()
|
||||
{
|
||||
device_reset_k053260( chip );
|
||||
k053260_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void K053260_Emu::write( int addr, int data )
|
||||
{
|
||||
k053260_w( chip, addr, data);
|
||||
}
|
||||
|
||||
void K053260_Emu::write_rom( int size, int start, int length, void * data )
|
||||
{
|
||||
k053260_write_rom( chip, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void K053260_Emu::mute_voices( int mask )
|
||||
{
|
||||
k053260_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void K053260_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
k053260_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// K053260 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef K053260_EMU_H
|
||||
#define K053260_EMU_H
|
||||
|
||||
class K053260_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
K053260_Emu();
|
||||
~K053260_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 5 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,79 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "K054539_Emu.h"
|
||||
#include "k054539.h"
|
||||
|
||||
K054539_Emu::K054539_Emu() { chip = 0; }
|
||||
|
||||
K054539_Emu::~K054539_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_k054539( chip );
|
||||
}
|
||||
|
||||
int K054539_Emu::set_rate( int clock_rate, int flags )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_k054539( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_k054539( clock_rate );
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
k054539_init_flags( chip, flags );
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void K054539_Emu::reset()
|
||||
{
|
||||
device_reset_k054539( chip );
|
||||
k054539_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void K054539_Emu::write( int addr, int data )
|
||||
{
|
||||
k054539_w( chip, addr, data);
|
||||
}
|
||||
|
||||
void K054539_Emu::write_rom( int size, int start, int length, void * data )
|
||||
{
|
||||
k054539_write_rom( chip, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void K054539_Emu::mute_voices( int mask )
|
||||
{
|
||||
k054539_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void K054539_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
k054539_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// K054539 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef K054539_EMU_H
|
||||
#define K054539_EMU_H
|
||||
|
||||
class K054539_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
K054539_Emu();
|
||||
~K054539_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock_rate, int flags );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 5 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,214 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Kss_Core.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size )
|
||||
{
|
||||
memset( unmapped_read, 0xFF, sizeof unmapped_read );
|
||||
}
|
||||
|
||||
Kss_Core::~Kss_Core() { }
|
||||
|
||||
void Kss_Core::unload()
|
||||
{
|
||||
rom.clear();
|
||||
}
|
||||
|
||||
static blargg_err_t check_kss_header( void const* header )
|
||||
{
|
||||
if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) )
|
||||
return blargg_err_file_type;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Kss_Core::load_( Data_Reader& in )
|
||||
{
|
||||
memset( &header_, 0, sizeof header_ );
|
||||
assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 );
|
||||
RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) );
|
||||
|
||||
RETURN_ERR( check_kss_header( header_.tag ) );
|
||||
|
||||
header_.last_track [0] = 255;
|
||||
if ( header_.tag [3] == 'C' )
|
||||
{
|
||||
if ( header_.extra_header )
|
||||
{
|
||||
header_.extra_header = 0;
|
||||
set_warning( "Unknown data in header" );
|
||||
}
|
||||
if ( header_.device_flags & ~0x0F )
|
||||
{
|
||||
header_.device_flags &= 0x0F;
|
||||
set_warning( "Unknown data in header" );
|
||||
}
|
||||
}
|
||||
else if ( header_.extra_header )
|
||||
{
|
||||
if ( header_.extra_header != header_.ext_size )
|
||||
{
|
||||
header_.extra_header = 0;
|
||||
set_warning( "Invalid extra_header_size" );
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy( header_.data_size, rom.begin(), header_.ext_size );
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
int ram_mode = header_.device_flags & 0x84; // MSX
|
||||
if ( header_.device_flags & 0x02 ) // SMS
|
||||
ram_mode = (header_.device_flags & 0x88);
|
||||
|
||||
if ( ram_mode )
|
||||
dprintf( "RAM not supported\n" ); // TODO: support
|
||||
}
|
||||
#endif
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Kss_Core::jsr( byte const (&addr) [2] )
|
||||
{
|
||||
ram [--cpu.r.sp] = idle_addr >> 8;
|
||||
ram [--cpu.r.sp] = idle_addr & 0xFF;
|
||||
cpu.r.pc = get_le16( addr );
|
||||
}
|
||||
|
||||
blargg_err_t Kss_Core::start_track( int track )
|
||||
{
|
||||
memset( ram, 0xC9, 0x4000 );
|
||||
memset( ram + 0x4000, 0, sizeof ram - 0x4000 );
|
||||
|
||||
// copy driver code to lo RAM
|
||||
static byte const bios [] = {
|
||||
0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG
|
||||
0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG
|
||||
};
|
||||
static byte const vectors [] = {
|
||||
0xC3, 0x01, 0x00, // $0093: WRTPSG vector
|
||||
0xC3, 0x09, 0x00, // $0096: RDPSG vector
|
||||
};
|
||||
memcpy( ram + 0x01, bios, sizeof bios );
|
||||
memcpy( ram + 0x93, vectors, sizeof vectors );
|
||||
|
||||
// copy non-banked data into RAM
|
||||
int load_addr = get_le16( header_.load_addr );
|
||||
int orig_load_size = get_le16( header_.load_size );
|
||||
int load_size = min( orig_load_size, rom.file_size() );
|
||||
load_size = min( load_size, (int) mem_size - load_addr );
|
||||
if ( load_size != orig_load_size )
|
||||
set_warning( "Excessive data size" );
|
||||
memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size );
|
||||
|
||||
rom.set_addr( -load_size - header_.extra_header );
|
||||
|
||||
// check available bank data
|
||||
int const bank_size = this->bank_size();
|
||||
int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size;
|
||||
bank_count = header_.bank_mode & 0x7F;
|
||||
if ( bank_count > max_banks )
|
||||
{
|
||||
bank_count = max_banks;
|
||||
set_warning( "Bank data missing" );
|
||||
}
|
||||
//dprintf( "load_size : $%X\n", load_size );
|
||||
//dprintf( "bank_size : $%X\n", bank_size );
|
||||
//dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F );
|
||||
|
||||
ram [idle_addr] = 0xFF;
|
||||
cpu.reset( unmapped_write, unmapped_read );
|
||||
cpu.map_mem( 0, mem_size, ram, ram );
|
||||
|
||||
cpu.r.sp = 0xF380;
|
||||
cpu.r.b.a = track;
|
||||
cpu.r.b.h = 0;
|
||||
next_play = play_period;
|
||||
gain_updated = false;
|
||||
jsr( header_.init_addr );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Kss_Core::set_bank( int logical, int physical )
|
||||
{
|
||||
int const bank_size = this->bank_size();
|
||||
|
||||
int addr = 0x8000;
|
||||
if ( logical && bank_size == 8 * 1024 )
|
||||
addr = 0xA000;
|
||||
|
||||
physical -= header_.first_bank;
|
||||
if ( (unsigned) physical >= (unsigned) bank_count )
|
||||
{
|
||||
byte* data = ram + addr;
|
||||
cpu.map_mem( addr, bank_size, data, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
int phys = physical * bank_size;
|
||||
for ( int offset = 0; offset < bank_size; offset += cpu.page_size )
|
||||
cpu.map_mem( addr + offset, cpu.page_size,
|
||||
unmapped_write, rom.at_addr( phys + offset ) );
|
||||
}
|
||||
}
|
||||
|
||||
void Kss_Core::cpu_out( time_t, addr_t addr, int data )
|
||||
{
|
||||
dprintf( "OUT $%04X,$%02X\n", addr, data );
|
||||
}
|
||||
|
||||
int Kss_Core::cpu_in( time_t, addr_t addr )
|
||||
{
|
||||
dprintf( "IN $%04X\n", addr );
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
blargg_err_t Kss_Core::end_frame( time_t end )
|
||||
{
|
||||
while ( cpu.time() < end )
|
||||
{
|
||||
time_t next = min( end, next_play );
|
||||
run_cpu( next );
|
||||
if ( cpu.r.pc == idle_addr )
|
||||
cpu.set_time( next );
|
||||
|
||||
if ( cpu.time() >= next_play )
|
||||
{
|
||||
next_play += play_period;
|
||||
if ( cpu.r.pc == idle_addr )
|
||||
{
|
||||
if ( !gain_updated )
|
||||
{
|
||||
gain_updated = true;
|
||||
update_gain();
|
||||
}
|
||||
|
||||
jsr( header_.play_addr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next_play -= end;
|
||||
check( next_play >= 0 );
|
||||
cpu.adjust_time( -end );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
// MSX computer KSS music file emulator
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef KSS_CORE_H
|
||||
#define KSS_CORE_H
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
#include "Rom_Data.h"
|
||||
#include "Z80_Cpu.h"
|
||||
|
||||
class Kss_Core : public Gme_Loader {
|
||||
public:
|
||||
// KSS file header
|
||||
struct header_t
|
||||
{
|
||||
enum { size = 0x20 };
|
||||
enum { base_size = 0x10 };
|
||||
enum { ext_size = size - base_size };
|
||||
|
||||
byte tag [4];
|
||||
byte load_addr [2];
|
||||
byte load_size [2];
|
||||
byte init_addr [2];
|
||||
byte play_addr [2];
|
||||
byte first_bank;
|
||||
byte bank_mode;
|
||||
byte extra_header;
|
||||
byte device_flags;
|
||||
|
||||
// KSSX extended data, if extra_header==0x10
|
||||
byte data_size [4];
|
||||
byte unused [4];
|
||||
byte first_track [2];
|
||||
byte last_track [2]; // if no extended data, we set this to 0xFF
|
||||
byte psg_vol;
|
||||
byte scc_vol;
|
||||
byte msx_music_vol;
|
||||
byte msx_audio_vol;
|
||||
};
|
||||
|
||||
// Header for currently loaded file
|
||||
header_t const& header() const { return header_; }
|
||||
|
||||
// ROM data
|
||||
Rom_Data const& rom_() const { return rom; }
|
||||
|
||||
typedef int time_t;
|
||||
void set_play_period( time_t p ) { play_period = p; }
|
||||
|
||||
blargg_err_t start_track( int );
|
||||
|
||||
blargg_err_t end_frame( time_t );
|
||||
|
||||
protected:
|
||||
typedef Z80_Cpu Kss_Cpu;
|
||||
Kss_Cpu cpu;
|
||||
|
||||
void set_bank( int logical, int physical );
|
||||
|
||||
typedef int addr_t;
|
||||
virtual void cpu_write( addr_t, int ) = 0;
|
||||
virtual int cpu_in( time_t, addr_t );
|
||||
virtual void cpu_out( time_t, addr_t, int );
|
||||
|
||||
// Called after one frame of emulation
|
||||
virtual void update_gain() = 0;
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Kss_Core();
|
||||
virtual ~Kss_Core();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
virtual void unload();
|
||||
|
||||
private:
|
||||
enum { idle_addr = 0xFFFF };
|
||||
|
||||
Rom_Data rom;
|
||||
header_t header_;
|
||||
bool gain_updated;
|
||||
int bank_count;
|
||||
time_t play_period;
|
||||
time_t next_play;
|
||||
|
||||
// large items
|
||||
enum { mem_size = 0x10000 };
|
||||
byte ram [mem_size + Kss_Cpu::cpu_padding];
|
||||
byte unmapped_read [0x100]; // TODO: why isn't this page_size?
|
||||
// because CPU can't read beyond this in last page? or because it will spill into unmapped_write?
|
||||
|
||||
byte unmapped_write [Kss_Cpu::page_size];
|
||||
|
||||
int bank_size() const { return (16 * 1024) >> (header_.bank_mode >> 7 & 1); }
|
||||
bool run_cpu( time_t end );
|
||||
void jsr( byte const (&addr) [2] );
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,280 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Fds_Apu.h"
|
||||
|
||||
/* Copyright (C) 2006 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const fract_range = 65536;
|
||||
|
||||
void Nes_Fds_Apu::reset()
|
||||
{
|
||||
memset( regs_, 0, sizeof regs_ );
|
||||
memset( mod_wave, 0, sizeof mod_wave );
|
||||
|
||||
last_time = 0;
|
||||
env_delay = 0;
|
||||
sweep_delay = 0;
|
||||
wave_pos = 0;
|
||||
last_amp = 0;
|
||||
wave_fract = fract_range;
|
||||
mod_fract = fract_range;
|
||||
mod_pos = 0;
|
||||
mod_write_pos = 0;
|
||||
|
||||
static byte const initial_regs [0x0B] = {
|
||||
0x80, // disable envelope
|
||||
0, 0, 0xC0, // disable wave and lfo
|
||||
0x80, // disable sweep
|
||||
0, 0, 0x80, // disable modulation
|
||||
0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
|
||||
};
|
||||
for ( int i = 0; i < (int) sizeof initial_regs; i++ )
|
||||
{
|
||||
// two writes to set both gain and period for envelope registers
|
||||
write_( io_addr + wave_size + i, 0 );
|
||||
write_( io_addr + wave_size + i, initial_regs [i] );
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Fds_Apu::write_( unsigned addr, int data )
|
||||
{
|
||||
unsigned reg = addr - io_addr;
|
||||
if ( reg < io_size )
|
||||
{
|
||||
if ( reg < wave_size )
|
||||
{
|
||||
if ( regs (0x4089) & 0x80 )
|
||||
regs_ [reg] = data & wave_sample_max;
|
||||
}
|
||||
else
|
||||
{
|
||||
regs_ [reg] = data;
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x4080:
|
||||
if ( data & 0x80 )
|
||||
env_gain = data & 0x3F;
|
||||
else
|
||||
env_speed = (data & 0x3F) + 1;
|
||||
break;
|
||||
|
||||
case 0x4084:
|
||||
if ( data & 0x80 )
|
||||
sweep_gain = data & 0x3F;
|
||||
else
|
||||
sweep_speed = (data & 0x3F) + 1;
|
||||
break;
|
||||
|
||||
case 0x4085:
|
||||
mod_pos = mod_write_pos;
|
||||
regs (0x4085) = data & 0x7F;
|
||||
break;
|
||||
|
||||
case 0x4088:
|
||||
if ( regs (0x4087) & 0x80 )
|
||||
{
|
||||
int pos = mod_write_pos;
|
||||
data &= 0x07;
|
||||
mod_wave [pos ] = data;
|
||||
mod_wave [pos + 1] = data;
|
||||
mod_write_pos = (pos + 2) & (wave_size - 1);
|
||||
mod_pos = (mod_pos + 2) & (wave_size - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Fds_Apu::set_tempo( double t )
|
||||
{
|
||||
lfo_tempo = lfo_base_tempo;
|
||||
if ( t != 1.0 )
|
||||
{
|
||||
lfo_tempo = int ((double) lfo_base_tempo / t + 0.5);
|
||||
if ( lfo_tempo <= 0 )
|
||||
lfo_tempo = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Fds_Apu::run_until( blip_time_t final_end_time )
|
||||
{
|
||||
int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082);
|
||||
Blip_Buffer* const output_ = this->output_;
|
||||
if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) )
|
||||
{
|
||||
output_->set_modified();
|
||||
|
||||
// master_volume
|
||||
#define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100
|
||||
static unsigned char const master_volumes [4] = {
|
||||
MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
|
||||
};
|
||||
int const master_volume = master_volumes [regs (0x4089) & 0x03];
|
||||
|
||||
// lfo_period
|
||||
blip_time_t lfo_period = regs (0x408A) * lfo_tempo;
|
||||
if ( regs (0x4083) & 0x40 )
|
||||
lfo_period = 0;
|
||||
|
||||
// sweep setup
|
||||
blip_time_t sweep_time = last_time + sweep_delay;
|
||||
blip_time_t const sweep_period = lfo_period * sweep_speed;
|
||||
if ( !sweep_period || regs (0x4084) & 0x80 )
|
||||
sweep_time = final_end_time;
|
||||
|
||||
// envelope setup
|
||||
blip_time_t env_time = last_time + env_delay;
|
||||
blip_time_t const env_period = lfo_period * env_speed;
|
||||
if ( !env_period || regs (0x4080) & 0x80 )
|
||||
env_time = final_end_time;
|
||||
|
||||
// modulation
|
||||
int mod_freq = 0;
|
||||
if ( !(regs (0x4087) & 0x80) )
|
||||
mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086);
|
||||
|
||||
blip_time_t end_time = last_time;
|
||||
do
|
||||
{
|
||||
// sweep
|
||||
if ( sweep_time <= end_time )
|
||||
{
|
||||
sweep_time += sweep_period;
|
||||
int mode = regs (0x4084) >> 5 & 2;
|
||||
int new_sweep_gain = sweep_gain + mode - 1;
|
||||
if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
|
||||
sweep_gain = new_sweep_gain;
|
||||
else
|
||||
regs (0x4084) |= 0x80; // optimization only
|
||||
}
|
||||
|
||||
// envelope
|
||||
if ( env_time <= end_time )
|
||||
{
|
||||
env_time += env_period;
|
||||
int mode = regs (0x4080) >> 5 & 2;
|
||||
int new_env_gain = env_gain + mode - 1;
|
||||
if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
|
||||
env_gain = new_env_gain;
|
||||
else
|
||||
regs (0x4080) |= 0x80; // optimization only
|
||||
}
|
||||
|
||||
// new end_time
|
||||
blip_time_t const start_time = end_time;
|
||||
end_time = final_end_time;
|
||||
if ( end_time > env_time ) end_time = env_time;
|
||||
if ( end_time > sweep_time ) end_time = sweep_time;
|
||||
|
||||
// frequency modulation
|
||||
int freq = wave_freq;
|
||||
if ( mod_freq )
|
||||
{
|
||||
// time of next modulation clock
|
||||
blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq;
|
||||
if ( end_time > mod_time )
|
||||
end_time = mod_time;
|
||||
|
||||
// run modulator up to next clock and save old sweep_bias
|
||||
int sweep_bias = regs (0x4085);
|
||||
mod_fract -= (end_time - start_time) * mod_freq;
|
||||
if ( mod_fract <= 0 )
|
||||
{
|
||||
mod_fract += fract_range;
|
||||
check( (unsigned) mod_fract <= fract_range );
|
||||
|
||||
static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
|
||||
int mod = mod_wave [mod_pos];
|
||||
mod_pos = (mod_pos + 1) & (wave_size - 1);
|
||||
int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
|
||||
if ( mod == 4 )
|
||||
new_sweep_bias = 0;
|
||||
regs (0x4085) = new_sweep_bias;
|
||||
}
|
||||
|
||||
// apply frequency modulation
|
||||
sweep_bias = (sweep_bias ^ 0x40) - 0x40;
|
||||
int factor = sweep_bias * sweep_gain;
|
||||
int extra = factor & 0x0F;
|
||||
factor >>= 4;
|
||||
if ( extra )
|
||||
{
|
||||
factor--;
|
||||
if ( sweep_bias >= 0 )
|
||||
factor += 3;
|
||||
}
|
||||
if ( factor > 193 ) factor -= 258;
|
||||
if ( factor < -64 ) factor += 256;
|
||||
freq += (freq * factor) >> 6;
|
||||
if ( freq <= 0 )
|
||||
continue;
|
||||
}
|
||||
|
||||
// wave
|
||||
int wave_fract = this->wave_fract;
|
||||
blip_time_t delay = (wave_fract + freq - 1) / freq;
|
||||
blip_time_t time = start_time + delay;
|
||||
|
||||
if ( time <= end_time )
|
||||
{
|
||||
// at least one wave clock within start_time...end_time
|
||||
|
||||
blip_time_t const min_delay = fract_range / freq;
|
||||
int wave_pos = this->wave_pos;
|
||||
|
||||
int volume = env_gain;
|
||||
if ( volume > vol_max )
|
||||
volume = vol_max;
|
||||
volume *= master_volume;
|
||||
|
||||
int const min_fract = min_delay * freq;
|
||||
|
||||
do
|
||||
{
|
||||
// clock wave
|
||||
int amp = regs_ [wave_pos] * volume;
|
||||
wave_pos = (wave_pos + 1) & (wave_size - 1);
|
||||
int delta = amp - last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
last_amp = amp;
|
||||
synth.offset_inline( time, delta, output_ );
|
||||
}
|
||||
|
||||
wave_fract += fract_range - delay * freq;
|
||||
check( unsigned (fract_range - wave_fract) < freq );
|
||||
|
||||
// delay until next clock
|
||||
delay = min_delay;
|
||||
if ( wave_fract > min_fract )
|
||||
delay++;
|
||||
check( delay && delay == (wave_fract + freq - 1) / freq );
|
||||
|
||||
time += delay;
|
||||
}
|
||||
while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
|
||||
|
||||
this->wave_pos = wave_pos;
|
||||
}
|
||||
this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
|
||||
check( this->wave_fract > 0 );
|
||||
}
|
||||
while ( end_time < final_end_time );
|
||||
|
||||
env_delay = env_time - final_end_time; check( env_delay >= 0 );
|
||||
sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
|
||||
}
|
||||
last_time = final_end_time;
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// NES FDS sound chip emulator
|
||||
|
||||
// $package
|
||||
#ifndef NES_FDS_APU_H
|
||||
#define NES_FDS_APU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
class Nes_Fds_Apu {
|
||||
public:
|
||||
// setup
|
||||
void set_tempo( double );
|
||||
enum { osc_count = 1 };
|
||||
void set_output( Blip_Buffer* buf );
|
||||
void volume( double );
|
||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||
|
||||
// emulation
|
||||
void reset();
|
||||
enum { io_addr = 0x4040 };
|
||||
enum { io_size = 0x53 };
|
||||
void write( blip_time_t time, unsigned addr, int data );
|
||||
int read( blip_time_t time, unsigned addr );
|
||||
void end_frame( blip_time_t );
|
||||
|
||||
public:
|
||||
Nes_Fds_Apu();
|
||||
void write_( unsigned addr, int data );
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
|
||||
void set_output( int index, Blip_Buffer* center,
|
||||
Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL );
|
||||
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; )
|
||||
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; )
|
||||
BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; )
|
||||
void osc_output( int, Blip_Buffer* );
|
||||
private:
|
||||
enum { wave_size = 0x40 };
|
||||
enum { master_vol_max = 10 };
|
||||
enum { vol_max = 0x20 };
|
||||
enum { wave_sample_max = 0x3F };
|
||||
|
||||
unsigned char regs_ [io_size];// last written value to registers
|
||||
|
||||
enum { lfo_base_tempo = 8 };
|
||||
int lfo_tempo; // normally 8; adjusted by set_tempo()
|
||||
|
||||
int env_delay;
|
||||
int env_speed;
|
||||
int env_gain;
|
||||
|
||||
int sweep_delay;
|
||||
int sweep_speed;
|
||||
int sweep_gain;
|
||||
|
||||
int wave_pos;
|
||||
int last_amp;
|
||||
blip_time_t wave_fract;
|
||||
|
||||
int mod_fract;
|
||||
int mod_pos;
|
||||
int mod_write_pos;
|
||||
unsigned char mod_wave [wave_size];
|
||||
|
||||
// synthesis
|
||||
blip_time_t last_time;
|
||||
Blip_Buffer* output_;
|
||||
Blip_Synth_Fast synth;
|
||||
|
||||
// allow access to registers by absolute address (i.e. 0x4080)
|
||||
unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; }
|
||||
|
||||
void run_until( blip_time_t );
|
||||
};
|
||||
|
||||
inline void Nes_Fds_Apu::volume( double v )
|
||||
{
|
||||
synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v );
|
||||
}
|
||||
|
||||
inline void Nes_Fds_Apu::set_output( Blip_Buffer* b )
|
||||
{
|
||||
output_ = b;
|
||||
}
|
||||
|
||||
inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* )
|
||||
{
|
||||
assert( (unsigned) i < osc_count );
|
||||
output_ = buf;
|
||||
}
|
||||
|
||||
inline void Nes_Fds_Apu::end_frame( blip_time_t end_time )
|
||||
{
|
||||
if ( end_time > last_time )
|
||||
run_until( end_time );
|
||||
last_time -= end_time;
|
||||
assert( last_time >= 0 );
|
||||
}
|
||||
|
||||
inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data )
|
||||
{
|
||||
run_until( time );
|
||||
write_( addr, data );
|
||||
}
|
||||
|
||||
inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr )
|
||||
{
|
||||
run_until( time );
|
||||
|
||||
int result = 0xFF;
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x4090:
|
||||
result = env_gain;
|
||||
break;
|
||||
|
||||
case 0x4092:
|
||||
result = sweep_gain;
|
||||
break;
|
||||
|
||||
default:
|
||||
unsigned i = addr - io_addr;
|
||||
if ( i < wave_size )
|
||||
result = regs_ [i];
|
||||
}
|
||||
|
||||
return result | 0x40;
|
||||
}
|
||||
|
||||
inline Nes_Fds_Apu::Nes_Fds_Apu()
|
||||
{
|
||||
lfo_tempo = lfo_base_tempo;
|
||||
set_output( NULL );
|
||||
volume( 1.0 );
|
||||
reset();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,70 @@
|
|||
// NES MMC5 sound chip emulator
|
||||
|
||||
// Nes_Snd_Emu $vers
|
||||
#ifndef NES_MMC5_APU_H
|
||||
#define NES_MMC5_APU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
class Nes_Mmc5_Apu : public Nes_Apu {
|
||||
public:
|
||||
enum { regs_addr = 0x5000 };
|
||||
enum { regs_size = 0x16 };
|
||||
|
||||
enum { osc_count = 3 };
|
||||
void write_register( blip_time_t, unsigned addr, int data );
|
||||
void set_output( Blip_Buffer* );
|
||||
void set_output( int index, Blip_Buffer* );
|
||||
|
||||
enum { exram_size = 1024 };
|
||||
unsigned char exram [exram_size];
|
||||
|
||||
BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; )
|
||||
BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; )
|
||||
};
|
||||
|
||||
inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b )
|
||||
{
|
||||
// in: square 1, square 2, PCM
|
||||
// out: square 1, square 2, skipped, skipped, PCM
|
||||
if ( i > 1 )
|
||||
i += 2;
|
||||
Nes_Apu::set_output( i, b );
|
||||
}
|
||||
|
||||
inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b )
|
||||
{
|
||||
set_output( 0, b );
|
||||
set_output( 1, b );
|
||||
set_output( 2, b );
|
||||
}
|
||||
|
||||
inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data )
|
||||
{
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x5015: // channel enables
|
||||
data &= 0x03; // enable the square waves only
|
||||
// fall through
|
||||
case 0x5000: // Square 1
|
||||
case 0x5002:
|
||||
case 0x5003:
|
||||
case 0x5004: // Square 2
|
||||
case 0x5006:
|
||||
case 0x5007:
|
||||
case 0x5011: // DAC
|
||||
Nes_Apu::write_register( time, addr - 0x1000, data );
|
||||
break;
|
||||
|
||||
case 0x5010: // some things write to this for some reason
|
||||
break;
|
||||
|
||||
#ifdef BLARGG_DEBUG_H
|
||||
default:
|
||||
dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,206 @@
|
|||
#include "Nes_Vrc7_Apu.h"
|
||||
|
||||
#include "ym2413.h"
|
||||
#include <string.h>
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const period = 36; // NES CPU clocks per FM clock
|
||||
|
||||
Nes_Vrc7_Apu::Nes_Vrc7_Apu()
|
||||
{
|
||||
opll = 0;
|
||||
}
|
||||
|
||||
blargg_err_t Nes_Vrc7_Apu::init()
|
||||
{
|
||||
CHECK_ALLOC( opll = ym2413_init( 3579545, 3579545 / 72, 1 ) );
|
||||
|
||||
set_output( 0 );
|
||||
volume( 1.0 );
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Nes_Vrc7_Apu::~Nes_Vrc7_Apu()
|
||||
{
|
||||
if ( opll )
|
||||
ym2413_shutdown( opll );
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf )
|
||||
{
|
||||
for ( int i = 0; i < osc_count; ++i )
|
||||
oscs [i].output = buf;
|
||||
output_changed();
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::output_changed()
|
||||
{
|
||||
mono.output = oscs [0].output;
|
||||
for ( int i = osc_count; --i; )
|
||||
{
|
||||
if ( mono.output != oscs [i].output )
|
||||
{
|
||||
mono.output = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( mono.output )
|
||||
{
|
||||
for ( int i = osc_count; --i; )
|
||||
{
|
||||
mono.last_amp += oscs [i].last_amp;
|
||||
oscs [i].last_amp = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::reset()
|
||||
{
|
||||
addr = 0;
|
||||
next_time = 0;
|
||||
mono.last_amp = 0;
|
||||
|
||||
for ( int i = osc_count; --i >= 0; )
|
||||
{
|
||||
Vrc7_Osc& osc = oscs [i];
|
||||
osc.last_amp = 0;
|
||||
for ( int j = 0; j < 3; ++j )
|
||||
osc.regs [j] = 0;
|
||||
}
|
||||
|
||||
ym2413_reset_chip( opll );
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::write_reg( int data )
|
||||
{
|
||||
addr = data;
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::write_data( blip_time_t time, int data )
|
||||
{
|
||||
int type = (addr >> 4) - 1;
|
||||
int chan = addr & 15;
|
||||
if ( (unsigned) type < 3 && chan < osc_count )
|
||||
oscs [chan].regs [type] = data;
|
||||
|
||||
if ( time > next_time )
|
||||
run_until( time );
|
||||
ym2413_write( opll, 0, addr );
|
||||
ym2413_write( opll, 1, data );
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::end_frame( blip_time_t time )
|
||||
{
|
||||
if ( time > next_time )
|
||||
run_until( time );
|
||||
|
||||
next_time -= time;
|
||||
assert( next_time >= 0 );
|
||||
|
||||
for ( int i = osc_count; --i >= 0; )
|
||||
{
|
||||
Blip_Buffer* output = oscs [i].output;
|
||||
if ( output )
|
||||
output->set_modified();
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const
|
||||
{
|
||||
out->latch = addr;
|
||||
out->delay = next_time;
|
||||
for ( int i = osc_count; --i >= 0; )
|
||||
{
|
||||
for ( int j = 0; j < 3; ++j )
|
||||
out->regs [i] [j] = oscs [i].regs [j];
|
||||
}
|
||||
memcpy( out->inst, ym2413_get_inst0( opll ), 8 );
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in )
|
||||
{
|
||||
assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 );
|
||||
|
||||
reset();
|
||||
next_time = in.delay;
|
||||
write_reg( in.latch );
|
||||
int i;
|
||||
for ( i = 0; i < osc_count; ++i )
|
||||
{
|
||||
for ( int j = 0; j < 3; ++j )
|
||||
oscs [i].regs [j] = in.regs [i] [j];
|
||||
}
|
||||
|
||||
for ( i = 0; i < 8; ++i )
|
||||
{
|
||||
ym2413_write( opll, 0, i );
|
||||
ym2413_write( opll, 1, in.inst [i] );
|
||||
}
|
||||
|
||||
for ( i = 0; i < 3; ++i )
|
||||
{
|
||||
for ( int j = 0; j < 6; ++j )
|
||||
{
|
||||
ym2413_write( opll, 0, 0x10 + i * 0x10 + j );
|
||||
ym2413_write( opll, 1, oscs [j].regs [i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nes_Vrc7_Apu::run_until( blip_time_t end_time )
|
||||
{
|
||||
require( end_time > next_time );
|
||||
|
||||
blip_time_t time = next_time;
|
||||
void* opll = this->opll; // cache
|
||||
Blip_Buffer* const mono_output = mono.output;
|
||||
if ( mono_output )
|
||||
{
|
||||
// optimal case
|
||||
do
|
||||
{
|
||||
ym2413_advance_lfo( opll );
|
||||
int amp = 0;
|
||||
for ( int i = 0; i < osc_count; i++ )
|
||||
amp += ym2413_calcch( opll, i );
|
||||
ym2413_advance( opll );
|
||||
int delta = amp - mono.last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
mono.last_amp = amp;
|
||||
synth.offset_inline( time, delta, mono_output );
|
||||
}
|
||||
time += period;
|
||||
}
|
||||
while ( time < end_time );
|
||||
}
|
||||
else
|
||||
{
|
||||
mono.last_amp = 0;
|
||||
do
|
||||
{
|
||||
ym2413_advance_lfo( opll );
|
||||
for ( int i = 0; i < osc_count; ++i )
|
||||
{
|
||||
Vrc7_Osc& osc = oscs [i];
|
||||
if ( osc.output )
|
||||
{
|
||||
int amp = ym2413_calcch( opll, i );
|
||||
int delta = amp - osc.last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
osc.last_amp = amp;
|
||||
synth.offset( time, delta, osc.output );
|
||||
}
|
||||
}
|
||||
}
|
||||
ym2413_advance( opll );
|
||||
time += period;
|
||||
}
|
||||
while ( time < end_time );
|
||||
}
|
||||
next_time = time;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// Konami VRC7 sound chip emulator
|
||||
|
||||
#ifndef NES_VRC7_APU_H
|
||||
#define NES_VRC7_APU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
struct vrc7_snapshot_t;
|
||||
|
||||
class Nes_Vrc7_Apu {
|
||||
public:
|
||||
blargg_err_t init();
|
||||
|
||||
// See Nes_Apu.h for reference
|
||||
void reset();
|
||||
void volume( double );
|
||||
void treble_eq( blip_eq_t const& );
|
||||
void set_output( Blip_Buffer* );
|
||||
enum { osc_count = 6 };
|
||||
void set_output( int index, Blip_Buffer* );
|
||||
void end_frame( blip_time_t );
|
||||
void save_snapshot( vrc7_snapshot_t* ) const;
|
||||
void load_snapshot( vrc7_snapshot_t const& );
|
||||
|
||||
void write_reg( int reg );
|
||||
void write_data( blip_time_t, int data );
|
||||
|
||||
public:
|
||||
Nes_Vrc7_Apu();
|
||||
~Nes_Vrc7_Apu();
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
private:
|
||||
// noncopyable
|
||||
Nes_Vrc7_Apu( const Nes_Vrc7_Apu& );
|
||||
Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& );
|
||||
|
||||
struct Vrc7_Osc
|
||||
{
|
||||
BOOST::uint8_t regs [3];
|
||||
Blip_Buffer* output;
|
||||
int last_amp;
|
||||
};
|
||||
|
||||
Vrc7_Osc oscs [osc_count];
|
||||
void* opll;
|
||||
int addr;
|
||||
blip_time_t next_time;
|
||||
struct {
|
||||
Blip_Buffer* output;
|
||||
int last_amp;
|
||||
} mono;
|
||||
|
||||
Blip_Synth_Fast synth;
|
||||
|
||||
void run_until( blip_time_t );
|
||||
void output_changed();
|
||||
};
|
||||
|
||||
struct vrc7_snapshot_t
|
||||
{
|
||||
BOOST::uint8_t latch;
|
||||
BOOST::uint8_t inst [8];
|
||||
BOOST::uint8_t regs [6] [3];
|
||||
BOOST::uint8_t delay;
|
||||
};
|
||||
|
||||
inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf )
|
||||
{
|
||||
assert( (unsigned) i < osc_count );
|
||||
oscs [i].output = buf;
|
||||
output_changed();
|
||||
}
|
||||
|
||||
// DB2LIN_AMP_BITS == 11, * 2
|
||||
inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); }
|
||||
|
||||
inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||
|
||||
#endif
|
|
@ -0,0 +1,302 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nsf_Core.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
#include "Nes_Namco_Apu.h"
|
||||
#include "Nes_Vrc6_Apu.h"
|
||||
#include "Nes_Fme7_Apu.h"
|
||||
#include "Nes_Fds_Apu.h"
|
||||
#include "Nes_Mmc5_Apu.h"
|
||||
#include "Nes_Vrc7_Apu.h"
|
||||
#endif
|
||||
|
||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Nsf_Core::Nsf_Core()
|
||||
{
|
||||
fds = NULL;
|
||||
fme7 = NULL;
|
||||
mmc5 = NULL;
|
||||
namco = NULL;
|
||||
vrc6 = NULL;
|
||||
vrc7 = NULL;
|
||||
}
|
||||
|
||||
Nsf_Core::~Nsf_Core()
|
||||
{
|
||||
unload();
|
||||
}
|
||||
|
||||
void Nsf_Core::unload()
|
||||
{
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
delete fds;
|
||||
fds = NULL;
|
||||
|
||||
delete fme7;
|
||||
fme7 = NULL;
|
||||
|
||||
delete namco;
|
||||
namco = NULL;
|
||||
|
||||
delete mmc5;
|
||||
mmc5 = NULL;
|
||||
|
||||
delete vrc6;
|
||||
vrc6 = NULL;
|
||||
|
||||
delete vrc7;
|
||||
vrc7 = NULL;
|
||||
#endif
|
||||
|
||||
Nsf_Impl::unload();
|
||||
}
|
||||
|
||||
void Nsf_Core::set_tempo( double t )
|
||||
{
|
||||
set_play_period( (int) (header().play_period() / t) );
|
||||
nes_apu()->set_tempo( t );
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
if ( fds )
|
||||
fds->set_tempo( t );
|
||||
#endif
|
||||
}
|
||||
|
||||
blargg_err_t Nsf_Core::post_load()
|
||||
{
|
||||
int chip_flags = header().chip_flags;
|
||||
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
if ( chip_flags & header_t::fds_mask )
|
||||
CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu );
|
||||
|
||||
if ( chip_flags & header_t::fme7_mask )
|
||||
CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu );
|
||||
|
||||
if ( chip_flags & header_t::mmc5_mask )
|
||||
CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu );
|
||||
|
||||
if ( chip_flags & header_t::namco_mask )
|
||||
CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu );
|
||||
|
||||
if ( chip_flags & header_t::vrc6_mask )
|
||||
CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu );
|
||||
|
||||
if ( chip_flags & header_t::vrc7_mask )
|
||||
{
|
||||
#if NSF_EMU_NO_VRC7
|
||||
chip_flags = ~chips_mask; // give warning rather than error
|
||||
#else
|
||||
CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu );
|
||||
RETURN_ERR( vrc7->init() );
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
set_tempo( 1.0 );
|
||||
|
||||
if ( chip_flags & ~chips_mask )
|
||||
set_warning( "Uses unsupported audio expansion hardware" );
|
||||
|
||||
return Nsf_Impl::post_load();
|
||||
}
|
||||
|
||||
int Nsf_Core::cpu_read( addr_t addr )
|
||||
{
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
{
|
||||
if ( addr == Nes_Namco_Apu::data_reg_addr && namco )
|
||||
return namco->read_data();
|
||||
|
||||
if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds )
|
||||
return fds->read( time(), addr );
|
||||
|
||||
int i = addr - 0x5C00;
|
||||
if ( (unsigned) i < mmc5->exram_size && mmc5 )
|
||||
return mmc5->exram [i];
|
||||
|
||||
int m = addr - 0x5205;
|
||||
if ( (unsigned) m < 2 && mmc5 )
|
||||
return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF;
|
||||
}
|
||||
#endif
|
||||
|
||||
return Nsf_Impl::cpu_read( addr );
|
||||
}
|
||||
|
||||
int Nsf_Core::unmapped_read( addr_t addr )
|
||||
{
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x2002:
|
||||
case 0x4016:
|
||||
case 0x4017:
|
||||
return addr >> 8;
|
||||
}
|
||||
|
||||
return Nsf_Impl::unmapped_read( addr );
|
||||
}
|
||||
|
||||
void Nsf_Core::cpu_write( addr_t addr, int data )
|
||||
{
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
{
|
||||
if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds )
|
||||
{
|
||||
fds->write( time(), addr, data );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( namco )
|
||||
{
|
||||
if ( addr == namco->addr_reg_addr )
|
||||
{
|
||||
namco->write_addr( data );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( addr == namco->data_reg_addr )
|
||||
{
|
||||
namco->write_data( time(), data );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( vrc6 )
|
||||
{
|
||||
int reg = addr & (vrc6->addr_step - 1);
|
||||
int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step;
|
||||
if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count )
|
||||
{
|
||||
vrc6->write_osc( time(), osc, reg, data );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( addr >= fme7->latch_addr && fme7 )
|
||||
{
|
||||
switch ( addr & fme7->addr_mask )
|
||||
{
|
||||
case Nes_Fme7_Apu::latch_addr:
|
||||
fme7->write_latch( data );
|
||||
return;
|
||||
|
||||
case Nes_Fme7_Apu::data_addr:
|
||||
fme7->write_data( time(), data );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( mmc5 )
|
||||
{
|
||||
if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size )
|
||||
{
|
||||
mmc5->write_register( time(), addr, data );
|
||||
return;
|
||||
}
|
||||
|
||||
int m = addr - 0x5205;
|
||||
if ( (unsigned) m < 2 )
|
||||
{
|
||||
mmc5_mul [m] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
int i = addr - 0x5C00;
|
||||
if ( (unsigned) i < mmc5->exram_size )
|
||||
{
|
||||
mmc5->exram [i] = data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( vrc7 )
|
||||
{
|
||||
if ( addr == 0x9010 )
|
||||
{
|
||||
vrc7->write_reg( data );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( (unsigned) (addr - 0x9028) <= 0x08 )
|
||||
{
|
||||
vrc7->write_data( time(), data );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return Nsf_Impl::cpu_write( addr, data );
|
||||
}
|
||||
|
||||
void Nsf_Core::unmapped_write( addr_t addr, int data )
|
||||
{
|
||||
switch ( addr )
|
||||
{
|
||||
case 0x8000: // some write to $8000 and $8001 repeatedly
|
||||
case 0x8001:
|
||||
case 0x4800: // probably namco sound mistakenly turned on in MCK
|
||||
case 0xF800:
|
||||
case 0xFFF8: // memory mapper?
|
||||
return;
|
||||
}
|
||||
|
||||
if ( mmc5 && addr == 0x5115 ) return;
|
||||
|
||||
// FDS memory
|
||||
if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return;
|
||||
|
||||
Nsf_Impl::unmapped_write( addr, data );
|
||||
}
|
||||
|
||||
blargg_err_t Nsf_Core::start_track( int track )
|
||||
{
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
if ( mmc5 )
|
||||
{
|
||||
mmc5_mul [0] = 0;
|
||||
mmc5_mul [1] = 0;
|
||||
memset( mmc5->exram, 0, mmc5->exram_size );
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
if ( fds ) fds ->reset();
|
||||
if ( fme7 ) fme7 ->reset();
|
||||
if ( mmc5 ) mmc5 ->reset();
|
||||
if ( namco ) namco->reset();
|
||||
if ( vrc6 ) vrc6 ->reset();
|
||||
if ( vrc7 ) vrc7 ->reset();
|
||||
#endif
|
||||
|
||||
return Nsf_Impl::start_track( track );
|
||||
}
|
||||
|
||||
void Nsf_Core::end_frame( time_t end )
|
||||
{
|
||||
Nsf_Impl::end_frame( end );
|
||||
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
if ( fds ) fds ->end_frame( end );
|
||||
if ( fme7 ) fme7 ->end_frame( end );
|
||||
if ( mmc5 ) mmc5 ->end_frame( end );
|
||||
if ( namco ) namco->end_frame( end );
|
||||
if ( vrc6 ) vrc6 ->end_frame( end );
|
||||
if ( vrc7 ) vrc7 ->end_frame( end );
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Loads NSF file and emulates CPU and sound chips
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef NSF_CORE_H
|
||||
#define NSF_CORE_H
|
||||
|
||||
#include "Nsf_Impl.h"
|
||||
|
||||
class Nes_Namco_Apu;
|
||||
class Nes_Vrc6_Apu;
|
||||
class Nes_Fme7_Apu;
|
||||
class Nes_Mmc5_Apu;
|
||||
class Nes_Vrc7_Apu;
|
||||
class Nes_Fds_Apu;
|
||||
|
||||
class Nsf_Core : public Nsf_Impl {
|
||||
public:
|
||||
|
||||
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
||||
// Loading a file resets tempo to 1.0.
|
||||
void set_tempo( double );
|
||||
|
||||
// Pointer to sound chip, or NULL if not used by current file.
|
||||
// Must be assigned to a Blip_Buffer to get any sound.
|
||||
Nes_Fds_Apu * fds_apu () { return fds; }
|
||||
Nes_Fme7_Apu * fme7_apu () { return fme7; }
|
||||
Nes_Mmc5_Apu * mmc5_apu () { return mmc5; }
|
||||
Nes_Namco_Apu* namco_apu() { return namco; }
|
||||
Nes_Vrc6_Apu * vrc6_apu () { return vrc6; }
|
||||
Nes_Vrc7_Apu * vrc7_apu () { return vrc7; }
|
||||
|
||||
// Mask for which chips are supported
|
||||
#if NSF_EMU_APU_ONLY
|
||||
enum { chips_mask = 0 };
|
||||
#else
|
||||
enum { chips_mask = header_t::all_mask };
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual int unmapped_read( addr_t );
|
||||
virtual void unmapped_write( addr_t, int data );
|
||||
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Nsf_Core();
|
||||
~Nsf_Core();
|
||||
virtual void unload();
|
||||
virtual blargg_err_t start_track( int );
|
||||
virtual void end_frame( time_t );
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t post_load();
|
||||
virtual int cpu_read( addr_t );
|
||||
virtual void cpu_write( addr_t, int );
|
||||
|
||||
private:
|
||||
byte mmc5_mul [2];
|
||||
|
||||
Nes_Fds_Apu* fds;
|
||||
Nes_Fme7_Apu* fme7;
|
||||
Nes_Mmc5_Apu* mmc5;
|
||||
Nes_Namco_Apu* namco;
|
||||
Nes_Vrc6_Apu* vrc6;
|
||||
Nes_Vrc7_Apu* vrc7;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,116 @@
|
|||
// Normal CPU for NSF emulator
|
||||
|
||||
// $package. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nsf_Impl.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
#ifdef BLARGG_DEBUG_H
|
||||
//#define CPU_LOG_START 1000000
|
||||
//#include "nes_cpu_log.h"
|
||||
#undef LOG_MEM
|
||||
#endif
|
||||
|
||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#ifndef LOG_MEM
|
||||
#define LOG_MEM( addr, str, data ) data
|
||||
#endif
|
||||
|
||||
int Nsf_Impl::read_mem( addr_t addr )
|
||||
{
|
||||
int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around
|
||||
if ( addr & 0xE000 )
|
||||
{
|
||||
result = *cpu.get_code( addr );
|
||||
if ( addr < sram_addr )
|
||||
{
|
||||
if ( addr == apu.status_addr )
|
||||
result = apu.read_status( time() );
|
||||
else
|
||||
result = cpu_read( addr );
|
||||
}
|
||||
}
|
||||
return LOG_MEM( addr, ">", result );
|
||||
}
|
||||
|
||||
void Nsf_Impl::write_mem( addr_t addr, int data )
|
||||
{
|
||||
(void) LOG_MEM( addr, "<", data );
|
||||
|
||||
int offset = addr - sram_addr;
|
||||
if ( (unsigned) offset < sram_size )
|
||||
{
|
||||
sram() [offset] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
// after sram because CPU handles most low_ram accesses internally already
|
||||
int temp = addr & (low_ram_size-1); // also handles wrap-around
|
||||
if ( !(addr & 0xE000) )
|
||||
{
|
||||
low_ram [temp] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
int bank = addr - banks_addr;
|
||||
if ( (unsigned) bank < bank_count )
|
||||
{
|
||||
write_bank( bank, data );
|
||||
}
|
||||
else if ( (unsigned) (addr - apu.io_addr) < apu.io_size )
|
||||
{
|
||||
apu.write_register( time(), addr, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
// 0x8000-0xDFFF is writable
|
||||
int i = addr - 0x8000;
|
||||
if ( (unsigned) i < fdsram_size && fds_enabled() )
|
||||
fdsram() [i] = data;
|
||||
else
|
||||
#endif
|
||||
cpu_write( addr, data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] ))
|
||||
#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data ))
|
||||
|
||||
#define CAN_WRITE_FAST( addr ) (addr < low_ram_size)
|
||||
#define WRITE_FAST WRITE_LOW
|
||||
|
||||
// addr < 0x2000 || addr >= 0x8000
|
||||
#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000)
|
||||
#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) ))
|
||||
|
||||
#define READ_MEM( addr ) read_mem( addr )
|
||||
#define WRITE_MEM( addr, data ) write_mem( addr, data )
|
||||
|
||||
#define CPU cpu
|
||||
|
||||
#define CPU_BEGIN \
|
||||
bool Nsf_Impl::run_cpu_until( time_t end )\
|
||||
{\
|
||||
cpu.set_end_time( end );\
|
||||
if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\
|
||||
{
|
||||
#include "Nes_Cpu_run.h"
|
||||
}
|
||||
return cpu.time_past_end() < 0;
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nsf_Impl.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"
|
||||
|
||||
// number of frames until play interrupts init
|
||||
int const initial_play_delay = 7; // KikiKaikai needed this to work
|
||||
int const bank_size = 0x1000;
|
||||
int const rom_addr = 0x8000;
|
||||
|
||||
int Nsf_Impl::read_code( addr_t addr ) const
|
||||
{
|
||||
return *cpu.get_code( addr );
|
||||
}
|
||||
|
||||
int Nsf_Impl::pcm_read( void* self, int addr )
|
||||
{
|
||||
return STATIC_CAST(Nsf_Impl*,self)->read_code( addr );
|
||||
}
|
||||
|
||||
Nsf_Impl::Nsf_Impl() : rom( bank_size ), enable_w4011( true )
|
||||
{
|
||||
apu.dmc_reader( pcm_read, this );
|
||||
assert( offsetof (header_t,unused [4]) == header_t::size );
|
||||
}
|
||||
|
||||
void Nsf_Impl::unload()
|
||||
{
|
||||
rom.clear();
|
||||
high_ram.clear();
|
||||
Gme_Loader::unload();
|
||||
}
|
||||
|
||||
Nsf_Impl::~Nsf_Impl() { unload(); }
|
||||
|
||||
bool nsf_header_t::valid_tag() const
|
||||
{
|
||||
return 0 == memcmp( tag, "NESM\x1A", 5 );
|
||||
}
|
||||
|
||||
double nsf_header_t::clock_rate() const
|
||||
{
|
||||
return pal_only() ? 1662607.125 : 1789772.727272727;
|
||||
}
|
||||
|
||||
int nsf_header_t::play_period() const
|
||||
{
|
||||
// NTSC
|
||||
int clocks = 29780;
|
||||
int value = 0x411A;
|
||||
byte const* rate_ptr = ntsc_speed;
|
||||
|
||||
// PAL
|
||||
if ( pal_only() )
|
||||
{
|
||||
clocks = 33247;
|
||||
value = 0x4E20;
|
||||
rate_ptr = pal_speed;
|
||||
}
|
||||
|
||||
// Default rate
|
||||
int rate = get_le16( rate_ptr );
|
||||
if ( rate == 0 )
|
||||
rate = value;
|
||||
|
||||
// Custom rate
|
||||
if ( rate != value )
|
||||
clocks = (int) (rate * clock_rate() * (1.0/1000000.0));
|
||||
|
||||
return clocks;
|
||||
}
|
||||
|
||||
// Gets address, given pointer to it in file header. If zero, returns rom_addr.
|
||||
Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] )
|
||||
{
|
||||
addr_t addr = get_le16( in );
|
||||
if ( addr == 0 )
|
||||
addr = rom_addr;
|
||||
return addr;
|
||||
}
|
||||
|
||||
blargg_err_t Nsf_Impl::load_( Data_Reader& in )
|
||||
{
|
||||
// pad ROM data with 0
|
||||
RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
|
||||
|
||||
if ( !header_.valid_tag() )
|
||||
return blargg_err_file_type;
|
||||
|
||||
RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) );
|
||||
|
||||
addr_t load_addr = get_addr( header_.load_addr );
|
||||
if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) )
|
||||
set_warning( "Load address is too low" );
|
||||
|
||||
rom.set_addr( load_addr % bank_size );
|
||||
|
||||
if ( header_.vers != 1 )
|
||||
set_warning( "Unknown file version" );
|
||||
|
||||
set_play_period( header_.play_period() );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Nsf_Impl::write_bank( int bank, int data )
|
||||
{
|
||||
// Find bank in ROM
|
||||
int offset = rom.mask_addr( data * bank_size );
|
||||
if ( offset >= rom.size() )
|
||||
special_event( "invalid bank" );
|
||||
void const* rom_data = rom.at_addr( offset );
|
||||
|
||||
#if !NSF_EMU_APU_ONLY
|
||||
if ( bank < bank_count - fds_banks && fds_enabled() )
|
||||
{
|
||||
// TODO: FDS bank switching is kind of hacky, might need to
|
||||
// treat ROM as RAM so changes won't get lost when switching.
|
||||
byte* out = sram();
|
||||
if ( bank >= fds_banks )
|
||||
{
|
||||
out = fdsram();
|
||||
bank -= fds_banks;
|
||||
}
|
||||
memcpy( &out [bank * bank_size], rom_data, bank_size );
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( bank >= fds_banks )
|
||||
cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data );
|
||||
}
|
||||
|
||||
void Nsf_Impl::map_memory()
|
||||
{
|
||||
// Map standard things
|
||||
cpu.reset( unmapped_code() );
|
||||
cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times
|
||||
cpu.map_code( sram_addr, sram_size, sram() );
|
||||
|
||||
// Determine initial banks
|
||||
byte banks [bank_count];
|
||||
static byte const zero_banks [sizeof header_.banks] = { 0 };
|
||||
if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) )
|
||||
{
|
||||
banks [0] = header_.banks [6];
|
||||
banks [1] = header_.banks [7];
|
||||
memcpy( banks + fds_banks, header_.banks, sizeof header_.banks );
|
||||
}
|
||||
else
|
||||
{
|
||||
// No initial banks, so assign them based on load_addr
|
||||
int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size;
|
||||
unsigned total_banks = rom.size() / bank_size;
|
||||
for ( int i = bank_count; --i >= 0; )
|
||||
{
|
||||
int bank = i - first_bank;
|
||||
if ( (unsigned) bank >= total_banks )
|
||||
bank = 0;
|
||||
banks [i] = bank;
|
||||
}
|
||||
}
|
||||
|
||||
// Map banks
|
||||
for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i )
|
||||
write_bank( i, banks [i] );
|
||||
|
||||
// Map FDS RAM
|
||||
if ( fds_enabled() )
|
||||
cpu.map_code( rom_addr, fdsram_size, fdsram() );
|
||||
}
|
||||
|
||||
inline void Nsf_Impl::push_byte( int b )
|
||||
{
|
||||
low_ram [0x100 + cpu.r.sp--] = b;
|
||||
}
|
||||
|
||||
// Jumps to routine, given pointer to address in file header. Pushes idle_addr
|
||||
// as return address, NOT old PC.
|
||||
void Nsf_Impl::jsr_then_stop( byte const addr [] )
|
||||
{
|
||||
cpu.r.pc = get_addr( addr );
|
||||
push_byte( (idle_addr - 1) >> 8 );
|
||||
push_byte( (idle_addr - 1) );
|
||||
}
|
||||
|
||||
blargg_err_t Nsf_Impl::start_track( int track )
|
||||
{
|
||||
int speed_flags = 0;
|
||||
#if NSF_EMU_EXTRA_FLAGS
|
||||
speed_flags = header().speed_flags;
|
||||
#endif
|
||||
|
||||
apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 );
|
||||
apu.enable_w4011_( enable_w4011 );
|
||||
apu.write_register( 0, 0x4015, 0x0F );
|
||||
apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 );
|
||||
|
||||
// Clear memory
|
||||
memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size );
|
||||
memset( low_ram, 0, low_ram_size );
|
||||
memset( sram(), 0, sram_size );
|
||||
|
||||
map_memory();
|
||||
|
||||
// Arrange time of first call to play routine
|
||||
play_extra = 0;
|
||||
next_play = play_period;
|
||||
|
||||
play_delay = initial_play_delay;
|
||||
saved_state.pc = idle_addr;
|
||||
|
||||
// Setup for call to init routine
|
||||
cpu.r.a = track;
|
||||
cpu.r.x = header_.pal_only();
|
||||
cpu.r.sp = 0xFF;
|
||||
jsr_then_stop( header_.init_addr );
|
||||
if ( cpu.r.pc < get_addr( header_.load_addr ) )
|
||||
set_warning( "Init address < load address" );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Nsf_Impl::unmapped_write( addr_t addr, int data )
|
||||
{
|
||||
dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data );
|
||||
}
|
||||
|
||||
int Nsf_Impl::unmapped_read( addr_t addr )
|
||||
{
|
||||
dprintf( "Unmapped read $%04X\n", (int) addr );
|
||||
return addr >> 8;
|
||||
}
|
||||
|
||||
void Nsf_Impl::special_event( const char str [] )
|
||||
{
|
||||
dprintf( "%s\n", str );
|
||||
}
|
||||
|
||||
void Nsf_Impl::run_once( time_t end )
|
||||
{
|
||||
// Emulate until next play call if possible
|
||||
if ( run_cpu_until( min( next_play, end ) ) )
|
||||
{
|
||||
// Halt instruction encountered
|
||||
|
||||
if ( cpu.r.pc != idle_addr )
|
||||
{
|
||||
special_event( "illegal instruction" );
|
||||
cpu.count_error();
|
||||
cpu.set_time( cpu.end_time() );
|
||||
return;
|
||||
}
|
||||
|
||||
// Init/play routine returned
|
||||
play_delay = 1; // play can now be called regularly
|
||||
|
||||
if ( saved_state.pc == idle_addr )
|
||||
{
|
||||
// nothing to run
|
||||
time_t t = cpu.end_time();
|
||||
if ( cpu.time() < t )
|
||||
cpu.set_time( t );
|
||||
}
|
||||
else
|
||||
{
|
||||
// continue init routine that was interrupted by play routine
|
||||
cpu.r = saved_state;
|
||||
saved_state.pc = idle_addr;
|
||||
}
|
||||
}
|
||||
|
||||
if ( time() >= next_play )
|
||||
{
|
||||
// Calculate time of next call to play routine
|
||||
play_extra ^= 1; // extra clock every other call
|
||||
next_play += play_period + play_extra;
|
||||
|
||||
// Call routine if ready
|
||||
if ( play_delay && !--play_delay )
|
||||
{
|
||||
// Save state if init routine is still running
|
||||
if ( cpu.r.pc != idle_addr )
|
||||
{
|
||||
check( saved_state.pc == idle_addr );
|
||||
saved_state = cpu.r;
|
||||
special_event( "play called during init" );
|
||||
}
|
||||
|
||||
jsr_then_stop( header_.play_addr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Nsf_Impl::run_until( time_t end )
|
||||
{
|
||||
while ( time() < end )
|
||||
run_once( end );
|
||||
}
|
||||
|
||||
void Nsf_Impl::end_frame( time_t end )
|
||||
{
|
||||
if ( time() < end )
|
||||
run_until( end );
|
||||
cpu.adjust_time( -end );
|
||||
|
||||
// Localize to new time frame
|
||||
next_play -= end;
|
||||
check( next_play >= 0 );
|
||||
if ( next_play < 0 )
|
||||
next_play = 0;
|
||||
|
||||
apu.end_frame( end );
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// Loads NSF file and emulates CPU and RAM, no sound chips
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef NSF_IMPL_H
|
||||
#define NSF_IMPL_H
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
#include "Nes_Cpu.h"
|
||||
#include "Rom_Data.h"
|
||||
#include "Nes_Apu.h"
|
||||
|
||||
// NSF file header
|
||||
struct nsf_header_t
|
||||
{
|
||||
typedef unsigned char byte;
|
||||
enum { size = 0x80 };
|
||||
|
||||
char tag [ 5];
|
||||
byte vers;
|
||||
byte track_count;
|
||||
byte first_track;
|
||||
byte load_addr [ 2];
|
||||
byte init_addr [ 2];
|
||||
byte play_addr [ 2];
|
||||
char game [32]; // NOT null-terminated if 32 chars in length
|
||||
char author [32];
|
||||
char copyright [32];
|
||||
byte ntsc_speed [ 2];
|
||||
byte banks [ 8];
|
||||
byte pal_speed [ 2];
|
||||
byte speed_flags;
|
||||
byte chip_flags;
|
||||
byte unused [ 4];
|
||||
|
||||
// Sound chip masks
|
||||
enum {
|
||||
vrc6_mask = 1 << 0,
|
||||
vrc7_mask = 1 << 1,
|
||||
fds_mask = 1 << 2,
|
||||
mmc5_mask = 1 << 3,
|
||||
namco_mask = 1 << 4,
|
||||
fme7_mask = 1 << 5,
|
||||
all_mask = (1 << 6) - 1
|
||||
};
|
||||
|
||||
// True if header has proper NSF file signature
|
||||
bool valid_tag() const;
|
||||
|
||||
// True if file supports only PAL speed
|
||||
bool pal_only() const { return (speed_flags & 3) == 1; }
|
||||
|
||||
// Clocks per second
|
||||
double clock_rate() const;
|
||||
|
||||
// Clocks between calls to play routine
|
||||
int play_period() const;
|
||||
};
|
||||
|
||||
/* Loads NSF file into memory, then emulates CPU, RAM, and ROM.
|
||||
Non-memory accesses are routed through cpu_read() and cpu_write(). */
|
||||
class Nsf_Impl : public Gme_Loader {
|
||||
public:
|
||||
|
||||
// Sound chip
|
||||
Nes_Apu* nes_apu() { return &apu; }
|
||||
|
||||
// Starts track, where 0 is the first
|
||||
virtual blargg_err_t start_track( int );
|
||||
|
||||
// Emulates to at least time t, then begins new time frame at
|
||||
// time t. Might emulate a few clocks extra, so after returning,
|
||||
// time() may not be zero.
|
||||
typedef int time_t; // clock count
|
||||
virtual void end_frame( time_t n );
|
||||
|
||||
// Finer control
|
||||
|
||||
// Header for currently loaded file
|
||||
typedef nsf_header_t header_t;
|
||||
header_t const& header() const { return header_; }
|
||||
|
||||
// Sets clocks between calls to play routine to p + 1/2 clock
|
||||
void set_play_period( int p ) { play_period = p; }
|
||||
|
||||
// Time play routine will next be called
|
||||
time_t play_time() const { return next_play; }
|
||||
|
||||
// Emulates to at least time t. Might emulate a few clocks extra.
|
||||
virtual void run_until( time_t t );
|
||||
|
||||
// Time emulated to
|
||||
time_t time() const { return cpu.time(); }
|
||||
|
||||
void enable_w4011_(bool enable = true) { enable_w4011 = enable; }
|
||||
|
||||
Rom_Data const& rom_() const { return rom; }
|
||||
|
||||
protected:
|
||||
// Nsf_Core use
|
||||
|
||||
typedef int addr_t;
|
||||
|
||||
// Called for unmapped accesses. Default just prints info if debugging.
|
||||
virtual void unmapped_write( addr_t, int data );
|
||||
virtual int unmapped_read( addr_t );
|
||||
|
||||
// Override in derived class
|
||||
// Bank writes and RAM at 0-$7FF and $6000-$7FFF are handled internally
|
||||
virtual int cpu_read( addr_t a ) { return unmapped_read( a ); }
|
||||
virtual void cpu_write( addr_t a, int data ){ unmapped_write( a, data ); }
|
||||
|
||||
// Reads byte as CPU would when executing code. Only works for RAM/ROM,
|
||||
// NOT I/O like sound chips.
|
||||
int read_code( addr_t addr ) const;
|
||||
|
||||
// Debugger services
|
||||
|
||||
enum { mem_size = 0x10000 };
|
||||
|
||||
// CPU sits here when waiting for next call to play routine
|
||||
enum { idle_addr = 0x5FF6 };
|
||||
|
||||
Nes_Cpu cpu;
|
||||
|
||||
// Runs CPU to at least time t and returns false, or returns true
|
||||
// if it encounters illegal instruction (halt).
|
||||
virtual bool run_cpu_until( time_t t );
|
||||
|
||||
// CPU calls through to these to access memory (except instructions)
|
||||
int read_mem( addr_t );
|
||||
void write_mem( addr_t, int );
|
||||
|
||||
// Address of play routine
|
||||
addr_t play_addr() const { return get_addr( header_.play_addr ); }
|
||||
|
||||
// Same as run_until, except emulation stops for any event (routine returned,
|
||||
// play routine called, illegal instruction).
|
||||
void run_once( time_t );
|
||||
|
||||
// Make a note of event
|
||||
virtual void special_event( const char str [] );
|
||||
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Nsf_Impl();
|
||||
~Nsf_Impl();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
virtual void unload();
|
||||
|
||||
private:
|
||||
enum { low_ram_size = 0x800 };
|
||||
enum { fdsram_size = 0x6000 };
|
||||
enum { sram_size = 0x2000 };
|
||||
enum { unmapped_size= Nes_Cpu::page_size + 8 };
|
||||
enum { fds_banks = 2 };
|
||||
enum { bank_count = fds_banks + 8 };
|
||||
enum { banks_addr = idle_addr };
|
||||
enum { sram_addr = 0x6000 };
|
||||
|
||||
blargg_vector<byte> high_ram;
|
||||
Rom_Data rom;
|
||||
|
||||
// Play routine timing
|
||||
time_t next_play;
|
||||
time_t play_period;
|
||||
int play_extra;
|
||||
int play_delay;
|
||||
bool enable_w4011;
|
||||
Nes_Cpu::registers_t saved_state; // of interrupted init routine
|
||||
|
||||
// Large objects after others
|
||||
header_t header_;
|
||||
Nes_Apu apu;
|
||||
byte low_ram [low_ram_size];
|
||||
|
||||
// Larger RAM areas allocated separately
|
||||
enum { fdsram_offset = sram_size + unmapped_size };
|
||||
byte* sram() { return high_ram.begin(); }
|
||||
byte* unmapped_code() { return &high_ram [sram_size]; }
|
||||
byte* fdsram() { return &high_ram [fdsram_offset]; }
|
||||
int fds_enabled() const { return header_.chip_flags & header_t::fds_mask; }
|
||||
|
||||
void map_memory();
|
||||
void write_bank( int index, int data );
|
||||
void jsr_then_stop( byte const addr [] );
|
||||
void push_byte( int );
|
||||
static addr_t get_addr( byte const [] );
|
||||
static int pcm_read( void*, int );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,66 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Okim6258_Emu.h"
|
||||
#include "okim6258.h"
|
||||
|
||||
Okim6258_Emu::Okim6258_Emu() { chip = 0; }
|
||||
|
||||
Okim6258_Emu::~Okim6258_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_okim6258( chip );
|
||||
}
|
||||
|
||||
int Okim6258_Emu::set_rate( int clock, int divider, int adpcm_type, int output_12bits )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_okim6258( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_okim6258( clock, divider, adpcm_type, output_12bits );
|
||||
if ( !chip )
|
||||
return 0;
|
||||
|
||||
reset();
|
||||
return okim6258_get_vclk( chip );
|
||||
}
|
||||
|
||||
void Okim6258_Emu::reset()
|
||||
{
|
||||
device_reset_okim6258( chip );
|
||||
}
|
||||
|
||||
void Okim6258_Emu::write( int addr, int data )
|
||||
{
|
||||
okim6258_write( chip, addr, data );
|
||||
}
|
||||
|
||||
void Okim6258_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
okim6258_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// OKIM6258 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef OKIM6258_EMU_H
|
||||
#define OKIM6258_EMU_H
|
||||
|
||||
class Okim6258_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
Okim6258_Emu();
|
||||
~Okim6258_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock, int divider, int adpcm_type, int output_12bits );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Okim6295_Emu.h"
|
||||
#include "okim6295.h"
|
||||
|
||||
Okim6295_Emu::Okim6295_Emu() { chip = 0; }
|
||||
|
||||
Okim6295_Emu::~Okim6295_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_okim6295( chip );
|
||||
}
|
||||
|
||||
int Okim6295_Emu::set_rate( int clock_rate )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_okim6295( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_okim6295( clock_rate );
|
||||
if ( !chip )
|
||||
return 0;
|
||||
|
||||
reset();
|
||||
return (clock_rate & 0x7FFFFFFF) / ((clock_rate & 0x80000000) ? 132 : 165);
|
||||
}
|
||||
|
||||
void Okim6295_Emu::reset()
|
||||
{
|
||||
device_reset_okim6295( chip );
|
||||
okim6295_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void Okim6295_Emu::write( int addr, int data )
|
||||
{
|
||||
okim6295_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void Okim6295_Emu::write_rom( int size, int start, int length, void * data )
|
||||
{
|
||||
okim6295_write_rom( chip, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void Okim6295_Emu::mute_voices( int mask )
|
||||
{
|
||||
okim6295_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void Okim6295_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
okim6295_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// OKIM6295 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef OKIM6295_EMU_H
|
||||
#define OKIM6295_EMU_H
|
||||
|
||||
class Okim6295_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
Okim6295_Emu();
|
||||
~Okim6295_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 4 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,271 @@
|
|||
#include "Opl_Apu.h"
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#include "ym2413.h"
|
||||
#include "fmopl.h"
|
||||
|
||||
Opl_Apu::Opl_Apu() { opl = 0; opl_memory = 0; }
|
||||
|
||||
blargg_err_t Opl_Apu::init( long clock, long rate, blip_time_t period, type_t type )
|
||||
{
|
||||
type_ = type;
|
||||
clock_ = clock;
|
||||
rate_ = rate;
|
||||
period_ = period;
|
||||
set_output( 0, 0 );
|
||||
volume( 1.0 );
|
||||
switch (type)
|
||||
{
|
||||
case type_opll:
|
||||
case type_msxmusic:
|
||||
case type_smsfmunit:
|
||||
opl = ym2413_init( clock, rate, 0 );
|
||||
break;
|
||||
|
||||
case type_vrc7:
|
||||
opl = ym2413_init( clock, rate, 1 );
|
||||
break;
|
||||
|
||||
case type_opl:
|
||||
opl = ym3526_init( clock, rate );
|
||||
break;
|
||||
|
||||
case type_msxaudio:
|
||||
//logfile = fopen("c:\\temp\\msxaudio.log", "wb");
|
||||
opl = y8950_init( clock, rate );
|
||||
opl_memory = malloc( 32768 );
|
||||
y8950_set_delta_t_memory( opl, opl_memory, 32768 );
|
||||
break;
|
||||
|
||||
case type_opl2:
|
||||
opl = ym3812_init( clock, rate );
|
||||
break;
|
||||
}
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Opl_Apu::~Opl_Apu()
|
||||
{
|
||||
if (opl)
|
||||
{
|
||||
switch (type_)
|
||||
{
|
||||
case type_opll:
|
||||
case type_msxmusic:
|
||||
case type_smsfmunit:
|
||||
case type_vrc7:
|
||||
ym2413_shutdown( opl );
|
||||
break;
|
||||
|
||||
case type_opl:
|
||||
ym3526_shutdown( opl );
|
||||
break;
|
||||
|
||||
case type_msxaudio:
|
||||
y8950_shutdown( opl );
|
||||
free( opl_memory );
|
||||
//fclose( logfile );
|
||||
break;
|
||||
|
||||
case type_opl2:
|
||||
ym3812_shutdown( opl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Opl_Apu::reset()
|
||||
{
|
||||
addr = 0;
|
||||
next_time = 0;
|
||||
last_amp = 0;
|
||||
|
||||
switch (type_)
|
||||
{
|
||||
case type_opll:
|
||||
case type_msxmusic:
|
||||
case type_smsfmunit:
|
||||
case type_vrc7:
|
||||
ym2413_reset_chip( opl );
|
||||
break;
|
||||
|
||||
case type_opl:
|
||||
ym3526_reset_chip( opl );
|
||||
break;
|
||||
|
||||
case type_msxaudio:
|
||||
y8950_reset_chip( opl );
|
||||
break;
|
||||
|
||||
case type_opl2:
|
||||
ym3812_reset_chip( opl );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Opl_Apu::write_data( blip_time_t time, int data )
|
||||
{
|
||||
run_until( time );
|
||||
switch (type_)
|
||||
{
|
||||
case type_opll:
|
||||
case type_msxmusic:
|
||||
case type_smsfmunit:
|
||||
case type_vrc7:
|
||||
ym2413_write( opl, 0, addr );
|
||||
ym2413_write( opl, 1, data );
|
||||
break;
|
||||
|
||||
case type_opl:
|
||||
ym3526_write( opl, 0, addr );
|
||||
ym3526_write( opl, 1, data );
|
||||
break;
|
||||
|
||||
case type_msxaudio:
|
||||
/*if ( addr >= 7 && addr <= 7 + 11 )
|
||||
{
|
||||
unsigned char temp [2] = { addr - 7, data };
|
||||
fwrite( &temp, 1, 2, logfile );
|
||||
}*/
|
||||
y8950_write( opl, 0, addr );
|
||||
y8950_write( opl, 1, data );
|
||||
break;
|
||||
|
||||
case type_opl2:
|
||||
ym3812_write( opl, 0, addr );
|
||||
ym3812_write( opl, 1, data );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int Opl_Apu::read( blip_time_t time, int port )
|
||||
{
|
||||
run_until( time );
|
||||
switch (type_)
|
||||
{
|
||||
case type_opll:
|
||||
case type_msxmusic:
|
||||
case type_smsfmunit:
|
||||
case type_vrc7:
|
||||
return ym2413_read( opl, port );
|
||||
|
||||
case type_opl:
|
||||
return ym3526_read( opl, port );
|
||||
|
||||
case type_msxaudio:
|
||||
{
|
||||
int ret = y8950_read( opl, port );
|
||||
/*unsigned char temp [2] = { port + 0x80, ret };
|
||||
fwrite( &temp, 1, 2, logfile );*/
|
||||
return ret;
|
||||
}
|
||||
|
||||
case type_opl2:
|
||||
return ym3812_read( opl, port );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Opl_Apu::end_frame( blip_time_t time )
|
||||
{
|
||||
run_until( time );
|
||||
next_time -= time;
|
||||
|
||||
if ( output_ )
|
||||
output_->set_modified();
|
||||
}
|
||||
|
||||
void Opl_Apu::run_until( blip_time_t end_time )
|
||||
{
|
||||
if ( end_time > next_time )
|
||||
{
|
||||
blip_time_t time_delta = end_time - next_time;
|
||||
blip_time_t time = next_time;
|
||||
unsigned count = time_delta / period_ + 1;
|
||||
switch (type_)
|
||||
{
|
||||
case type_opll:
|
||||
case type_msxmusic:
|
||||
case type_smsfmunit:
|
||||
case type_vrc7:
|
||||
{
|
||||
SAMP bufMO[ 1024 ];
|
||||
SAMP bufRO[ 1024 ];
|
||||
SAMP * buffers[2] = { bufMO, bufRO };
|
||||
|
||||
while ( count > 0 )
|
||||
{
|
||||
unsigned todo = count;
|
||||
if ( todo > 1024 ) todo = 1024;
|
||||
ym2413_update_one( opl, buffers, todo );
|
||||
|
||||
if ( output_ )
|
||||
{
|
||||
int last_amp = this->last_amp;
|
||||
for ( unsigned i = 0; i < todo; i++ )
|
||||
{
|
||||
int amp = bufMO [i] + bufRO [i];
|
||||
int delta = amp - last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
last_amp = amp;
|
||||
synth.offset_inline( time, delta, output_ );
|
||||
}
|
||||
time += period_;
|
||||
}
|
||||
this->last_amp = last_amp;
|
||||
}
|
||||
else time += period_ * todo;
|
||||
|
||||
count -= todo;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case type_opl:
|
||||
case type_msxaudio:
|
||||
case type_opl2:
|
||||
{
|
||||
OPLSAMPLE buffer[ 1024 ];
|
||||
|
||||
while ( count > 0 )
|
||||
{
|
||||
unsigned todo = count;
|
||||
if ( todo > 1024 ) todo = 1024;
|
||||
switch (type_)
|
||||
{
|
||||
case type_opl: ym3526_update_one( opl, buffer, todo ); break;
|
||||
case type_msxaudio: y8950_update_one( opl, buffer, todo ); break;
|
||||
case type_opl2: ym3812_update_one( opl, buffer, todo ); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if ( output_ )
|
||||
{
|
||||
int last_amp = this->last_amp;
|
||||
for ( unsigned i = 0; i < todo; i++ )
|
||||
{
|
||||
int amp = buffer [i];
|
||||
int delta = amp - last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
last_amp = amp;
|
||||
synth.offset_inline( time, delta, output_ );
|
||||
}
|
||||
time += period_;
|
||||
}
|
||||
this->last_amp = last_amp;
|
||||
}
|
||||
else time += period_ * todo;
|
||||
|
||||
count -= todo;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
next_time = time;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef OPL_APU_H
|
||||
#define OPL_APU_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Blip_Buffer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
class Opl_Apu {
|
||||
public:
|
||||
Opl_Apu();
|
||||
~Opl_Apu();
|
||||
|
||||
enum type_t { type_opll = 0x10, type_msxmusic = 0x11, type_smsfmunit = 0x12,
|
||||
type_vrc7 = 0x13, type_opl = 0x20, type_msxaudio = 0x21, type_opl2 = 0x22 };
|
||||
blargg_err_t init( long clock, long rate, blip_time_t period, type_t );
|
||||
|
||||
void reset();
|
||||
void volume( double v ) { synth.volume( 1.0 / (4096 * 6) * v ); }
|
||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||
enum { osc_count = 1 };
|
||||
void osc_output( int index, Blip_Buffer* );
|
||||
void set_output( int i, Blip_Buffer* buf, Blip_Buffer* = NULL, Blip_Buffer* = NULL ) { osc_output( 0, buf ); }
|
||||
void end_frame( blip_time_t );
|
||||
|
||||
void write_addr( int data ) { addr = data; }
|
||||
void write_data( blip_time_t, int data );
|
||||
|
||||
int read( blip_time_t, int port );
|
||||
|
||||
static bool supported() { return true; }
|
||||
|
||||
private:
|
||||
// noncopyable
|
||||
Opl_Apu( const Opl_Apu& );
|
||||
Opl_Apu& operator = ( const Opl_Apu& );
|
||||
|
||||
Blip_Buffer* output_;
|
||||
type_t type_;
|
||||
void* opl;
|
||||
void* opl_memory;
|
||||
//FILE* logfile;
|
||||
unsigned char regs[ 0x100 ];
|
||||
blip_time_t next_time;
|
||||
int last_amp;
|
||||
int addr;
|
||||
|
||||
long clock_;
|
||||
long rate_;
|
||||
blip_time_t period_;
|
||||
|
||||
Blip_Synth_Fast synth;
|
||||
|
||||
void run_until( blip_time_t );
|
||||
};
|
||||
|
||||
inline void Opl_Apu::osc_output( int i, Blip_Buffer* buf )
|
||||
{
|
||||
assert( (unsigned) i < osc_count );
|
||||
output_ = buf;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,66 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Pwm_Emu.h"
|
||||
#include "pwm.h"
|
||||
|
||||
Pwm_Emu::Pwm_Emu() { chip = 0; }
|
||||
|
||||
Pwm_Emu::~Pwm_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_pwm( chip );
|
||||
}
|
||||
|
||||
int Pwm_Emu::set_rate( int clock )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_pwm( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_pwm( clock );
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Pwm_Emu::reset()
|
||||
{
|
||||
device_reset_pwm( chip );
|
||||
}
|
||||
|
||||
void Pwm_Emu::write( int channel, int data )
|
||||
{
|
||||
pwm_chn_w( chip, channel, data );
|
||||
}
|
||||
|
||||
void Pwm_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
pwm_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// PWM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef PWM_EMU_H
|
||||
#define PWM_EMU_H
|
||||
|
||||
class Pwm_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
Pwm_Emu();
|
||||
~Pwm_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 24 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to channel
|
||||
void write( int channel, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,83 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Qsound_Apu.h"
|
||||
#include "qmix.h"
|
||||
|
||||
Qsound_Apu::Qsound_Apu() { chip = 0; rom = 0; rom_size = 0; sample_rate = 0; }
|
||||
|
||||
Qsound_Apu::~Qsound_Apu()
|
||||
{
|
||||
if ( chip ) free( chip );
|
||||
if ( rom ) free( rom );
|
||||
}
|
||||
|
||||
int Qsound_Apu::set_rate( int clock_rate )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
free( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = malloc( _qmix_get_state_size() );
|
||||
if ( !chip )
|
||||
return 0;
|
||||
|
||||
reset();
|
||||
|
||||
return clock_rate / 166;
|
||||
}
|
||||
|
||||
void Qsound_Apu::set_sample_rate( int sample_rate )
|
||||
{
|
||||
this->sample_rate = sample_rate;
|
||||
if ( chip ) _qmix_set_sample_rate( chip, sample_rate );
|
||||
}
|
||||
|
||||
void Qsound_Apu::reset()
|
||||
{
|
||||
_qmix_clear_state( chip );
|
||||
_qmix_set_sample_rate( chip, sample_rate );
|
||||
if ( rom ) _qmix_set_sample_rom( chip, rom, rom_size );
|
||||
}
|
||||
|
||||
void Qsound_Apu::write( int addr, int data )
|
||||
{
|
||||
_qmix_command( chip, addr, data );
|
||||
}
|
||||
|
||||
void Qsound_Apu::write_rom( int size, int start, int length, void const* data )
|
||||
{
|
||||
if ( size > rom_size )
|
||||
{
|
||||
rom_size = size;
|
||||
rom = realloc( rom, size );
|
||||
}
|
||||
if ( start > size ) start = size;
|
||||
if ( start + length > size ) length = size - start;
|
||||
memcpy( (uint8*)rom + start, data, length );
|
||||
if ( chip ) _qmix_set_sample_rom( chip, rom, rom_size );
|
||||
}
|
||||
|
||||
void Qsound_Apu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
sint16 buf[ 1024 * 2 ];
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
_qmix_render( chip, buf, todo );
|
||||
|
||||
for (int i = 0; i < todo * 2; i++)
|
||||
{
|
||||
int output = buf [i];
|
||||
output += out [0];
|
||||
if ( (short)output != output ) output = 0x7FFF ^ ( output >> 31 );
|
||||
out [0] = output;
|
||||
out++;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Capcom QSound sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef QSOUND_APU_H
|
||||
#define QSOUND_APU_H
|
||||
|
||||
class Qsound_Apu {
|
||||
void* chip;
|
||||
void* rom;
|
||||
int rom_size;
|
||||
int sample_rate;
|
||||
public:
|
||||
Qsound_Apu();
|
||||
~Qsound_Apu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock_rate );
|
||||
void set_sample_rate( int sample_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void const* data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,79 @@
|
|||
// $package. http://www.slack.net/~ant/
|
||||
|
||||
#include "Resampler.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"
|
||||
|
||||
Resampler::Resampler()
|
||||
{
|
||||
write_pos = 0;
|
||||
rate_ = 0;
|
||||
}
|
||||
|
||||
Resampler::~Resampler() { }
|
||||
|
||||
void Resampler::clear()
|
||||
{
|
||||
write_pos = 0;
|
||||
clear_();
|
||||
}
|
||||
|
||||
inline int Resampler::resample_wrapper( sample_t out [], int* out_size,
|
||||
sample_t const in [], int in_size )
|
||||
{
|
||||
assert( rate() );
|
||||
|
||||
sample_t* out_ = out;
|
||||
int result = resample_( &out_, out + *out_size, in, in_size ) - in;
|
||||
assert( out_ <= out + *out_size );
|
||||
assert( result <= in_size );
|
||||
|
||||
*out_size = out_ - out;
|
||||
return result;
|
||||
}
|
||||
|
||||
int Resampler::resample( sample_t out [], int out_size, sample_t const in [], int* in_size )
|
||||
{
|
||||
*in_size = resample_wrapper( out, &out_size, in, *in_size );
|
||||
return out_size;
|
||||
}
|
||||
|
||||
|
||||
//// Buffering
|
||||
|
||||
blargg_err_t Resampler::resize_buffer( int new_size )
|
||||
{
|
||||
RETURN_ERR( buf.resize( new_size ) );
|
||||
clear();
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
int Resampler::skip_input( int count )
|
||||
{
|
||||
write_pos -= count;
|
||||
if ( write_pos < 0 ) // occurs when downsampling
|
||||
{
|
||||
count += write_pos;
|
||||
write_pos = 0;
|
||||
}
|
||||
memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] );
|
||||
return count;
|
||||
}
|
||||
|
||||
int Resampler::read( sample_t out [], int out_size )
|
||||
{
|
||||
if ( out_size )
|
||||
skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) );
|
||||
return out_size;
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// Common interface for resamplers
|
||||
|
||||
// $package
|
||||
#ifndef RESAMPLER_H
|
||||
#define RESAMPLER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
class Resampler {
|
||||
public:
|
||||
|
||||
virtual ~Resampler();
|
||||
|
||||
// Sets input/output resampling ratio
|
||||
blargg_err_t set_rate( double );
|
||||
|
||||
// Current input/output ratio
|
||||
double rate() const { return rate_; }
|
||||
|
||||
// Samples are 16-bit signed
|
||||
typedef short sample_t;
|
||||
|
||||
// One of two different buffering schemes can be used, as decided by the caller:
|
||||
|
||||
// External buffering (caller provides input buffer)
|
||||
|
||||
// Resamples in to at most n out samples and returns number of samples actually
|
||||
// written. Sets *in_size to number of input samples that aren't needed anymore
|
||||
// and should be removed from input.
|
||||
int resample( sample_t out [], int n, sample_t const in [], int* in_size );
|
||||
|
||||
// Internal buffering (resampler manages buffer)
|
||||
|
||||
// Resizes input buffer to n samples, then clears it
|
||||
blargg_err_t resize_buffer( int n );
|
||||
|
||||
// Clears input buffer
|
||||
void clear();
|
||||
|
||||
// Writes at most n samples to input buffer and returns number actually written.
|
||||
// Result will be less than n if there isn't enough free space in buffer.
|
||||
int write( sample_t const in [], int n );
|
||||
|
||||
// Number of input samples in buffer
|
||||
int written() const { return write_pos; }
|
||||
|
||||
// Removes first n input samples from buffer, fewer if there aren't that many.
|
||||
// Returns number of samples actually removed.
|
||||
int skip_input( int n );
|
||||
|
||||
// Resamples input to at most n output samples. Returns number of samples
|
||||
// actually written to out. Result will be less than n if there aren't
|
||||
// enough input samples in buffer.
|
||||
int read( sample_t out [], int n );
|
||||
|
||||
// Direct writing to input buffer, instead of using write( in, n ) above
|
||||
|
||||
// Pointer to place to write input samples
|
||||
sample_t* buffer() { return &buf [write_pos]; }
|
||||
|
||||
// Number of samples that can be written to buffer()
|
||||
int buffer_free() const { return buf.size() - write_pos; }
|
||||
|
||||
// Notifies resampler that n input samples have been written to buffer().
|
||||
// N must not be greater than buffer_free().
|
||||
void write( int n );
|
||||
|
||||
// Derived interface
|
||||
protected:
|
||||
virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; )
|
||||
|
||||
virtual void clear_() { }
|
||||
|
||||
// Resample as many available in samples as will fit within out_size and
|
||||
// return pointer past last input sample read and set *out just past
|
||||
// the last output sample.
|
||||
virtual sample_t const* resample_( sample_t** out, sample_t const* out_end,
|
||||
sample_t const in [], int in_size ) BLARGG_PURE( { return in; } )
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Resampler();
|
||||
|
||||
private:
|
||||
blargg_vector<sample_t> buf;
|
||||
int write_pos;
|
||||
double rate_;
|
||||
|
||||
int resample_wrapper( sample_t out [], int* out_size,
|
||||
sample_t const in [], int in_size );
|
||||
};
|
||||
|
||||
inline void Resampler::write( int count )
|
||||
{
|
||||
write_pos += count;
|
||||
assert( (unsigned) write_pos <= buf.size() );
|
||||
}
|
||||
|
||||
inline blargg_err_t Resampler::set_rate_( double r )
|
||||
{
|
||||
rate_ = r;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
inline blargg_err_t Resampler::set_rate( double r )
|
||||
{
|
||||
return set_rate_( r );
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,82 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Rf5C164_Emu.h"
|
||||
#include "scd_pcm.h"
|
||||
|
||||
Rf5C164_Emu::Rf5C164_Emu() { chip = 0; }
|
||||
|
||||
Rf5C164_Emu::~Rf5C164_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_rf5c164( chip );
|
||||
}
|
||||
|
||||
int Rf5C164_Emu::set_rate( int clock )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_rf5c164( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_rf5c164( clock );
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Rf5C164_Emu::reset()
|
||||
{
|
||||
device_reset_rf5c164( chip );
|
||||
rf5c164_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void Rf5C164_Emu::write( int addr, int data )
|
||||
{
|
||||
rf5c164_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void Rf5C164_Emu::write_mem( int addr, int data )
|
||||
{
|
||||
rf5c164_mem_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void Rf5C164_Emu::write_ram( int start, int length, void * data )
|
||||
{
|
||||
rf5c164_write_ram( chip, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void Rf5C164_Emu::mute_voices( int mask )
|
||||
{
|
||||
rf5c164_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void Rf5C164_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
rf5c164_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// RF5C164 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef RF5C164_EMU_H
|
||||
#define RF5C164_EMU_H
|
||||
|
||||
class Rf5C164_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
Rf5C164_Emu();
|
||||
~Rf5C164_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Writes to memory
|
||||
void write_mem( int addr, int data );
|
||||
|
||||
// Writes length bytes from data at start offset in RAM
|
||||
void write_ram( int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,82 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Rf5C68_Emu.h"
|
||||
#include "rf5c68.h"
|
||||
|
||||
Rf5C68_Emu::Rf5C68_Emu() { chip = 0; }
|
||||
|
||||
Rf5C68_Emu::~Rf5C68_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_rf5c68( chip );
|
||||
}
|
||||
|
||||
int Rf5C68_Emu::set_rate()
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_rf5c68( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_rf5c68();
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Rf5C68_Emu::reset()
|
||||
{
|
||||
device_reset_rf5c68( chip );
|
||||
rf5c68_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void Rf5C68_Emu::write( int addr, int data )
|
||||
{
|
||||
rf5c68_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void Rf5C68_Emu::write_mem( int addr, int data )
|
||||
{
|
||||
rf5c68_mem_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void Rf5C68_Emu::write_ram( int start, int length, void * data )
|
||||
{
|
||||
rf5c68_write_ram( chip, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void Rf5C68_Emu::mute_voices( int mask )
|
||||
{
|
||||
rf5c68_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void Rf5C68_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
rf5c68_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// RF5C68 sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef RF5C68_EMU_H
|
||||
#define RF5C68_EMU_H
|
||||
|
||||
class Rf5C68_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
Rf5C68_Emu();
|
||||
~Rf5C68_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate();
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Writes to memory
|
||||
void write_mem( int addr, int data );
|
||||
|
||||
// Writes length bytes from data at start offset in RAM
|
||||
void write_ram( int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,99 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Rom_Data.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"
|
||||
|
||||
void Rom_Data::clear()
|
||||
{
|
||||
file_size_ = 0;
|
||||
rom_addr = 0;
|
||||
mask = 0;
|
||||
rom.clear();
|
||||
}
|
||||
|
||||
Rom_Data::Rom_Data( int page_size ) :
|
||||
pad_size( page_size + pad_extra )
|
||||
{
|
||||
// page_size should be power of 2
|
||||
check( (page_size & (page_size - 1)) == 0 );
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
Rom_Data::~Rom_Data()
|
||||
{ }
|
||||
|
||||
// Reads file into array, placing file_offset bytes of padding before the beginning, and pad_size after the end
|
||||
blargg_err_t Rom_Data::load_( Data_Reader& in, int header_size, int file_offset )
|
||||
{
|
||||
clear();
|
||||
file_size_ = in.remain();
|
||||
if ( file_size_ <= header_size ) // <= because there must be data after header
|
||||
return blargg_err_file_type;
|
||||
|
||||
RETURN_ERR( rom.resize( file_offset + file_size_ + pad_size ) );
|
||||
|
||||
return in.read( rom.begin() + file_offset, file_size_ );
|
||||
}
|
||||
|
||||
blargg_err_t Rom_Data::load( Data_Reader& in, int header_size,
|
||||
void* header_out, int fill )
|
||||
{
|
||||
int file_offset = pad_size - header_size;
|
||||
blargg_err_t err = load_( in, header_size, file_offset );
|
||||
if ( err )
|
||||
{
|
||||
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 blargg_ok;
|
||||
}
|
||||
|
||||
void Rom_Data::set_addr( int addr )
|
||||
{
|
||||
int const page_size = pad_size - pad_extra;
|
||||
|
||||
// Minimum size that contains all bytes and is a multiple of page_size
|
||||
int const size = (addr + file_size_ + page_size - 1) / page_size * page_size;
|
||||
|
||||
// Find lowest power of 2 that is >= size
|
||||
int power2 = 1;
|
||||
while ( power2 < size )
|
||||
power2 *= 2;
|
||||
|
||||
mask = power2 - 1;
|
||||
|
||||
// Address of first byte of ROM (possibly negative)
|
||||
rom_addr = addr - page_size - pad_extra;
|
||||
|
||||
if ( rom.resize( size - rom_addr + pad_extra ) ) { } // OK if shrink fails
|
||||
}
|
||||
|
||||
byte* Rom_Data::at_addr( int addr )
|
||||
{
|
||||
int offset = mask_addr( addr ) - rom_addr;
|
||||
|
||||
if ( (unsigned) offset > (unsigned) (rom.size() - pad_size) )
|
||||
offset = 0; // unmapped
|
||||
|
||||
return &rom [offset];
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// Manages ROM data loaded from file in an efficient manner
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef ROM_DATA_H
|
||||
#define ROM_DATA_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
#include "Data_Reader.h"
|
||||
|
||||
/* Loads a ROM file into memory and allows access to it in page-sized chunks.
|
||||
|
||||
* ROM file consists of header followed by ROM data. Instead of storing the entire
|
||||
ROM contents, the file only stores the occupied portion, with the bytes before and
|
||||
after that cleared to some value. The size and format of the header is up to the
|
||||
caller, as is the starting address of the ROM data following it. File loading is
|
||||
performed with a single read, rather than two or more that might otherwise be
|
||||
required.
|
||||
|
||||
* Once ROM data is loaded and its address specified, a pointer to any "page" can
|
||||
be obtained. ROM data is mirrored using smallest power of 2 that contains it.
|
||||
Addresses not aligned to pages can also be used, but this might cause unexpected
|
||||
results.
|
||||
|
||||
Example with file data of size 0x0C put at address 0x0F, with page size of 8:
|
||||
|
||||
---------------0123456789AB--------------------0123456789AB---------...
|
||||
^ ^ ^ ^ ^ ^ ^ ^ ^
|
||||
0 0x08 0x10 0x18 0x20 0x28 0x30 0x38 0x40
|
||||
|
||||
at_addr(0x00) = pointer to 8 bytes of fill.
|
||||
at_addr(0x08) = pointer to 7 bytes of fill, followed by first byte of file.
|
||||
at_addr(0x10) = pointer to next 8 bytes of file.
|
||||
at_addr(0x18) = pointer to last 3 bytes of file, followed by 5 bytes of fill.
|
||||
at_addr(0x20) = pointer to 8 bytes of fill.
|
||||
at_addr(0x28) = pointer to 7 bytes of fill, followed by first byte of file.
|
||||
etc. */
|
||||
|
||||
class Rom_Data {
|
||||
enum { pad_extra = 8 };
|
||||
public:
|
||||
typedef unsigned char byte;
|
||||
|
||||
// Page_size should be a power of 2
|
||||
Rom_Data( int page_size );
|
||||
|
||||
// Loads file into memory, then copies header to *header_out and fills
|
||||
// unmapped bank and file data padding with fill. Returns blargg_err_file_type
|
||||
// if in.remain() <= header_size.
|
||||
blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill );
|
||||
|
||||
// Below, "file data" refers to data AFTER the header
|
||||
|
||||
// Size of file data
|
||||
int file_size() const { return file_size_; }
|
||||
|
||||
// Pointer to beginning of file data
|
||||
byte * begin() { return rom.begin() + pad_size; }
|
||||
byte const* begin() const { return rom.begin() + pad_size; }
|
||||
|
||||
// Pointer to unmapped page cleared with fill value
|
||||
byte* unmapped() { return rom.begin(); }
|
||||
|
||||
// Sets address that file data will start at. Must be set before using following
|
||||
// functions, and cannot be set more than once.
|
||||
void set_addr( int addr );
|
||||
|
||||
// Address of first empty page (file size + addr rounded up to multiple of page_size)
|
||||
int size() const { return rom.size() - pad_extra + rom_addr; }
|
||||
|
||||
// Masks address to nearest power of two greater than size()
|
||||
int mask_addr( int addr ) const { return addr & mask; }
|
||||
|
||||
// Pointer to page beginning at addr, or unmapped() if outside data.
|
||||
// Mirrored using mask_addr().
|
||||
byte* at_addr( int addr );
|
||||
|
||||
// Frees memory
|
||||
void clear();
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
~Rom_Data();
|
||||
|
||||
protected:
|
||||
blargg_vector<byte> rom;
|
||||
int mask;
|
||||
int rom_addr;
|
||||
int const pad_size;
|
||||
int file_size_;
|
||||
|
||||
blargg_err_t load_( Data_Reader& in, int header_size, int file_offset );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,192 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Sap_Core.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 idle_addr = 0xD2D2;
|
||||
|
||||
Sap_Core::Sap_Core()
|
||||
{
|
||||
set_tempo( 1 );
|
||||
}
|
||||
|
||||
void Sap_Core::push( int b )
|
||||
{
|
||||
mem.ram [0x100 + cpu.r.sp--] = (byte) b;
|
||||
}
|
||||
|
||||
void Sap_Core::jsr_then_stop( addr_t addr )
|
||||
{
|
||||
cpu.r.pc = addr;
|
||||
|
||||
// Some rips pop three bytes off stack before RTS.
|
||||
push( (idle_addr - 1) >> 8 );
|
||||
push( idle_addr - 1 );
|
||||
|
||||
// 3 bytes so that RTI or RTS will jump to idle_addr.
|
||||
// RTI will use the first two bytes as the address, 0xD2D2.
|
||||
// RTS will use the last two bytes, 0xD2D1, which it internally increments.
|
||||
push( (idle_addr - 1) >> 8 );
|
||||
push( (idle_addr - 1) >> 8 );
|
||||
push( idle_addr - 1 );
|
||||
}
|
||||
|
||||
// Runs routine and allows it up to one second to return
|
||||
void Sap_Core::run_routine( addr_t addr )
|
||||
{
|
||||
jsr_then_stop( addr );
|
||||
run_cpu( lines_per_frame * base_scanline_period * 60 );
|
||||
check( cpu.r.pc == idle_addr );
|
||||
check( cpu.r.sp >= 0xFF - 6 );
|
||||
}
|
||||
|
||||
inline void Sap_Core::call_init( int track )
|
||||
{
|
||||
cpu.r.a = track;
|
||||
|
||||
switch ( info.type )
|
||||
{
|
||||
case 'B':
|
||||
run_routine( info.init_addr );
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
cpu.r.a = 0x70;
|
||||
cpu.r.x = info.music_addr&0xFF;
|
||||
cpu.r.y = info.music_addr >> 8;
|
||||
run_routine( info.play_addr + 3 );
|
||||
cpu.r.a = 0;
|
||||
cpu.r.x = track;
|
||||
run_routine( info.play_addr + 3 );
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
check( info.fastplay == lines_per_frame );
|
||||
jsr_then_stop( info.init_addr );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Sap_Core::setup_ram()
|
||||
{
|
||||
memset( &mem, 0, sizeof mem );
|
||||
|
||||
ram() [idle_addr] = cpu.halt_opcode;
|
||||
|
||||
addr_t const irq_addr = idle_addr - 1;
|
||||
ram() [irq_addr] = cpu.halt_opcode;
|
||||
ram() [0xFFFE] = (byte) irq_addr;
|
||||
ram() [0xFFFF] = irq_addr >> 8;
|
||||
}
|
||||
|
||||
blargg_err_t Sap_Core::start_track( int track, info_t const& new_info )
|
||||
{
|
||||
info = new_info;
|
||||
|
||||
check( ram() [idle_addr] == cpu.halt_opcode );
|
||||
|
||||
apu_ .reset( &apu_impl_ );
|
||||
apu2_.reset( &apu_impl_ );
|
||||
|
||||
cpu.reset( ram() );
|
||||
|
||||
frame_start = 0;
|
||||
next_play = play_period() * 4;
|
||||
saved_state.pc = idle_addr;
|
||||
|
||||
time_mask = 0; // disables sound during init
|
||||
call_init( track );
|
||||
time_mask = ~0;
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Sap_Core::run_until( time_t end )
|
||||
{
|
||||
while ( cpu.time() < end )
|
||||
{
|
||||
time_t next = min( next_play, end );
|
||||
if ( (run_cpu( next ) && cpu.r.pc != idle_addr) || cpu.error_count() )
|
||||
// TODO: better error
|
||||
return BLARGG_ERR( BLARGG_ERR_GENERIC, "Emulation error (illegal instruction)" );
|
||||
|
||||
if ( cpu.r.pc == idle_addr )
|
||||
{
|
||||
if ( saved_state.pc == idle_addr )
|
||||
{
|
||||
// no code to run until next play call
|
||||
cpu.set_time( next );
|
||||
}
|
||||
else
|
||||
{
|
||||
// play had interrupted non-returning init, so restore registers
|
||||
// init routine was running
|
||||
check( cpu.r.sp == saved_state.sp - 3 );
|
||||
cpu.r = saved_state;
|
||||
saved_state.pc = idle_addr;
|
||||
}
|
||||
}
|
||||
|
||||
if ( cpu.time() >= next_play )
|
||||
{
|
||||
next_play += play_period();
|
||||
|
||||
if ( cpu.r.pc == idle_addr || info.type == 'D' )
|
||||
{
|
||||
// Save state if init routine is still running
|
||||
if ( cpu.r.pc != idle_addr )
|
||||
{
|
||||
check( info.type == 'D' );
|
||||
check( saved_state.pc == idle_addr );
|
||||
saved_state = cpu.r;
|
||||
}
|
||||
|
||||
addr_t addr = info.play_addr;
|
||||
if ( info.type == 'C' )
|
||||
addr += 6;
|
||||
jsr_then_stop( addr );
|
||||
}
|
||||
else
|
||||
{
|
||||
dprintf( "init/play hadn't returned before next play call\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Sap_Core::end_frame( time_t end )
|
||||
{
|
||||
RETURN_ERR( run_until( end ) );
|
||||
|
||||
cpu.adjust_time( -end );
|
||||
|
||||
time_t frame_time = lines_per_frame * scanline_period;
|
||||
while ( frame_start < end )
|
||||
frame_start += frame_time;
|
||||
frame_start -= end + frame_time;
|
||||
|
||||
if ( (next_play -= end) < 0 )
|
||||
{
|
||||
next_play = 0;
|
||||
check( false );
|
||||
}
|
||||
|
||||
apu_.end_frame( end );
|
||||
if ( info.stereo )
|
||||
apu2_.end_frame( end );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// Atari XL/XE SAP core CPU and RAM emulator
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef SAP_CORE_H
|
||||
#define SAP_CORE_H
|
||||
|
||||
#include "Sap_Apu.h"
|
||||
#include "Nes_Cpu.h"
|
||||
|
||||
class Sap_Core {
|
||||
public:
|
||||
|
||||
// Sound chips and common state
|
||||
Sap_Apu& apu() { return apu_; }
|
||||
Sap_Apu& apu2() { return apu2_; }
|
||||
Sap_Apu_Impl& apu_impl() { return apu_impl_; }
|
||||
|
||||
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
||||
void set_tempo( double );
|
||||
|
||||
// Clears RAM and sets up default vectors, etc.
|
||||
void setup_ram();
|
||||
|
||||
// 64K RAM to load file data blocks into
|
||||
BOOST::uint8_t* ram() { return mem.ram; }
|
||||
|
||||
// Calls init routine and configures playback. RAM must have been
|
||||
// set up already.
|
||||
struct info_t {
|
||||
int init_addr;
|
||||
int play_addr;
|
||||
int music_addr;
|
||||
int type;
|
||||
int fastplay;
|
||||
bool stereo;
|
||||
};
|
||||
blargg_err_t start_track( int track, info_t const& );
|
||||
|
||||
// Ends time frame at time t, then begins new at time 0
|
||||
typedef Nes_Cpu::time_t time_t; // Clock count
|
||||
blargg_err_t end_frame( time_t t );
|
||||
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Sap_Core();
|
||||
|
||||
private:
|
||||
enum { base_scanline_period = 114 };
|
||||
enum { lines_per_frame = 312 };
|
||||
typedef Nes_Cpu::addr_t addr_t;
|
||||
|
||||
time_t scanline_period;
|
||||
time_t next_play;
|
||||
time_t time_mask;
|
||||
time_t frame_start;
|
||||
Nes_Cpu cpu;
|
||||
Nes_Cpu::registers_t saved_state;
|
||||
info_t info;
|
||||
Sap_Apu apu_;
|
||||
Sap_Apu apu2_;
|
||||
|
||||
// large items
|
||||
struct {
|
||||
BOOST::uint8_t padding1 [ 0x100];
|
||||
BOOST::uint8_t ram [0x10000];
|
||||
BOOST::uint8_t padding2 [ 0x100];
|
||||
} mem; // TODO: put on freestore
|
||||
Sap_Apu_Impl apu_impl_;
|
||||
|
||||
void push( int b );
|
||||
void jsr_then_stop( addr_t );
|
||||
void run_routine( addr_t );
|
||||
void call_init( int track );
|
||||
bool run_cpu( time_t end );
|
||||
int play_addr();
|
||||
int read_d40b();
|
||||
int read_mem( addr_t );
|
||||
void write_D2xx( int d2xx, int data );
|
||||
|
||||
time_t time() const { return cpu.time() & time_mask; }
|
||||
blargg_err_t run_until( time_t t );
|
||||
time_t play_period() const { return info.fastplay * scanline_period; }
|
||||
};
|
||||
|
||||
inline void Sap_Core::set_tempo( double t )
|
||||
{
|
||||
scanline_period = (int) (base_scanline_period / t + 0.5);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "SegaPcm_Emu.h"
|
||||
#include "segapcm.h"
|
||||
|
||||
SegaPcm_Emu::SegaPcm_Emu() { chip = 0; }
|
||||
|
||||
SegaPcm_Emu::~SegaPcm_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_segapcm( chip );
|
||||
}
|
||||
|
||||
int SegaPcm_Emu::set_rate( int intf_type )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_segapcm( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_segapcm( intf_type );
|
||||
if ( !chip )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SegaPcm_Emu::reset()
|
||||
{
|
||||
device_reset_segapcm( chip );
|
||||
segapcm_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void SegaPcm_Emu::write( int addr, int data )
|
||||
{
|
||||
sega_pcm_w( chip, addr, data );
|
||||
}
|
||||
|
||||
void SegaPcm_Emu::write_rom( int size, int start, int length, void * data )
|
||||
{
|
||||
sega_pcm_write_rom( chip, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void SegaPcm_Emu::mute_voices( int mask )
|
||||
{
|
||||
segapcm_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void SegaPcm_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
SEGAPCM_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Sega PCM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef SEGAPCM_EMU_H
|
||||
#define SEGAPCM_EMU_H
|
||||
|
||||
class SegaPcm_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
SegaPcm_Emu();
|
||||
~SegaPcm_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int intf_type );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 16 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,108 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Sgc_Core.h"
|
||||
|
||||
/* Copyright (C) 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"
|
||||
|
||||
void Sgc_Core::set_tempo( double t )
|
||||
{
|
||||
set_play_period( clock_rate() / (header().rate ? 50 : 60) / t );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Core::load_( Data_Reader& dr )
|
||||
{
|
||||
RETURN_ERR( Sgc_Impl::load_( dr ) );
|
||||
|
||||
if ( sega_mapping() && fm_apu_.supported() )
|
||||
RETURN_ERR( fm_apu_.init( clock_rate(), clock_rate() / 72 ) );
|
||||
|
||||
set_tempo( 1.0 );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Core::start_track( int t )
|
||||
{
|
||||
if ( sega_mapping() )
|
||||
{
|
||||
apu_.reset();
|
||||
fm_apu_.reset();
|
||||
fm_accessed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
apu_.reset( 0x0003, 15 );
|
||||
}
|
||||
|
||||
return Sgc_Impl::start_track( t );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Core::end_frame( time_t t )
|
||||
{
|
||||
RETURN_ERR( Sgc_Impl::end_frame( t ) );
|
||||
apu_.end_frame( t );
|
||||
if ( sega_mapping() && fm_accessed )
|
||||
{
|
||||
if ( fm_apu_.supported() )
|
||||
fm_apu_.end_frame( t );
|
||||
else
|
||||
set_warning( "FM sound not supported" );
|
||||
}
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
Sgc_Core::Sgc_Core()
|
||||
{ }
|
||||
|
||||
Sgc_Core::~Sgc_Core()
|
||||
{ }
|
||||
|
||||
void Sgc_Core::cpu_out( time_t time, addr_t addr, int data )
|
||||
{
|
||||
int port = addr & 0xFF;
|
||||
|
||||
if ( sega_mapping() )
|
||||
{
|
||||
switch ( port )
|
||||
{
|
||||
case 0x06:
|
||||
apu_.write_ggstereo( time, data );
|
||||
return;
|
||||
|
||||
case 0x7E:
|
||||
case 0x7F:
|
||||
apu_.write_data( time, data ); dprintf( "$7E<-%02X\n", data );
|
||||
return;
|
||||
|
||||
case 0xF0:
|
||||
fm_accessed = true;
|
||||
if ( fm_apu_.supported() )
|
||||
fm_apu_.write_addr( data );//, dprintf( "$F0<-%02X\n", data );
|
||||
return;
|
||||
|
||||
case 0xF1:
|
||||
fm_accessed = true;
|
||||
if ( fm_apu_.supported() )
|
||||
fm_apu_.write_data( time, data );//, dprintf( "$F1<-%02X\n", data );
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ( port >= 0xE0 )
|
||||
{
|
||||
apu_.write_data( time, data );
|
||||
return;
|
||||
}
|
||||
|
||||
Sgc_Impl::cpu_out( time, addr, data );
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Sega/Game Gear/Coleco SGC music file emulator core
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef SGC_CORE_H
|
||||
#define SGC_CORE_H
|
||||
|
||||
#include "Sgc_Impl.h"
|
||||
#include "Sms_Fm_Apu.h"
|
||||
#include "Sms_Apu.h"
|
||||
|
||||
class Sgc_Core : public Sgc_Impl {
|
||||
public:
|
||||
|
||||
// Adjusts music tempo, where 1.0 is normal. Can be changed while playing.
|
||||
// Resets to 1.0 when loading file.
|
||||
void set_tempo( double );
|
||||
|
||||
// Starts track, where 0 is the first.
|
||||
blargg_err_t start_track( int );
|
||||
|
||||
// Ends time frame at time t
|
||||
blargg_err_t end_frame( time_t t );
|
||||
|
||||
// SN76489 sound chip
|
||||
Sms_Apu& apu() { return apu_; }
|
||||
Sms_Fm_Apu& fm_apu() { return fm_apu_; }
|
||||
|
||||
protected:
|
||||
// Overrides
|
||||
virtual void cpu_out( time_t, addr_t, int data );
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Sgc_Core();
|
||||
~Sgc_Core();
|
||||
|
||||
private:
|
||||
bool fm_accessed;
|
||||
Sms_Apu apu_;
|
||||
Sms_Fm_Apu fm_apu_;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,36 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Sgc_Impl.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
//#include "z80_cpu_log.h"
|
||||
|
||||
/* Copyright (C) 2009 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data )
|
||||
#define IN_PORT( addr ) cpu_in( addr )
|
||||
#define WRITE_MEM( addr, data ) cpu_write( addr, data )
|
||||
#define IDLE_ADDR idle_addr
|
||||
#define CPU cpu
|
||||
#define RST_BASE vectors_addr
|
||||
|
||||
#define CPU_BEGIN \
|
||||
bool Sgc_Impl::run_cpu( time_t end_time )\
|
||||
{\
|
||||
cpu.set_end_time( end_time );
|
||||
|
||||
#include "Z80_Cpu_run.h"
|
||||
|
||||
return warning;
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Sgc_Emu.h"
|
||||
|
||||
/* Copyright (C) 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 osc_count = Sms_Apu::osc_count + Sms_Fm_Apu::osc_count;
|
||||
|
||||
Sgc_Emu::Sgc_Emu()
|
||||
{
|
||||
set_type( gme_sgc_type );
|
||||
set_silence_lookahead( 6 );
|
||||
set_gain( 1.2 );
|
||||
}
|
||||
|
||||
Sgc_Emu::~Sgc_Emu() { }
|
||||
|
||||
void Sgc_Emu::unload()
|
||||
{
|
||||
core_.unload();
|
||||
Music_Emu::unload();
|
||||
}
|
||||
|
||||
// Track info
|
||||
|
||||
static void copy_sgc_fields( Sgc_Emu::header_t const& h, track_info_t* out )
|
||||
{
|
||||
GME_COPY_FIELD( h, out, game );
|
||||
GME_COPY_FIELD( h, out, author );
|
||||
GME_COPY_FIELD( h, out, copyright );
|
||||
}
|
||||
|
||||
static void hash_sgc_file( Sgc_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.rate, sizeof(h.rate) );
|
||||
out.hash_( &h.reserved1[0], sizeof(h.reserved1) );
|
||||
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.reserved2[0], sizeof(h.reserved2) );
|
||||
out.hash_( &h.rst_addrs[0], sizeof(h.rst_addrs) );
|
||||
out.hash_( &h.mapping[0], sizeof(h.mapping) );
|
||||
out.hash_( &h.first_song, sizeof(h.first_song) );
|
||||
out.hash_( &h.song_count, sizeof(h.song_count) );
|
||||
out.hash_( &h.first_effect, sizeof(h.first_effect) );
|
||||
out.hash_( &h.last_effect, sizeof(h.last_effect) );
|
||||
out.hash_( &h.system, sizeof(h.system) );
|
||||
out.hash_( &h.reserved3[0], sizeof(h.reserved3) );
|
||||
out.hash_( data, data_size );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Emu::track_info_( track_info_t* out, int ) const
|
||||
{
|
||||
copy_sgc_fields( header(), out );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
struct Sgc_File : Gme_Info_
|
||||
{
|
||||
Sgc_Emu::header_t const* h;
|
||||
|
||||
Sgc_File() { set_type( gme_sgc_type ); }
|
||||
|
||||
blargg_err_t load_mem_( byte const begin [], int size )
|
||||
{
|
||||
h = ( Sgc_Emu::header_t const* ) begin;
|
||||
|
||||
set_track_count( h->song_count );
|
||||
if ( !h->valid_tag() )
|
||||
return blargg_err_file_type;
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t track_info_( track_info_t* out, int ) const
|
||||
{
|
||||
copy_sgc_fields( *h, out );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t hash_( Hash_Function& out ) const
|
||||
{
|
||||
hash_sgc_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out );
|
||||
return blargg_ok;
|
||||
}
|
||||
};
|
||||
|
||||
static Music_Emu* new_sgc_emu () { return BLARGG_NEW Sgc_Emu ; }
|
||||
static Music_Emu* new_sgc_file() { return BLARGG_NEW Sgc_File; }
|
||||
|
||||
gme_type_t_ const gme_sgc_type [1] = {{ "Z80 PSG", 0, &new_sgc_emu, &new_sgc_file, "SGC", 1 }};
|
||||
|
||||
// Setup
|
||||
|
||||
blargg_err_t Sgc_Emu::load_( Data_Reader& in )
|
||||
{
|
||||
RETURN_ERR( core_.load( in ) );
|
||||
set_warning( core_.warning() );
|
||||
set_track_count( header().song_count );
|
||||
set_voice_count( core_.sega_mapping() ? osc_count : core_.apu().osc_count );
|
||||
|
||||
core_.apu ().volume( gain() );
|
||||
core_.fm_apu().volume( gain() );
|
||||
|
||||
static const char* const names [osc_count + 1] = {
|
||||
"Square 1", "Square 2", "Square 3", "Noise", "FM"
|
||||
};
|
||||
set_voice_names( names );
|
||||
|
||||
static int const types [osc_count + 1] = {
|
||||
wave_type+1, wave_type+2, wave_type+3, mixed_type+1, mixed_type+2
|
||||
};
|
||||
set_voice_types( types );
|
||||
|
||||
return setup_buffer( core_.clock_rate() );
|
||||
}
|
||||
|
||||
void Sgc_Emu::update_eq( blip_eq_t const& eq )
|
||||
{
|
||||
core_.apu ().treble_eq( eq );
|
||||
core_.fm_apu().treble_eq( eq );
|
||||
}
|
||||
|
||||
void Sgc_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r )
|
||||
{
|
||||
if ( i < core_.apu().osc_count )
|
||||
core_.apu().set_output( i, c, l, r );
|
||||
else
|
||||
core_.fm_apu().set_output( c, l, r );
|
||||
}
|
||||
|
||||
void Sgc_Emu::set_tempo_( double t )
|
||||
{
|
||||
core_.set_tempo( t );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Emu::start_track_( int track )
|
||||
{
|
||||
RETURN_ERR( core_.start_track( track ) );
|
||||
return Classic_Emu::start_track_( track );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Emu::run_clocks( blip_time_t& duration, int )
|
||||
{
|
||||
RETURN_ERR( core_.end_frame( duration ) );
|
||||
set_warning( core_.warning() );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Emu::hash_( Hash_Function& out ) const
|
||||
{
|
||||
hash_sgc_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out );
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Sega/Game Gear/Coleco SGC music file emulator
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef SGC_EMU_H
|
||||
#define SGC_EMU_H
|
||||
|
||||
#include "Classic_Emu.h"
|
||||
#include "Sgc_Core.h"
|
||||
|
||||
class Sgc_Emu : public Classic_Emu {
|
||||
public:
|
||||
// SGC file header (see Sgc_Impl.h)
|
||||
typedef Sgc_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;
|
||||
|
||||
// Sets 0x2000-byte Coleco BIOS. Necessary to play Coleco tracks.
|
||||
static void set_coleco_bios( void const* p ){ Sgc_Core::set_coleco_bios( p ); }
|
||||
|
||||
static gme_type_t static_type() { return gme_sgc_type; }
|
||||
|
||||
// Internal
|
||||
public:
|
||||
Sgc_Emu();
|
||||
~Sgc_Emu();
|
||||
|
||||
protected:
|
||||
// Classic_Emu overrides
|
||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
virtual blargg_err_t start_track_( int );
|
||||
virtual blargg_err_t run_clocks( blip_time_t&, int );
|
||||
virtual void set_tempo_( double );
|
||||
virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* );
|
||||
virtual void update_eq( blip_eq_t const& );
|
||||
virtual void unload();
|
||||
|
||||
private:
|
||||
Sgc_Core core_;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,225 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Sgc_Impl.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"
|
||||
|
||||
void const* Sgc_Impl::coleco_bios;
|
||||
|
||||
Sgc_Impl::Sgc_Impl() :
|
||||
rom( bank_size )
|
||||
{
|
||||
assert( offsetof (header_t,copyright [32]) == header_t::size );
|
||||
}
|
||||
|
||||
Sgc_Impl::~Sgc_Impl()
|
||||
{ }
|
||||
|
||||
bool Sgc_Impl::header_t::valid_tag() const
|
||||
{
|
||||
return 0 == memcmp( tag, "SGC\x1A", 4 );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Impl::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 )
|
||||
set_warning( "Unknown file version" );
|
||||
|
||||
if ( header_.system > 2 )
|
||||
set_warning( "Unknown system" );
|
||||
|
||||
addr_t load_addr = get_le16( header_.load_addr );
|
||||
if ( load_addr < 0x400 )
|
||||
set_warning( "Invalid load address" );
|
||||
|
||||
rom.set_addr( load_addr );
|
||||
play_period = clock_rate() / 60;
|
||||
|
||||
if ( sega_mapping() )
|
||||
{
|
||||
RETURN_ERR( ram.resize( 0x2000 + Sgc_Cpu::page_padding ) );
|
||||
RETURN_ERR( ram2.resize( bank_size + Sgc_Cpu::page_padding ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
RETURN_ERR( ram.resize( 0x400 + Sgc_Cpu::page_padding ) );
|
||||
}
|
||||
|
||||
RETURN_ERR( vectors.resize( Sgc_Cpu::page_size + Sgc_Cpu::page_padding ) );
|
||||
|
||||
// TODO: doesn't need to be larger than page size, if we do mapping calls right
|
||||
RETURN_ERR( unmapped_write.resize( bank_size ) );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Sgc_Impl::unload()
|
||||
{
|
||||
rom.clear();
|
||||
vectors.clear();
|
||||
ram.clear();
|
||||
ram2.clear();
|
||||
unmapped_write.clear();
|
||||
Gme_Loader::unload();
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Impl::start_track( int track )
|
||||
{
|
||||
memset( ram .begin(), 0, ram .size() );
|
||||
memset( ram2.begin(), 0, ram2.size() );
|
||||
memset( vectors.begin(), 0xFF, vectors.size() );
|
||||
cpu.reset( unmapped_write.begin(), rom.unmapped() );
|
||||
|
||||
if ( sega_mapping() )
|
||||
{
|
||||
vectors_addr = 0x10000 - Sgc_Cpu::page_size;
|
||||
idle_addr = vectors_addr;
|
||||
for ( int i = 1; i < 8; ++i )
|
||||
{
|
||||
vectors [i*8 + 0] = 0xC3; // JP addr
|
||||
vectors [i*8 + 1] = header_.rst_addrs [i*2 + 0];
|
||||
vectors [i*8 + 2] = header_.rst_addrs [i*2 + 1];
|
||||
}
|
||||
|
||||
cpu.map_mem( 0xC000, 0x2000, ram.begin() );
|
||||
cpu.map_mem( vectors_addr, cpu.page_size, unmapped_write.begin(), vectors.begin() );
|
||||
|
||||
bank2 = NULL;
|
||||
for ( int i = 0; i < 4; ++i )
|
||||
cpu_write( 0xFFFC + i, header_.mapping [i] );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !coleco_bios )
|
||||
return BLARGG_ERR( BLARGG_ERR_CALLER, "Coleco BIOS not set" );
|
||||
|
||||
vectors_addr = 0;
|
||||
cpu.map_mem( 0, 0x2000, unmapped_write.begin(), coleco_bios );
|
||||
for ( int i = 0; i < 8; ++i )
|
||||
cpu.map_mem( 0x6000 + i*0x400, 0x400, ram.begin() );
|
||||
|
||||
idle_addr = 0x2000;
|
||||
cpu.map_mem( 0x2000, cpu.page_size, unmapped_write.begin(), vectors.begin() );
|
||||
|
||||
for ( int i = 0; i < 0x8000 / bank_size; ++i )
|
||||
{
|
||||
int addr = 0x8000 + i*bank_size;
|
||||
cpu.map_mem( addr, bank_size, unmapped_write.begin(), rom.at_addr( addr ) );
|
||||
}
|
||||
}
|
||||
|
||||
cpu.r.sp = get_le16( header_.stack_ptr );
|
||||
cpu.r.b.a = track;
|
||||
next_play = play_period;
|
||||
|
||||
jsr( header_.init_addr );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
// Emulation
|
||||
|
||||
void Sgc_Impl::jsr( byte const (&addr) [2] )
|
||||
{
|
||||
*cpu.write( --cpu.r.sp ) = idle_addr >> 8;
|
||||
*cpu.write( --cpu.r.sp ) = idle_addr & 0xFF;
|
||||
cpu.r.pc = get_le16( addr );
|
||||
}
|
||||
|
||||
void Sgc_Impl::set_bank( int bank, void const* data )
|
||||
{
|
||||
//dprintf( "map bank %d to %p\n", bank, (byte*) data - rom.at_addr( 0 ) );
|
||||
cpu.map_mem( bank * bank_size, bank_size, unmapped_write.begin(), data );
|
||||
}
|
||||
|
||||
void Sgc_Impl::cpu_write( addr_t addr, int data )
|
||||
{
|
||||
if ( (addr ^ 0xFFFC) > 3 || !sega_mapping() )
|
||||
{
|
||||
*cpu.write( addr ) = data;
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( addr )
|
||||
{
|
||||
case 0xFFFC:
|
||||
cpu.map_mem( 2 * bank_size, bank_size, ram2.begin() );
|
||||
if ( data & 0x08 )
|
||||
break;
|
||||
|
||||
bank2 = ram2.begin();
|
||||
// FALL THROUGH
|
||||
|
||||
case 0xFFFF: {
|
||||
bool rom_mapped = (cpu.read( 2 * bank_size ) == bank2);
|
||||
bank2 = rom.at_addr( data * bank_size );
|
||||
if ( rom_mapped )
|
||||
set_bank( 2, bank2 );
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xFFFD:
|
||||
set_bank( 0, rom.at_addr( data * bank_size ) );
|
||||
break;
|
||||
|
||||
case 0xFFFE:
|
||||
set_bank( 1, rom.at_addr( data * bank_size ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int Sgc_Impl::cpu_in( addr_t addr )
|
||||
{
|
||||
dprintf( "in %02X\n", addr );
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Sgc_Impl::cpu_out( time_t, addr_t addr, int )
|
||||
{
|
||||
dprintf( "out %02X\n", addr & 0xFF );
|
||||
}
|
||||
|
||||
blargg_err_t Sgc_Impl::end_frame( time_t end )
|
||||
{
|
||||
while ( cpu.time() < end )
|
||||
{
|
||||
time_t next = min( end, next_play );
|
||||
if ( run_cpu( next ) )
|
||||
{
|
||||
set_warning( "Unsupported CPU instruction" );
|
||||
cpu.set_time( 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 )
|
||||
jsr( header_.play_addr );
|
||||
}
|
||||
}
|
||||
|
||||
next_play -= end;
|
||||
check( next_play >= 0 );
|
||||
cpu.adjust_time( -end );
|
||||
|
||||
return blargg_ok;
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// Sega/Game Gear/Coleco SGC music file emulator implementation internals
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef SGC_IMPL_H
|
||||
#define SGC_IMPL_H
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
#include "Rom_Data.h"
|
||||
#include "Z80_Cpu.h"
|
||||
|
||||
class Sgc_Impl : public Gme_Loader {
|
||||
public:
|
||||
|
||||
// SGC file header
|
||||
struct header_t
|
||||
{
|
||||
enum { size = 0xA0 };
|
||||
|
||||
char tag [4]; // "SGC\x1A"
|
||||
byte vers; // 0x01
|
||||
byte rate; // 0=NTSC 1=PAL
|
||||
byte reserved1 [2];
|
||||
byte load_addr [2];
|
||||
byte init_addr [2];
|
||||
byte play_addr [2];
|
||||
byte stack_ptr [2];
|
||||
byte reserved2 [2];
|
||||
byte rst_addrs [7*2];
|
||||
byte mapping [4]; // Used by Sega only
|
||||
byte first_song; // Song to start playing first
|
||||
byte song_count;
|
||||
byte first_effect;
|
||||
byte last_effect;
|
||||
byte system; // 0=Master System 1=Game Gear 2=Colecovision
|
||||
byte reserved3 [23];
|
||||
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;
|
||||
|
||||
int effect_count() const { return last_effect ? last_effect - first_effect + 1 : 0; }
|
||||
};
|
||||
|
||||
// Header for currently loaded file
|
||||
header_t const& header() const { return header_; }
|
||||
|
||||
Rom_Data const& rom_() const { return rom; }
|
||||
|
||||
int clock_rate() const { return header_.rate ? 3546893 : 3579545; }
|
||||
|
||||
// 0x2000 bytes
|
||||
static void set_coleco_bios( void const* p ) { coleco_bios = p; }
|
||||
|
||||
// Clocks between calls to play routine
|
||||
typedef int time_t;
|
||||
void set_play_period( time_t p ) { play_period = p; }
|
||||
|
||||
// 0 = first track
|
||||
blargg_err_t start_track( int );
|
||||
|
||||
// Runs for t clocks
|
||||
blargg_err_t end_frame( time_t t );
|
||||
|
||||
// True if Master System or Game Gear
|
||||
bool sega_mapping() const;
|
||||
|
||||
protected:
|
||||
typedef Z80_Cpu Sgc_Cpu;
|
||||
Sgc_Cpu cpu;
|
||||
|
||||
typedef int addr_t;
|
||||
virtual void cpu_out( time_t, addr_t, int data ) BLARGG_PURE( ; )
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Sgc_Impl();
|
||||
~Sgc_Impl();
|
||||
virtual void unload();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_( Data_Reader& );
|
||||
|
||||
private:
|
||||
enum { bank_size = 0x4000 };
|
||||
|
||||
Rom_Data rom;
|
||||
time_t play_period;
|
||||
time_t next_play;
|
||||
void const* bank2; // ROM selected for bank 2, in case RAM is currently hiding it
|
||||
addr_t vectors_addr; // RST vectors start here
|
||||
addr_t idle_addr; // return address for init/play routines
|
||||
static void const* coleco_bios;
|
||||
|
||||
// large items
|
||||
header_t header_;
|
||||
blargg_vector<byte> vectors;
|
||||
blargg_vector<byte> ram;
|
||||
blargg_vector<byte> ram2;
|
||||
blargg_vector<byte> unmapped_write;
|
||||
|
||||
bool run_cpu( time_t end );
|
||||
void jsr( byte const (&addr) [2] );
|
||||
void cpu_write( addr_t, int data );
|
||||
int cpu_in( addr_t );
|
||||
|
||||
void set_bank( int bank, void const* data );
|
||||
};
|
||||
|
||||
inline bool Sgc_Impl::sega_mapping() const
|
||||
{
|
||||
return header_.system <= 1;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,80 @@
|
|||
#include "Sms_Fm_Apu.h"
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
Sms_Fm_Apu::Sms_Fm_Apu()
|
||||
{ }
|
||||
|
||||
Sms_Fm_Apu::~Sms_Fm_Apu()
|
||||
{ }
|
||||
|
||||
blargg_err_t Sms_Fm_Apu::init( double clock_rate, double sample_rate )
|
||||
{
|
||||
period_ = clock_rate / sample_rate + 0.5;
|
||||
CHECK_ALLOC( !apu.set_rate( sample_rate, clock_rate ) );
|
||||
|
||||
set_output( 0 );
|
||||
volume( 1.0 );
|
||||
reset();
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Sms_Fm_Apu::reset()
|
||||
{
|
||||
addr = 0;
|
||||
next_time = 0;
|
||||
last_amp = 0;
|
||||
|
||||
apu.reset();
|
||||
}
|
||||
|
||||
void Sms_Fm_Apu::write_data( blip_time_t time, int data )
|
||||
{
|
||||
if ( time > next_time )
|
||||
run_until( time );
|
||||
|
||||
apu.write( addr, data );
|
||||
}
|
||||
|
||||
void Sms_Fm_Apu::run_until( blip_time_t end_time )
|
||||
{
|
||||
assert( end_time > next_time );
|
||||
|
||||
Blip_Buffer* const output = this->output_;
|
||||
if ( !output )
|
||||
{
|
||||
next_time = end_time;
|
||||
return;
|
||||
}
|
||||
|
||||
blip_time_t time = next_time;
|
||||
do
|
||||
{
|
||||
Ym2413_Emu::sample_t samples [2] = {0};
|
||||
apu.run( 1, samples );
|
||||
int amp = (samples [0] + samples [1]) >> 1;
|
||||
|
||||
int delta = amp - last_amp;
|
||||
if ( delta )
|
||||
{
|
||||
last_amp = amp;
|
||||
synth.offset_inline( time, delta, output );
|
||||
}
|
||||
time += period_;
|
||||
}
|
||||
while ( time < end_time );
|
||||
|
||||
next_time = time;
|
||||
}
|
||||
|
||||
void Sms_Fm_Apu::end_frame( blip_time_t time )
|
||||
{
|
||||
if ( time > next_time )
|
||||
run_until( time );
|
||||
|
||||
next_time -= time;
|
||||
assert( next_time >= 0 );
|
||||
|
||||
if ( output_ )
|
||||
output_->set_modified();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#ifndef SMS_FM_APU_H
|
||||
#define SMS_FM_APU_H
|
||||
|
||||
#include "Blip_Buffer.h"
|
||||
#include "Ym2413_Emu.h"
|
||||
|
||||
class Sms_Fm_Apu {
|
||||
public:
|
||||
static bool supported() { return Ym2413_Emu::supported(); }
|
||||
blargg_err_t init( double clock_rate, double sample_rate );
|
||||
|
||||
void set_output( Blip_Buffer* b, Blip_Buffer* = NULL, Blip_Buffer* = NULL ) { output_ = b; }
|
||||
void volume( double v ) { synth.volume( 0.4 / 4096 * v ); }
|
||||
void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); }
|
||||
|
||||
void reset();
|
||||
|
||||
void write_addr( int data ) { addr = data; }
|
||||
void write_data( blip_time_t, int data );
|
||||
|
||||
void end_frame( blip_time_t t );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Sms_Fm_Apu();
|
||||
~Sms_Fm_Apu();
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
enum { osc_count = 1 };
|
||||
void set_output( int i, Blip_Buffer* b, Blip_Buffer* = NULL, Blip_Buffer* = NULL ) { output_ = b; }
|
||||
|
||||
private:
|
||||
Blip_Buffer* output_;
|
||||
blip_time_t next_time;
|
||||
int last_amp;
|
||||
int addr;
|
||||
|
||||
int clock_;
|
||||
int rate_;
|
||||
blip_time_t period_;
|
||||
|
||||
Blip_Synth_Norm synth;
|
||||
Ym2413_Emu apu;
|
||||
|
||||
void run_until( blip_time_t );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,81 @@
|
|||
// snes_spc $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Filter.h"
|
||||
|
||||
/* Copyright (C) 2007 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
void Spc_Filter::clear() { memset( ch, 0, sizeof ch ); }
|
||||
|
||||
Spc_Filter::Spc_Filter()
|
||||
{
|
||||
enabled = true;
|
||||
gain = gain_unit;
|
||||
bass = bass_norm;
|
||||
clear();
|
||||
}
|
||||
|
||||
void Spc_Filter::run( short io [], int count )
|
||||
{
|
||||
require( (count & 1) == 0 ); // must be even
|
||||
|
||||
int const gain = this->gain;
|
||||
if ( enabled )
|
||||
{
|
||||
int const bass = this->bass;
|
||||
chan_t* c = &ch [2];
|
||||
do
|
||||
{
|
||||
// cache in registers
|
||||
int sum = (--c)->sum;
|
||||
int pp1 = c->pp1;
|
||||
int p1 = c->p1;
|
||||
|
||||
for ( int i = 0; i < count; i += 2 )
|
||||
{
|
||||
// Low-pass filter (two point FIR with coeffs 0.25, 0.75)
|
||||
int f = io [i] + p1;
|
||||
p1 = io [i] * 3;
|
||||
|
||||
// High-pass filter ("leaky integrator")
|
||||
int delta = f - pp1;
|
||||
pp1 = f;
|
||||
int s = sum >> (gain_bits + 2);
|
||||
sum += (delta * gain) - (sum >> bass);
|
||||
|
||||
// Clamp to 16 bits
|
||||
if ( (short) s != s )
|
||||
s = (s >> 31) ^ 0x7FFF;
|
||||
|
||||
io [i] = (short) s;
|
||||
}
|
||||
|
||||
c->p1 = p1;
|
||||
c->pp1 = pp1;
|
||||
c->sum = sum;
|
||||
++io;
|
||||
}
|
||||
while ( c != ch );
|
||||
}
|
||||
else if ( gain != gain_unit )
|
||||
{
|
||||
short* const end = io + count;
|
||||
while ( io < end )
|
||||
{
|
||||
int s = (*io * gain) >> gain_bits;
|
||||
if ( (short) s != s )
|
||||
s = (s >> 31) ^ 0x7FFF;
|
||||
*io++ = (short) s;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Simple low-pass and high-pass filter to better match sound output of a SNES
|
||||
|
||||
// snes_spc $vers
|
||||
#ifndef SPC_FILTER_H
|
||||
#define SPC_FILTER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
struct Spc_Filter {
|
||||
public:
|
||||
|
||||
// Filters count samples of stereo sound in place. Count must be a multiple of 2.
|
||||
typedef short sample_t;
|
||||
void run( sample_t io [], int count );
|
||||
|
||||
// Optional features
|
||||
|
||||
// Clears filter to silence
|
||||
void clear();
|
||||
|
||||
// Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit
|
||||
// are fine, since output is clamped to 16-bit sample range.
|
||||
enum { gain_unit = 0x100 };
|
||||
void set_gain( int gain );
|
||||
|
||||
// Enables/disables filtering (when disabled, gain is still applied)
|
||||
void enable( bool b );
|
||||
|
||||
// Sets amount of bass (logarithmic scale)
|
||||
enum { bass_none = 0 };
|
||||
enum { bass_norm = 8 }; // normal amount
|
||||
enum { bass_max = 31 };
|
||||
void set_bass( int bass );
|
||||
|
||||
public:
|
||||
Spc_Filter();
|
||||
BLARGG_DISABLE_NOTHROW
|
||||
private:
|
||||
enum { gain_bits = 8 };
|
||||
int gain;
|
||||
int bass;
|
||||
bool enabled;
|
||||
struct chan_t { int p1, pp1, sum; };
|
||||
chan_t ch [2];
|
||||
};
|
||||
|
||||
inline void Spc_Filter::enable( bool b ) { enabled = b; }
|
||||
|
||||
inline void Spc_Filter::set_gain( int g ) { gain = g; }
|
||||
|
||||
inline void Spc_Filter::set_bass( int b ) { bass = b; }
|
||||
|
||||
#endif
|
|
@ -0,0 +1,430 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Spc_Sfm.h"
|
||||
|
||||
#include "blargg_endian.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Copyright (C) 2004-2013 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
// TODO: support Spc_Filter's bass
|
||||
|
||||
Sfm_Emu::Sfm_Emu()
|
||||
{
|
||||
set_type( gme_sfm_type );
|
||||
set_gain( 1.4 );
|
||||
}
|
||||
|
||||
Sfm_Emu::~Sfm_Emu() { }
|
||||
|
||||
// Track info
|
||||
|
||||
static void hash_sfm_file( byte const* data, int data_size, Music_Emu::Hash_Function& out )
|
||||
{
|
||||
out.hash_( data, data_size );
|
||||
}
|
||||
|
||||
blargg_err_t Sfm_Emu::track_info_( track_info_t* out, int ) const
|
||||
{
|
||||
const char * title = metadata.enumValue("information:title");
|
||||
if (title) strncpy( out->song, title, 255 );
|
||||
else out->song[0] = 0;
|
||||
out->song[255] = '\0';
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
static blargg_err_t check_sfm_header( void const* header )
|
||||
{
|
||||
if ( memcmp( header, "SFM1", 4 ) )
|
||||
return blargg_err_file_type;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
struct Sfm_File : Gme_Info_
|
||||
{
|
||||
blargg_vector<byte> data;
|
||||
Bml_Parser metadata;
|
||||
|
||||
Sfm_File() { set_type( gme_sfm_type ); }
|
||||
|
||||
blargg_err_t load_( Data_Reader& in )
|
||||
{
|
||||
int file_size = in.remain();
|
||||
if ( file_size < Sfm_Emu::sfm_min_file_size )
|
||||
return blargg_err_file_type;
|
||||
RETURN_ERR( data.resize( file_size ) );
|
||||
RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) );
|
||||
RETURN_ERR( check_sfm_header( data.begin() ) );
|
||||
int metadata_size = get_le32( data.begin() + 4 );
|
||||
byte temp = data[ 8 + metadata_size ];
|
||||
data[ 8 + metadata_size ] = '\0';
|
||||
metadata.parseDocument( (const char *)data.begin() + 8 );
|
||||
data[ 8 + metadata_size ] = temp;
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t track_info_( track_info_t* out, int ) const
|
||||
{
|
||||
const char * title = metadata.enumValue("information:title");
|
||||
if (title) strncpy( out->song, title, 255 );
|
||||
else out->song[0] = 0;
|
||||
out->song[255] = '\0';
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t hash_( Hash_Function& out ) const
|
||||
{
|
||||
hash_sfm_file( data.begin(), data.end() - data.begin(), out );
|
||||
return blargg_ok;
|
||||
}
|
||||
};
|
||||
|
||||
static Music_Emu* new_sfm_emu () { return BLARGG_NEW Sfm_Emu ; }
|
||||
static Music_Emu* new_sfm_file() { return BLARGG_NEW Sfm_File; }
|
||||
|
||||
gme_type_t_ const gme_sfm_type [1] = {{ "Super Nintendo with log", 1, &new_sfm_emu, &new_sfm_file, "SFM", 0 }};
|
||||
|
||||
// Setup
|
||||
|
||||
blargg_err_t Sfm_Emu::set_sample_rate_( int sample_rate )
|
||||
{
|
||||
RETURN_ERR( apu.init() );
|
||||
if ( sample_rate != native_sample_rate )
|
||||
{
|
||||
RETURN_ERR( resampler.resize_buffer( native_sample_rate / 20 * 2 ) );
|
||||
RETURN_ERR( resampler.set_rate( (double) native_sample_rate / sample_rate ) ); // 0.9965 rolloff
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
void Sfm_Emu::mute_voices_( int m )
|
||||
{
|
||||
Music_Emu::mute_voices_( m );
|
||||
apu.mute_voices( m );
|
||||
}
|
||||
|
||||
blargg_err_t Sfm_Emu::load_mem_( byte const in [], int size )
|
||||
{
|
||||
set_voice_count( Spc_Dsp::voice_count );
|
||||
if ( size < Sfm_Emu::sfm_min_file_size )
|
||||
return blargg_err_file_type;
|
||||
|
||||
static const char* const names [Spc_Dsp::voice_count] = {
|
||||
"DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8"
|
||||
};
|
||||
set_voice_names( names );
|
||||
|
||||
return check_sfm_header( in );
|
||||
}
|
||||
|
||||
// Emulation
|
||||
|
||||
void Sfm_Emu::set_tempo_( double t )
|
||||
{
|
||||
apu.set_tempo( (int) (t * Snes_Spc::tempo_unit) );
|
||||
}
|
||||
|
||||
// (n ? n : 256)
|
||||
#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1)
|
||||
|
||||
#define META_ENUM_INT(n) (value = metadata.enumValue(n), value ? strtoul(value, &end, 10) : 0)
|
||||
|
||||
static const byte ipl_rom[0x40] =
|
||||
{
|
||||
0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0,
|
||||
0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78,
|
||||
0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4,
|
||||
0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5,
|
||||
0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB,
|
||||
0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA,
|
||||
0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD,
|
||||
0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF
|
||||
};
|
||||
|
||||
blargg_err_t Sfm_Emu::start_track_( int track )
|
||||
{
|
||||
RETURN_ERR( Music_Emu::start_track_( track ) );
|
||||
resampler.clear();
|
||||
filter.clear();
|
||||
const byte * ptr = file_begin();
|
||||
int metadata_size = get_le32(ptr + 4);
|
||||
if ( file_size() < metadata_size + Sfm_Emu::sfm_min_file_size )
|
||||
return "SFM file too small";
|
||||
char * temp = new char[metadata_size + 1];
|
||||
temp[metadata_size] = '\0';
|
||||
memcpy(temp, ptr + 8, metadata_size);
|
||||
metadata.parseDocument(temp);
|
||||
delete [] temp;
|
||||
|
||||
apu.init_rom( ipl_rom );
|
||||
|
||||
apu.reset();
|
||||
|
||||
memcpy( apu.m.ram.ram, ptr + 8 + metadata_size, 65536 );
|
||||
|
||||
memcpy( apu.dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 );
|
||||
|
||||
apu.set_sfm_queue( ptr + 8 + metadata_size + 65536 + 128, ptr + file_size() );
|
||||
|
||||
byte regs[Snes_Spc::reg_count] = {0};
|
||||
|
||||
char * end;
|
||||
const char * value;
|
||||
|
||||
regs[Snes_Spc::r_test] = META_ENUM_INT("smp:test");
|
||||
regs[Snes_Spc::r_control] |= META_ENUM_INT("smp:iplrom") ? 0x80 : 0;
|
||||
regs[Snes_Spc::r_dspaddr] = META_ENUM_INT("smp:dspaddr");
|
||||
|
||||
value = metadata.enumValue("smp:ram");
|
||||
if (value)
|
||||
{
|
||||
regs[Snes_Spc::r_f8] = strtoul(value, &end, 10);
|
||||
if (*end)
|
||||
{
|
||||
value = end + 1;
|
||||
regs[Snes_Spc::r_f9] = strtoul(value, &end, 10);
|
||||
}
|
||||
}
|
||||
|
||||
char temp_path[256];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
sprintf(temp_path, "smp:timer[%u]:", i);
|
||||
size_t length = strlen(temp_path);
|
||||
strcpy(temp_path + length, "enable");
|
||||
value = metadata.enumValue(temp_path);
|
||||
if (value)
|
||||
{
|
||||
regs[Snes_Spc::r_control] |= strtoul(value, &end, 10) ? 1 << i : 0;
|
||||
}
|
||||
strcpy(temp_path + length, "target");
|
||||
value = metadata.enumValue(temp_path);
|
||||
if (value)
|
||||
{
|
||||
regs[Snes_Spc::r_t0target + i] = strtoul(value, &end, 10);
|
||||
}
|
||||
strcpy(temp_path + length, "stage");
|
||||
value = metadata.enumValue(temp_path);
|
||||
if (value)
|
||||
{
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
if (value) value = strchr(value, ',');
|
||||
if (value) ++value;
|
||||
}
|
||||
if (value)
|
||||
{
|
||||
regs[Snes_Spc::r_t0out + i] = strtoul(value, &end, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apu.load_regs( regs );
|
||||
apu.m.rom_enabled = 0;
|
||||
apu.regs_loaded();
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
sprintf(temp_path, "smp:timer[%u]:", i);
|
||||
size_t length = strlen(temp_path);
|
||||
strcpy(temp_path + length, "stage");
|
||||
value = metadata.enumValue(temp_path);
|
||||
if (value)
|
||||
{
|
||||
const char * stage = value;
|
||||
apu.m.timers[i].next_time = strtoul(stage, &end, 10) + 1;
|
||||
for (int j = 0; j < 2; ++j)
|
||||
{
|
||||
if (stage) stage = strchr(stage, ',');
|
||||
if (stage) ++stage;
|
||||
}
|
||||
if (stage)
|
||||
{
|
||||
apu.m.timers[i].divider = strtoul(value, &end, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apu.dsp.m.echo_hist_pos = &apu.dsp.m.echo_hist[META_ENUM_INT("dsp:echohistaddr")];
|
||||
|
||||
value = metadata.enumValue("dsp:echohistdata");
|
||||
if (value)
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
apu.dsp.m.echo_hist[i][0] = strtoul(value, &end, 10);
|
||||
value = strchr(value, ',');
|
||||
if (!value) break;
|
||||
++value;
|
||||
apu.dsp.m.echo_hist[i][1] = strtoul(value, &end, 10);
|
||||
value = strchr(value, ',');
|
||||
if (!value) break;
|
||||
++value;
|
||||
}
|
||||
}
|
||||
|
||||
apu.dsp.m.phase = META_ENUM_INT("dsp:sample");
|
||||
apu.dsp.m.kon = META_ENUM_INT("dsp:kon");
|
||||
apu.dsp.m.noise = META_ENUM_INT("dsp:noise");
|
||||
apu.dsp.m.counter = META_ENUM_INT("dsp:counter");
|
||||
apu.dsp.m.echo_offset = META_ENUM_INT("dsp:echooffset");
|
||||
apu.dsp.m.echo_length = META_ENUM_INT("dsp:echolength");
|
||||
apu.dsp.m.new_kon = META_ENUM_INT("dsp:koncache");
|
||||
apu.dsp.m.endx_buf = META_ENUM_INT("dsp:endx");
|
||||
apu.dsp.m.envx_buf = META_ENUM_INT("dsp:envx");
|
||||
apu.dsp.m.outx_buf = META_ENUM_INT("dsp:outx");
|
||||
apu.dsp.m.t_pmon = META_ENUM_INT("dsp:pmon");
|
||||
apu.dsp.m.t_non = META_ENUM_INT("dsp:non");
|
||||
apu.dsp.m.t_eon = META_ENUM_INT("dsp:eon");
|
||||
apu.dsp.m.t_dir = META_ENUM_INT("dsp:dir");
|
||||
apu.dsp.m.t_koff = META_ENUM_INT("dsp:koff");
|
||||
apu.dsp.m.t_brr_next_addr = META_ENUM_INT("dsp:brrnext");
|
||||
apu.dsp.m.t_adsr0 = META_ENUM_INT("dsp:adsr0");
|
||||
apu.dsp.m.t_brr_header = META_ENUM_INT("dsp:brrheader");
|
||||
apu.dsp.m.t_brr_byte = META_ENUM_INT("dsp:brrdata");
|
||||
apu.dsp.m.t_srcn = META_ENUM_INT("dsp:srcn");
|
||||
apu.dsp.m.t_esa = META_ENUM_INT("dsp:esa");
|
||||
apu.dsp.m.t_echo_enabled = !META_ENUM_INT("dsp:echodisable");
|
||||
apu.dsp.m.t_dir_addr = META_ENUM_INT("dsp:diraddr");
|
||||
apu.dsp.m.t_pitch = META_ENUM_INT("dsp:pitch");
|
||||
apu.dsp.m.t_output = META_ENUM_INT("dsp:output");
|
||||
apu.dsp.m.t_looped = META_ENUM_INT("dsp:looped");
|
||||
apu.dsp.m.t_echo_ptr = META_ENUM_INT("dsp:echoaddr");
|
||||
|
||||
|
||||
#define META_ENUM_LEVELS(n, o) \
|
||||
value = metadata.enumValue(n); \
|
||||
if (value) \
|
||||
{ \
|
||||
(o)[0] = strtoul(value, &end, 10); \
|
||||
if (*end) \
|
||||
{ \
|
||||
value = end + 1; \
|
||||
(o)[1] = strtoul(value, &end, 10); \
|
||||
} \
|
||||
}
|
||||
|
||||
META_ENUM_LEVELS("dsp:mainout", apu.dsp.m.t_main_out);
|
||||
META_ENUM_LEVELS("dsp:echoout", apu.dsp.m.t_echo_out);
|
||||
META_ENUM_LEVELS("dsp:echoin", apu.dsp.m.t_echo_in);
|
||||
|
||||
#undef META_ENUM_LEVELS
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
sprintf(temp_path, "dsp:voice[%u]:", i);
|
||||
size_t length = strlen(temp_path);
|
||||
Spc_Dsp::voice_t & voice = apu.dsp.m.voices[i];
|
||||
strcpy(temp_path + length, "brrhistaddr");
|
||||
value = metadata.enumValue(temp_path);
|
||||
if (value)
|
||||
{
|
||||
voice.buf_pos = strtoul(value, &end, 10);
|
||||
}
|
||||
strcpy(temp_path + length, "brrhistdata");
|
||||
value = metadata.enumValue(temp_path);
|
||||
if (value)
|
||||
{
|
||||
for (int j = 0; j < Spc_Dsp::brr_buf_size; ++j)
|
||||
{
|
||||
voice.buf[j] = voice.buf[j + Spc_Dsp::brr_buf_size] = strtoul(value, &end, 10);
|
||||
if (!*end) break;
|
||||
value = end + 1;
|
||||
}
|
||||
}
|
||||
strcpy(temp_path + length, "interpaddr");
|
||||
voice.interp_pos = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "brraddr");
|
||||
voice.brr_addr = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "brroffset");
|
||||
voice.brr_offset = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "vbit");
|
||||
voice.vbit = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "vidx");
|
||||
voice.regs = &apu.dsp.m.regs[META_ENUM_INT(temp_path)];
|
||||
strcpy(temp_path + length, "kondelay");
|
||||
voice.kon_delay = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "envmode");
|
||||
voice.env_mode = (Spc_Dsp::env_mode_t) META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "env");
|
||||
voice.env = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "envxout");
|
||||
voice.t_envx_out = META_ENUM_INT(temp_path);
|
||||
strcpy(temp_path + length, "envcache");
|
||||
voice.hidden_env = META_ENUM_INT(temp_path);
|
||||
}
|
||||
|
||||
filter.set_gain( (int) (gain() * Spc_Filter::gain_unit) );
|
||||
apu.clear_echo( true );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
#undef META_ENUM_INT
|
||||
|
||||
blargg_err_t Sfm_Emu::play_and_filter( int count, sample_t out [] )
|
||||
{
|
||||
RETURN_ERR( apu.play( count, out ) );
|
||||
filter.run( out, count );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Sfm_Emu::skip_( int count )
|
||||
{
|
||||
if ( sample_rate() != native_sample_rate )
|
||||
{
|
||||
count = (int) (count * resampler.rate()) & ~1;
|
||||
count -= resampler.skip_input( count );
|
||||
}
|
||||
|
||||
// TODO: shouldn't skip be adjusted for the 64 samples read afterwards?
|
||||
|
||||
if ( count > 0 )
|
||||
{
|
||||
RETURN_ERR( apu.skip( count ) );
|
||||
filter.clear();
|
||||
}
|
||||
|
||||
// eliminate pop due to resampler
|
||||
const int resampler_latency = 64;
|
||||
sample_t buf [resampler_latency];
|
||||
return play_( resampler_latency, buf );
|
||||
}
|
||||
|
||||
blargg_err_t Sfm_Emu::play_( int count, sample_t out [] )
|
||||
{
|
||||
if ( sample_rate() == native_sample_rate )
|
||||
return play_and_filter( count, out );
|
||||
|
||||
int remain = count;
|
||||
while ( remain > 0 )
|
||||
{
|
||||
remain -= resampler.read( &out [count - remain], remain );
|
||||
if ( remain > 0 )
|
||||
{
|
||||
int n = resampler.buffer_free();
|
||||
RETURN_ERR( play_and_filter( n, resampler.buffer() ) );
|
||||
resampler.write( n );
|
||||
}
|
||||
}
|
||||
check( remain == 0 );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
blargg_err_t Sfm_Emu::hash_( Hash_Function& out ) const
|
||||
{
|
||||
hash_sfm_file( file_begin(), file_size(), out );
|
||||
return blargg_ok;
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Super Nintendo SFM music file emulator
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef SPC_SFM_H
|
||||
#define SPC_SFM_H
|
||||
|
||||
#include "Music_Emu.h"
|
||||
#include "Snes_Spc.h"
|
||||
#include "Spc_Filter.h"
|
||||
|
||||
#include "Bml_Parser.h"
|
||||
|
||||
#if GME_SPC_FAST_RESAMPLER
|
||||
#include "Upsampler.h"
|
||||
typedef Upsampler Spc_Emu_Resampler;
|
||||
#else
|
||||
#include "Fir_Resampler.h"
|
||||
typedef Fir_Resampler<24> Spc_Emu_Resampler;
|
||||
#endif
|
||||
|
||||
class Sfm_Emu : public Music_Emu {
|
||||
public:
|
||||
// Minimum allowed file size
|
||||
enum { sfm_min_file_size = 8 + 65536 + 128 };
|
||||
|
||||
// The Super Nintendo hardware samples at 32kHz. Other sample rates are
|
||||
// handled by resampling the 32kHz output; emulation accuracy is not affected.
|
||||
enum { native_sample_rate = 32000 };
|
||||
|
||||
// Disables annoying pseudo-surround effect some music uses
|
||||
void disable_surround( bool disable = true ) { apu.disable_surround( disable ); }
|
||||
|
||||
// Enables gaussian, cubic or sinc interpolation
|
||||
void interpolation_level( int level = 0 ) { apu.interpolation_level( level ); }
|
||||
|
||||
const Snes_Spc * get_apu() const;
|
||||
|
||||
blargg_err_t hash_( Hash_Function& ) const;
|
||||
|
||||
static gme_type_t static_type() { return gme_sfm_type; }
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Sfm_Emu();
|
||||
~Sfm_Emu();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_mem_( byte const [], int );
|
||||
virtual blargg_err_t track_info_( track_info_t*, int track ) const;
|
||||
virtual blargg_err_t set_sample_rate_( int );
|
||||
virtual blargg_err_t start_track_( int );
|
||||
virtual blargg_err_t play_( int, sample_t [] );
|
||||
virtual blargg_err_t skip_( int );
|
||||
virtual void mute_voices_( int );
|
||||
virtual void set_tempo_( double );
|
||||
|
||||
private:
|
||||
Spc_Emu_Resampler resampler;
|
||||
Spc_Filter filter;
|
||||
Snes_Spc apu;
|
||||
|
||||
Bml_Parser metadata;
|
||||
|
||||
blargg_err_t play_and_filter( int count, sample_t out [] );
|
||||
};
|
||||
|
||||
#endif // SPC_SFM_H
|
|
@ -0,0 +1,293 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Track_Filter.h"
|
||||
|
||||
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
|
||||
can redistribute it and/or modify it under the terms of the GNU Lesser
|
||||
General Public License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version. This
|
||||
module is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||
details. You should have received a copy of the GNU Lesser General Public
|
||||
License along with this module; if not, write to the Free Software Foundation,
|
||||
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
|
||||
|
||||
#include "blargg_source.h"
|
||||
|
||||
int const fade_block_size = 512;
|
||||
int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift)
|
||||
int const silence_threshold = 8;
|
||||
|
||||
blargg_err_t Track_Filter::init( callbacks_t* c )
|
||||
{
|
||||
callbacks = c;
|
||||
return buf.resize( buf_size );
|
||||
}
|
||||
|
||||
void Track_Filter::clear_time_vars()
|
||||
{
|
||||
emu_time = buf_remain;
|
||||
out_time = 0;
|
||||
silence_time = 0;
|
||||
silence_count = 0;
|
||||
}
|
||||
|
||||
void Track_Filter::stop()
|
||||
{
|
||||
emu_track_ended_ = true;
|
||||
track_ended_ = true;
|
||||
fade_start = indefinite_count;
|
||||
fade_step = 1;
|
||||
buf_remain = 0;
|
||||
emu_error = NULL;
|
||||
clear_time_vars();
|
||||
}
|
||||
|
||||
Track_Filter::Track_Filter() : setup_()
|
||||
{
|
||||
callbacks = NULL;
|
||||
setup_.max_silence = indefinite_count;
|
||||
silence_ignored_ = false;
|
||||
stop();
|
||||
}
|
||||
|
||||
Track_Filter::~Track_Filter() { }
|
||||
|
||||
blargg_err_t Track_Filter::start_track()
|
||||
{
|
||||
emu_error = NULL;
|
||||
stop();
|
||||
|
||||
emu_track_ended_ = false;
|
||||
track_ended_ = false;
|
||||
|
||||
if ( !silence_ignored_ )
|
||||
{
|
||||
// play until non-silence or end of track
|
||||
while ( emu_time < setup_.max_initial )
|
||||
{
|
||||
fill_buf();
|
||||
if ( buf_remain | emu_track_ended_ )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
clear_time_vars();
|
||||
return emu_error;
|
||||
}
|
||||
|
||||
void Track_Filter::end_track_if_error( blargg_err_t err )
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
emu_error = err;
|
||||
emu_track_ended_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
blargg_err_t Track_Filter::skip( int count )
|
||||
{
|
||||
emu_error = NULL;
|
||||
out_time += count;
|
||||
|
||||
// remove from silence and buf first
|
||||
{
|
||||
int n = min( count, silence_count );
|
||||
silence_count -= n;
|
||||
count -= n;
|
||||
|
||||
n = min( count, buf_remain );
|
||||
buf_remain -= n;
|
||||
count -= n;
|
||||
}
|
||||
|
||||
if ( count && !emu_track_ended_ )
|
||||
{
|
||||
emu_time += count;
|
||||
silence_time = emu_time; // would otherwise be invalid
|
||||
end_track_if_error( callbacks->skip_( count ) );
|
||||
}
|
||||
|
||||
if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended
|
||||
track_ended_ |= emu_track_ended_;
|
||||
|
||||
return emu_error;
|
||||
}
|
||||
|
||||
blargg_err_t Track_Filter::skip_( int count )
|
||||
{
|
||||
while ( count && !emu_track_ended_ )
|
||||
{
|
||||
int n = buf_size;
|
||||
if ( n > count )
|
||||
n = count;
|
||||
count -= n;
|
||||
RETURN_ERR( callbacks->play_( n, buf.begin() ) );
|
||||
}
|
||||
return blargg_ok;
|
||||
}
|
||||
|
||||
// Fading
|
||||
|
||||
void Track_Filter::set_fade( int start, int length )
|
||||
{
|
||||
fade_start = start;
|
||||
fade_step = length / (fade_block_size * fade_shift);
|
||||
if ( fade_step < 1 )
|
||||
fade_step = 1;
|
||||
}
|
||||
|
||||
bool Track_Filter::is_fading() const
|
||||
{
|
||||
return out_time >= fade_start && fade_start != indefinite_count;
|
||||
}
|
||||
|
||||
// unit / pow( 2.0, (double) x / step )
|
||||
static int int_log( int x, int step, int unit )
|
||||
{
|
||||
int shift = x / step;
|
||||
int fraction = (x - shift * step) * unit / step;
|
||||
return ((unit - fraction) + (fraction >> 1)) >> shift;
|
||||
}
|
||||
|
||||
void Track_Filter::handle_fade( sample_t out [], int out_count )
|
||||
{
|
||||
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 Track_Filter::emu_play( sample_t out [], int count )
|
||||
{
|
||||
emu_time += count;
|
||||
if ( !emu_track_ended_ )
|
||||
end_track_if_error( callbacks->play_( count, out ) );
|
||||
else
|
||||
memset( out, 0, count * sizeof *out );
|
||||
}
|
||||
|
||||
// number of consecutive silent samples at end
|
||||
static int count_silence( Track_Filter::sample_t begin [], int size )
|
||||
{
|
||||
Track_Filter::sample_t first = *begin;
|
||||
*begin = silence_threshold * 2; // sentinel
|
||||
Track_Filter::sample_t* p = begin + size;
|
||||
while ( (unsigned) (*--p + silence_threshold) <= (unsigned) silence_threshold * 2 ) { }
|
||||
*begin = first;
|
||||
return size - (p - begin);
|
||||
}
|
||||
|
||||
// fill internal buffer and check it for silence
|
||||
void Track_Filter::fill_buf()
|
||||
{
|
||||
assert( !buf_remain );
|
||||
if ( !emu_track_ended_ )
|
||||
{
|
||||
emu_play( buf.begin(), buf_size );
|
||||
int 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 Track_Filter::play( int out_count, sample_t out [] )
|
||||
{
|
||||
emu_error = NULL;
|
||||
if ( track_ended_ )
|
||||
{
|
||||
memset( out, 0, out_count * sizeof *out );
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( emu_time >= out_time );
|
||||
|
||||
// prints nifty graph of how far ahead we are when searching for silence
|
||||
//dprintf( "%*s \n", int ((emu_time - out_time) * 7 / 44100), "*" );
|
||||
|
||||
// use any remaining silence samples
|
||||
int pos = 0;
|
||||
if ( silence_count )
|
||||
{
|
||||
if ( !silence_ignored_ )
|
||||
{
|
||||
// during a run of silence, run emulator at >=2x speed so it gets ahead
|
||||
int ahead_time = setup_.lookahead * (out_time + out_count - silence_time) +
|
||||
silence_time;
|
||||
while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) )
|
||||
fill_buf();
|
||||
|
||||
// end track if sufficient silence has been found
|
||||
if ( emu_time - silence_time > setup_.max_silence )
|
||||
{
|
||||
track_ended_ = emu_track_ended_ = true;
|
||||
silence_count = out_count;
|
||||
buf_remain = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// fill from remaining silence
|
||||
pos = min( silence_count, out_count );
|
||||
memset( out, 0, pos * sizeof *out );
|
||||
silence_count -= pos;
|
||||
}
|
||||
|
||||
// use any remaining samples from buffer
|
||||
if ( buf_remain )
|
||||
{
|
||||
int n = min( buf_remain, (int) (out_count - pos) );
|
||||
memcpy( out + pos, buf.begin() + (buf_size - buf_remain), n * sizeof *out );
|
||||
buf_remain -= n;
|
||||
pos += n;
|
||||
}
|
||||
|
||||
// generate remaining samples normally
|
||||
int remain = out_count - pos;
|
||||
if ( remain )
|
||||
{
|
||||
emu_play( out + pos, remain );
|
||||
track_ended_ |= emu_track_ended_;
|
||||
|
||||
if ( silence_ignored_ && !is_fading() )
|
||||
{
|
||||
// if left unupdated, ahead_time could become too large
|
||||
silence_time = emu_time;
|
||||
}
|
||||
else
|
||||
{
|
||||
// check end for a new run of silence
|
||||
int 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 ( is_fading() )
|
||||
handle_fade( out, out_count );
|
||||
}
|
||||
out_time += out_count;
|
||||
return emu_error;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// Removes silence from beginning of track, fades end of track. Also looks ahead
|
||||
// for excessive silence, and if found, ends track.
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef TRACK_FILTER_H
|
||||
#define TRACK_FILTER_H
|
||||
|
||||
#include "blargg_common.h"
|
||||
|
||||
class Track_Filter {
|
||||
public:
|
||||
typedef int sample_count_t;
|
||||
typedef short sample_t;
|
||||
|
||||
enum { indefinite_count = INT_MAX/2 + 1 };
|
||||
|
||||
struct callbacks_t {
|
||||
// Samples may be stereo or mono
|
||||
virtual blargg_err_t play_( int count, sample_t* out ) BLARGG_PURE( { return blargg_ok; } )
|
||||
virtual blargg_err_t skip_( int count ) BLARGG_PURE( { return blargg_ok; } )
|
||||
virtual ~callbacks_t() { } // avoids silly "non-virtual dtor" warning
|
||||
};
|
||||
|
||||
// Initializes filter. Must be done once before using object.
|
||||
blargg_err_t init( callbacks_t* );
|
||||
|
||||
struct setup_t {
|
||||
sample_count_t max_initial; // maximum silence to strip from beginning of track
|
||||
sample_count_t max_silence; // maximum silence in middle of track without it ending
|
||||
int lookahead; // internal speed when looking ahead for silence (2=200% etc.)
|
||||
};
|
||||
|
||||
// Gets/sets setup
|
||||
setup_t const& setup() const { return setup_; }
|
||||
void setup( setup_t const& s ) { setup_ = s; }
|
||||
|
||||
// Disables automatic end-of-track detection and skipping of silence at beginning
|
||||
void ignore_silence( bool disable = true ) { silence_ignored_ = disable; }
|
||||
|
||||
// Clears state and skips initial silence in track
|
||||
blargg_err_t start_track();
|
||||
|
||||
// Sets time that fade starts, and how long until track ends.
|
||||
void set_fade( sample_count_t start, sample_count_t length );
|
||||
|
||||
// Generates n samples into buf
|
||||
blargg_err_t play( int n, sample_t buf [] );
|
||||
|
||||
// Skips n samples
|
||||
blargg_err_t skip( int n );
|
||||
|
||||
// Number of samples played/skipped since start_track()
|
||||
int sample_count() const { return out_time; }
|
||||
|
||||
// True if track ended. Causes are end of source samples, end of fade,
|
||||
// or excessive silence.
|
||||
bool track_ended() const { return track_ended_; }
|
||||
|
||||
// Clears state
|
||||
void stop();
|
||||
|
||||
// For use by callbacks
|
||||
|
||||
// Sets internal "track ended" flag and stops generation of further source samples
|
||||
void set_track_ended() { emu_track_ended_ = true; }
|
||||
|
||||
// For use by skip_() callback
|
||||
blargg_err_t skip_( int count );
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Track_Filter();
|
||||
~Track_Filter();
|
||||
|
||||
private:
|
||||
callbacks_t* callbacks;
|
||||
setup_t setup_;
|
||||
const char* emu_error;
|
||||
bool silence_ignored_;
|
||||
|
||||
// Timing
|
||||
int out_time; // number of samples played since start of track
|
||||
int emu_time; // number of samples emulator has generated since start of track
|
||||
int emu_track_ended_; // emulator has reached end of track
|
||||
volatile int track_ended_;
|
||||
void clear_time_vars();
|
||||
void end_track_if_error( blargg_err_t );
|
||||
|
||||
// Fading
|
||||
int fade_start;
|
||||
int fade_step;
|
||||
bool is_fading() const;
|
||||
void handle_fade( sample_t out [], int count );
|
||||
|
||||
// Silence detection
|
||||
int silence_time; // absolute number of samples where most recent silence began
|
||||
int silence_count; // number of samples of silence to play before using buf
|
||||
int buf_remain; // number of samples left in silence buffer
|
||||
enum { buf_size = 2048 };
|
||||
blargg_vector<sample_t> buf;
|
||||
void fill_buf();
|
||||
void emu_play( sample_t out [], int count );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,73 @@
|
|||
// $package. http://www.slack.net/~ant/
|
||||
|
||||
#include "Upsampler.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 = 15;
|
||||
int const unit = 1 << shift;
|
||||
|
||||
void Upsampler::clear_()
|
||||
{
|
||||
pos = 0;
|
||||
Resampler::clear_();
|
||||
}
|
||||
|
||||
Upsampler::Upsampler()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
blargg_err_t Upsampler::set_rate_( double new_factor )
|
||||
{
|
||||
step = (int) (new_factor * unit + 0.5);
|
||||
return Resampler::set_rate_( 1.0 / unit * step );
|
||||
}
|
||||
|
||||
Resampler::sample_t const* Upsampler::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;
|
||||
|
||||
do
|
||||
{
|
||||
#define INTERP( i, out )\
|
||||
{\
|
||||
int t = in [0 + i] * (unit - pos) + in [stereo + i] * pos;\
|
||||
out = t >> shift;\
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Increases sampling rate using linear interpolation
|
||||
|
||||
// $package
|
||||
#ifndef UPSAMPLER_H
|
||||
#define UPSAMPLER_H
|
||||
|
||||
#include "Resampler.h"
|
||||
|
||||
class Upsampler : public Resampler {
|
||||
public:
|
||||
Upsampler();
|
||||
|
||||
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 );
|
||||
|
||||
protected:
|
||||
enum { stereo = 2 };
|
||||
enum { write_offset = 2 * stereo };
|
||||
int pos;
|
||||
int step;
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,346 @@
|
|||
// Sega VGM music file emulator core
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef VGM_CORE_H
|
||||
#define VGM_CORE_H
|
||||
|
||||
#include "Gme_Loader.h"
|
||||
#include "Ymz280b_Emu.h"
|
||||
#include "Ymf262_Emu.h"
|
||||
#include "Ym2612_Emu.h"
|
||||
#include "Ym2610b_Emu.h"
|
||||
#include "Ym2608_Emu.h"
|
||||
#include "Ym3812_Emu.h"
|
||||
#include "Ym2413_Emu.h"
|
||||
#include "Ym2151_Emu.h"
|
||||
#include "C140_Emu.h"
|
||||
#include "SegaPcm_Emu.h"
|
||||
#include "Rf5C68_Emu.h"
|
||||
#include "Rf5C164_Emu.h"
|
||||
#include "Pwm_Emu.h"
|
||||
#include "Okim6258_Emu.h"
|
||||
#include "Okim6295_Emu.h"
|
||||
#include "K051649_Emu.h"
|
||||
#include "K053260_Emu.h"
|
||||
#include "K054539_Emu.h"
|
||||
#include "Qsound_Apu.h"
|
||||
#include "Ym2203_Emu.h"
|
||||
#include "Ay_Apu.h"
|
||||
#include "Hes_Apu.h"
|
||||
#include "Sms_Apu.h"
|
||||
#include "Multi_Buffer.h"
|
||||
#include "Chip_Resampler.h"
|
||||
|
||||
template<class Emu>
|
||||
class Chip_Emu : public Emu {
|
||||
int last_time;
|
||||
short* out;
|
||||
enum { disabled_time = -1 };
|
||||
public:
|
||||
Chip_Emu() { last_time = disabled_time; out = NULL; }
|
||||
void enable( bool b = true ) { last_time = b ? 0 : disabled_time; }
|
||||
bool enabled() const { return last_time != disabled_time; }
|
||||
void begin_frame( short* buf ) { out = buf; last_time = 0; }
|
||||
|
||||
int run_until( int time )
|
||||
{
|
||||
int count = time - last_time;
|
||||
if ( count > 0 )
|
||||
{
|
||||
if ( last_time < 0 )
|
||||
return false;
|
||||
last_time = time;
|
||||
short* p = out;
|
||||
out += count * Emu::out_chan_count;
|
||||
Emu::run( count, p );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class Vgm_Core : public Gme_Loader {
|
||||
public:
|
||||
|
||||
// VGM file header
|
||||
struct header_t
|
||||
{
|
||||
enum { size_min = 0x40 };
|
||||
enum { size_151 = 0x80 };
|
||||
enum { size_max = 0xC0 };
|
||||
|
||||
char tag [4]; // 0x00
|
||||
byte data_size [4]; // 0x04
|
||||
byte version [4]; // 0x08
|
||||
byte psg_rate [4]; // 0x0C
|
||||
byte ym2413_rate [4]; // 0x10
|
||||
byte gd3_offset [4]; // 0x14
|
||||
byte track_duration [4]; // 0x18
|
||||
byte loop_offset [4]; // 0x1C
|
||||
byte loop_duration [4]; // 0x20
|
||||
byte frame_rate [4]; // 0x24 v1.01 V
|
||||
byte noise_feedback [2]; // 0x28 v1.10 V
|
||||
byte noise_width; // 0x2A
|
||||
byte sn76489_flags; // 0x2B v1.51 <
|
||||
byte ym2612_rate [4]; // 0x2C v1.10 V
|
||||
byte ym2151_rate [4]; // 0x30
|
||||
byte data_offset [4]; // 0x34 v1.50 V
|
||||
byte segapcm_rate [4]; // 0x38 v1.51 V
|
||||
byte segapcm_reg [4]; // 0x3C
|
||||
byte rf5c68_rate [4]; // 0x40
|
||||
byte ym2203_rate [4]; // 0x44
|
||||
byte ym2608_rate [4]; // 0x48
|
||||
byte ym2610_rate [4]; // 0x4C
|
||||
byte ym3812_rate [4]; // 0x50
|
||||
byte ym3526_rate [4]; // 0x54
|
||||
byte y8950_rate [4]; // 0x58
|
||||
byte ymf262_rate [4]; // 0x5C
|
||||
byte ymf278b_rate [4]; // 0x60
|
||||
byte ymf271_rate [4]; // 0x64
|
||||
byte ymz280b_rate [4]; // 0x68
|
||||
byte rf5c164_rate [4]; // 0x6C
|
||||
byte pwm_rate [4]; // 0x70
|
||||
byte ay8910_rate [4]; // 0x74
|
||||
byte ay8910_type; // 0x78
|
||||
byte ay8910_flags; // 0x79
|
||||
byte ym2203_ay8910_flags;// 0x7A
|
||||
byte ym2608_ay8910_flags;// 0x7B
|
||||
byte volume_modifier; // 0x7C v1.60 V
|
||||
byte reserved; // 0x7D
|
||||
byte loop_base; // 0x7E
|
||||
byte loop_modifier; // 0x7F v1.51 <
|
||||
byte gbdmg_rate [4]; // 0x80 v1.61 V
|
||||
byte nesapu_rate [4]; // 0x84
|
||||
byte multipcm_rate [4]; // 0x88
|
||||
byte upd7759_rate [4]; // 0x8C
|
||||
byte okim6258_rate [4]; // 0x90
|
||||
byte okim6258_flags; // 0x94
|
||||
byte k054539_flags; // 0x95
|
||||
byte c140_type; // 0x96
|
||||
byte reserved_flags; // 0x97
|
||||
byte okim6295_rate [4]; // 0x98
|
||||
byte k051649_rate [4]; // 0x9C
|
||||
byte k054539_rate [4]; // 0xA0
|
||||
byte huc6280_rate [4]; // 0xA4
|
||||
byte c140_rate [4]; // 0xA8
|
||||
byte k053260_rate [4]; // 0xAC
|
||||
byte pokey_rate [4]; // 0xB0
|
||||
byte qsound_rate [4]; // 0xB4
|
||||
byte reserved2 [4]; // 0xB8
|
||||
byte extra_offset [4]; // 0xBC
|
||||
|
||||
// True if header has valid file signature
|
||||
bool valid_tag() const;
|
||||
int size() const;
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
// Header for currently loaded file
|
||||
header_t const& header() const { return _header; }
|
||||
|
||||
// Raw file data, for parsing GD3 tags
|
||||
byte const* file_begin() const { return Gme_Loader::file_begin(); }
|
||||
byte const* file_end () const { return Gme_Loader::file_end(); }
|
||||
|
||||
// If file uses FM, initializes FM sound emulator using *sample_rate. If
|
||||
// *sample_rate is zero, sets *sample_rate to the proper accurate rate and
|
||||
// uses that. The output of the FM sound emulator is resampled to the
|
||||
// final sampling rate.
|
||||
blargg_err_t init_chips( double* fm_rate, bool reinit = false );
|
||||
|
||||
// True if any FM chips are used by file. Always false until init_fm()
|
||||
// is called.
|
||||
bool uses_fm() const { return ym2612[0].enabled() || ym2413[0].enabled() || ym2151[0].enabled() || c140.enabled() ||
|
||||
segapcm.enabled() || rf5c68.enabled() || rf5c164.enabled() || pwm.enabled() || okim6258.enabled() || okim6295[0].enabled() ||
|
||||
k051649.enabled() || k053260.enabled() || k054539.enabled() || ym2203[0].enabled() || ym3812[0].enabled() || ymf262[0].enabled() ||
|
||||
ymz280b.enabled() || ym2610[0].enabled() || ym2608[0].enabled() || qsound[0].enabled() ||
|
||||
(header().ay8910_rate[0] | header().ay8910_rate[1] | header().ay8910_rate[2] | header().ay8910_rate[3]) ||
|
||||
(header().huc6280_rate[0] | header().huc6280_rate[1] | header().huc6280_rate[2] | header().huc6280_rate[3]); }
|
||||
|
||||
// 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 );
|
||||
|
||||
void set_sample_rate( int r ) { sample_rate = r; }
|
||||
|
||||
// Starts track
|
||||
void start_track();
|
||||
|
||||
// Runs PSG-only VGM for msec and returns number of clocks it ran for
|
||||
blip_time_t run_psg( int msec );
|
||||
|
||||
// Plays FM for at most count samples into *out, and returns number of
|
||||
// samples actually generated (always even). Also runs PSG for blip_time.
|
||||
int play_frame( blip_time_t blip_time, int count, blip_sample_t out [] );
|
||||
|
||||
// True if all of file data has been played
|
||||
bool track_ended() const { return pos >= file_end(); }
|
||||
|
||||
// 0 for PSG and YM2612 DAC, 1 for AY, 2 for HuC6280
|
||||
Stereo_Buffer stereo_buf[3];
|
||||
|
||||
// PCM sound is always generated here
|
||||
Blip_Buffer * blip_buf[2];
|
||||
|
||||
// PSG sound chips, for assigning to Blip_Buffer, and setting volume and EQ
|
||||
Sms_Apu psg[2];
|
||||
Ay_Apu ay[2];
|
||||
Hes_Apu huc6280[2];
|
||||
|
||||
// PCM synth, for setting volume and EQ
|
||||
Blip_Synth_Fast pcm;
|
||||
|
||||
// FM sound chips
|
||||
Chip_Resampler_Emu<Ymf262_Emu> ymf262[2];
|
||||
Chip_Resampler_Emu<Ym3812_Emu> ym3812[2];
|
||||
Chip_Resampler_Emu<Ym2612_Emu> ym2612[2];
|
||||
Chip_Resampler_Emu<Ym2610b_Emu> ym2610[2];
|
||||
Chip_Resampler_Emu<Ym2608_Emu> ym2608[2];
|
||||
Chip_Resampler_Emu<Ym2413_Emu> ym2413[2];
|
||||
Chip_Resampler_Emu<Ym2151_Emu> ym2151[2];
|
||||
Chip_Resampler_Emu<Ym2203_Emu> ym2203[2];
|
||||
|
||||
// PCM sound chips
|
||||
Chip_Resampler_Emu<C140_Emu> c140;
|
||||
Chip_Resampler_Emu<SegaPcm_Emu> segapcm;
|
||||
Chip_Resampler_Emu<Rf5C68_Emu> rf5c68;
|
||||
Chip_Resampler_Emu<Rf5C164_Emu> rf5c164;
|
||||
Chip_Resampler_Emu<Pwm_Emu> pwm;
|
||||
Chip_Resampler_Emu<Okim6258_Emu> okim6258; int okim6258_hz;
|
||||
Chip_Resampler_Emu<Okim6295_Emu> okim6295[2]; int okim6295_hz;
|
||||
Chip_Resampler_Emu<K051649_Emu> k051649;
|
||||
Chip_Resampler_Emu<K053260_Emu> k053260;
|
||||
Chip_Resampler_Emu<K054539_Emu> k054539;
|
||||
Chip_Resampler_Emu<Ymz280b_Emu> ymz280b; int ymz280b_hz;
|
||||
Chip_Resampler_Emu<Qsound_Apu> qsound[2];
|
||||
|
||||
// DAC control
|
||||
typedef struct daccontrol_data
|
||||
{
|
||||
bool Enable;
|
||||
byte Bank;
|
||||
} DACCTRL_DATA;
|
||||
|
||||
byte DacCtrlUsed;
|
||||
byte DacCtrlUsg[0xFF];
|
||||
DACCTRL_DATA DacCtrl[0xFF];
|
||||
byte DacCtrlMap[0xFF];
|
||||
int DacCtrlTime[0xFF];
|
||||
void ** dac_control;
|
||||
|
||||
void dac_control_grow(byte chip_id);
|
||||
|
||||
int dac_control_recursion;
|
||||
|
||||
int run_dac_control( int time );
|
||||
|
||||
public:
|
||||
void chip_reg_write(unsigned Sample, byte ChipType, byte ChipID, byte Port, byte Offset, byte Data);
|
||||
|
||||
// Implementation
|
||||
public:
|
||||
Vgm_Core();
|
||||
~Vgm_Core();
|
||||
|
||||
protected:
|
||||
virtual blargg_err_t load_mem_( byte const [], int );
|
||||
|
||||
private:
|
||||
// blip_time_t // PSG clocks
|
||||
typedef int vgm_time_t; // 44100 per second, REGARDLESS of sample rate
|
||||
typedef int fm_time_t; // FM sample count
|
||||
|
||||
int sample_rate;
|
||||
int vgm_rate; // rate of log, 44100 normally, adjusted by tempo
|
||||
double fm_rate; // FM samples per second
|
||||
|
||||
header_t _header;
|
||||
|
||||
// VGM to FM time
|
||||
int fm_time_factor;
|
||||
int fm_time_offset;
|
||||
fm_time_t to_fm_time( vgm_time_t ) const;
|
||||
|
||||
// VGM to PSG time
|
||||
int blip_time_factor;
|
||||
blip_time_t to_psg_time( vgm_time_t ) const;
|
||||
|
||||
int blip_ay_time_factor;
|
||||
int ay_time_offset;
|
||||
blip_time_t to_ay_time( vgm_time_t ) const;
|
||||
|
||||
int blip_huc6280_time_factor;
|
||||
int huc6280_time_offset;
|
||||
blip_time_t to_huc6280_time( vgm_time_t ) const;
|
||||
|
||||
// Current time and position in log
|
||||
vgm_time_t vgm_time;
|
||||
byte const* pos;
|
||||
byte const* loop_begin;
|
||||
bool has_looped;
|
||||
|
||||
// PCM
|
||||
enum { PCM_BANK_COUNT = 0x40 };
|
||||
typedef struct _vgm_pcm_bank_data
|
||||
{
|
||||
unsigned DataSize;
|
||||
byte* Data;
|
||||
unsigned DataStart;
|
||||
} VGM_PCM_DATA;
|
||||
typedef struct _vgm_pcm_bank
|
||||
{
|
||||
unsigned BankCount;
|
||||
VGM_PCM_DATA* Bank;
|
||||
unsigned DataSize;
|
||||
byte* Data;
|
||||
unsigned DataPos;
|
||||
unsigned BnkPos;
|
||||
} VGM_PCM_BANK;
|
||||
|
||||
typedef struct pcmbank_table
|
||||
{
|
||||
byte ComprType;
|
||||
byte CmpSubType;
|
||||
byte BitDec;
|
||||
byte BitCmp;
|
||||
unsigned EntryCount;
|
||||
void* Entries;
|
||||
} PCMBANK_TBL;
|
||||
|
||||
VGM_PCM_BANK PCMBank[PCM_BANK_COUNT];
|
||||
PCMBANK_TBL PCMTbl;
|
||||
|
||||
void ReadPCMTable(unsigned DataSize, const byte* Data);
|
||||
void AddPCMData(byte Type, unsigned DataSize, const byte* Data);
|
||||
bool DecompressDataBlk(VGM_PCM_DATA* Bank, unsigned DataSize, const byte* Data);
|
||||
const byte* GetPointerFromPCMBank(byte Type, unsigned DataPos);
|
||||
|
||||
byte const* pcm_pos; // current position in PCM data
|
||||
int dac_amp[2];
|
||||
int dac_disabled[2]; // -1 if disabled
|
||||
void write_pcm( vgm_time_t, int chip, int amp );
|
||||
|
||||
blip_time_t run( vgm_time_t );
|
||||
int run_ym2151( int chip, int time );
|
||||
int run_ym2203( int chip, int time );
|
||||
int run_ym2413( int chip, int time );
|
||||
int run_ym2612( int chip, int time );
|
||||
int run_ym3812( int chip, int time );
|
||||
int run_ymf262( int chip, int time );
|
||||
int run_ym2610( int chip, int time );
|
||||
int run_ym2608( int chip, int time );
|
||||
int run_ymz280b( int time );
|
||||
int run_c140( int time );
|
||||
int run_segapcm( int time );
|
||||
int run_rf5c68( int time );
|
||||
int run_rf5c164( int time );
|
||||
int run_pwm( int time );
|
||||
int run_okim6258( int time );
|
||||
int run_okim6295( int chip, int time );
|
||||
int run_k051649( int time );
|
||||
int run_k053260( int time );
|
||||
int run_k054539( int time );
|
||||
int run_qsound( int chip, int time );
|
||||
void update_fm_rates( int* ym2151_rate, int* ym2413_rate, int* ym2612_rate ) const;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,75 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ym2151_Emu.h"
|
||||
#include "ym2151.h"
|
||||
|
||||
Ym2151_Emu::Ym2151_Emu() { PSG = 0; }
|
||||
|
||||
Ym2151_Emu::~Ym2151_Emu()
|
||||
{
|
||||
if ( PSG ) ym2151_shutdown( PSG );
|
||||
}
|
||||
|
||||
int Ym2151_Emu::set_rate( double sample_rate, double clock_rate )
|
||||
{
|
||||
if ( PSG )
|
||||
{
|
||||
ym2151_shutdown( PSG );
|
||||
PSG = 0;
|
||||
}
|
||||
|
||||
PSG = ym2151_init( clock_rate, sample_rate );
|
||||
if ( !PSG )
|
||||
return 1;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ym2151_Emu::reset()
|
||||
{
|
||||
ym2151_reset_chip( PSG );
|
||||
ym2151_set_mask( PSG, 0 );
|
||||
}
|
||||
|
||||
static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL};
|
||||
|
||||
void Ym2151_Emu::write( int addr, int data )
|
||||
{
|
||||
ym2151_update_one( PSG, DUMMYBUF, 0 );
|
||||
ym2151_write_reg( PSG, addr, data );
|
||||
}
|
||||
|
||||
void Ym2151_Emu::mute_voices( int mask )
|
||||
{
|
||||
ym2151_set_mask( PSG, mask );
|
||||
}
|
||||
|
||||
void Ym2151_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
SAMP bufL[ 1024 ];
|
||||
SAMP bufR[ 1024 ];
|
||||
SAMP * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
ym2151_update_one( PSG, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// YM2151 FM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YM2151_EMU_H
|
||||
#define YM2151_EMU_H
|
||||
|
||||
class Ym2151_Emu {
|
||||
void* PSG;
|
||||
public:
|
||||
Ym2151_Emu();
|
||||
~Ym2151_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( double sample_rate, double clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,155 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ym2203_Emu.h"
|
||||
#include "fm.h"
|
||||
#include <string.h>
|
||||
|
||||
static void psg_set_clock(void *param, int clock)
|
||||
{
|
||||
Ym2203_Emu *info = (Ym2203_Emu *)param;
|
||||
info->psg_set_clock( clock );
|
||||
}
|
||||
|
||||
static void psg_write(void *param, int address, int data)
|
||||
{
|
||||
Ym2203_Emu *info = (Ym2203_Emu *)param;
|
||||
info->psg_write( address, data );
|
||||
}
|
||||
|
||||
static int psg_read(void *param)
|
||||
{
|
||||
Ym2203_Emu *info = (Ym2203_Emu *)param;
|
||||
return info->psg_read();
|
||||
}
|
||||
|
||||
static void psg_reset(void *param)
|
||||
{
|
||||
Ym2203_Emu *info = (Ym2203_Emu *)param;
|
||||
info->psg_reset();
|
||||
}
|
||||
|
||||
static const ssg_callbacks psgintf =
|
||||
{
|
||||
psg_set_clock,
|
||||
psg_write,
|
||||
psg_read,
|
||||
psg_reset
|
||||
};
|
||||
|
||||
Ym2203_Emu::Ym2203_Emu() { opn = 0; psg.set_type( Ay_Apu::Ym2203 ); }
|
||||
|
||||
Ym2203_Emu::~Ym2203_Emu()
|
||||
{
|
||||
if ( opn ) ym2203_shutdown( opn );
|
||||
}
|
||||
|
||||
int Ym2203_Emu::set_rate( int sample_rate, int clock_rate )
|
||||
{
|
||||
if ( opn )
|
||||
{
|
||||
ym2203_shutdown( opn );
|
||||
opn = 0;
|
||||
}
|
||||
|
||||
opn = ym2203_init( this, clock_rate, sample_rate, &psgintf );
|
||||
if ( !opn )
|
||||
return 1;
|
||||
|
||||
this->sample_rate = sample_rate;
|
||||
psg_clock = clock_rate * 2;
|
||||
|
||||
buffer.set_sample_rate( sample_rate );
|
||||
buffer.clock_rate( psg_clock );
|
||||
|
||||
psg.volume( 1.0 );
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ym2203_Emu::reset()
|
||||
{
|
||||
psg.reset();
|
||||
ym2203_reset_chip( opn );
|
||||
mute_voices( 0 );
|
||||
}
|
||||
|
||||
static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL};
|
||||
|
||||
void Ym2203_Emu::write( int addr, int data )
|
||||
{
|
||||
ym2203_update_one( opn, DUMMYBUF, 0 );
|
||||
ym2203_write( opn, 0, addr );
|
||||
ym2203_write( opn, 1, data );
|
||||
}
|
||||
|
||||
void Ym2203_Emu::mute_voices( int mask )
|
||||
{
|
||||
ym2203_set_mutemask( opn, mask );
|
||||
for ( unsigned i = 0, j = 1 << 3; i < 3; i++, j <<= 1)
|
||||
{
|
||||
Blip_Buffer * buf = ( mask & j ) ? NULL : &buffer;
|
||||
psg.set_output( i, buf );
|
||||
}
|
||||
}
|
||||
|
||||
void Ym2203_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
blip_sample_t buf[ 1024 ];
|
||||
FMSAMPLE bufL[ 1024 ];
|
||||
FMSAMPLE bufR[ 1024 ];
|
||||
FMSAMPLE * buffers[2] = { bufL, bufR };
|
||||
|
||||
blip_time_t psg_end_time = pair_count * psg_clock / sample_rate;
|
||||
psg.end_frame( psg_end_time );
|
||||
buffer.end_frame( psg_end_time );
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
ym2203_update_one( opn, buffers, todo );
|
||||
|
||||
int sample_count = buffer.read_samples( buf, todo );
|
||||
memset( buf + sample_count, 0, ( todo - sample_count ) * sizeof( blip_sample_t ) );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l, output_r;
|
||||
int output = buf [i];
|
||||
output_l = output + bufL [i];
|
||||
output_r = output + bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
||||
|
||||
void Ym2203_Emu::psg_set_clock( int clock )
|
||||
{
|
||||
psg_clock = clock * 2;
|
||||
buffer.clock_rate( psg_clock );
|
||||
}
|
||||
|
||||
void Ym2203_Emu::psg_write( int addr, int data )
|
||||
{
|
||||
if ( !(addr & 1) ) psg.write_addr( data );
|
||||
else psg.write_data( 0, data );
|
||||
}
|
||||
|
||||
int Ym2203_Emu::psg_read()
|
||||
{
|
||||
return psg.read();
|
||||
}
|
||||
|
||||
void Ym2203_Emu::psg_reset()
|
||||
{
|
||||
psg.reset();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// YM2203 FM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YM2203_EMU_H
|
||||
#define YM2203_EMU_H
|
||||
|
||||
#include "Ay_Apu.h"
|
||||
|
||||
class Ym2203_Emu {
|
||||
void* opn;
|
||||
Ay_Apu psg;
|
||||
Blip_Buffer buffer;
|
||||
unsigned sample_rate;
|
||||
unsigned psg_clock;
|
||||
public:
|
||||
Ym2203_Emu();
|
||||
~Ym2203_Emu();
|
||||
|
||||
// Sets output chip clock rate, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int sample_rate, int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 6 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
|
||||
// SSG interface
|
||||
inline void psg_set_clock( int clock );
|
||||
inline void psg_write( int addr, int data );
|
||||
inline int psg_read();
|
||||
inline void psg_reset();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,167 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ym2608_Emu.h"
|
||||
#include "fm.h"
|
||||
#include <string.h>
|
||||
|
||||
static void psg_set_clock(void *param, int clock)
|
||||
{
|
||||
Ym2608_Emu *info = (Ym2608_Emu *)param;
|
||||
info->psg_set_clock( clock );
|
||||
}
|
||||
|
||||
static void psg_write(void *param, int address, int data)
|
||||
{
|
||||
Ym2608_Emu *info = (Ym2608_Emu *)param;
|
||||
info->psg_write( address, data );
|
||||
}
|
||||
|
||||
static int psg_read(void *param)
|
||||
{
|
||||
Ym2608_Emu *info = (Ym2608_Emu *)param;
|
||||
return info->psg_read();
|
||||
}
|
||||
|
||||
static void psg_reset(void *param)
|
||||
{
|
||||
Ym2608_Emu *info = (Ym2608_Emu *)param;
|
||||
info->psg_reset();
|
||||
}
|
||||
|
||||
static const ssg_callbacks psgintf =
|
||||
{
|
||||
psg_set_clock,
|
||||
psg_write,
|
||||
psg_read,
|
||||
psg_reset
|
||||
};
|
||||
|
||||
Ym2608_Emu::Ym2608_Emu() { opn = 0; psg.set_type( Ay_Apu::Ym2608 ); }
|
||||
|
||||
Ym2608_Emu::~Ym2608_Emu()
|
||||
{
|
||||
if ( opn ) ym2608_shutdown( opn );
|
||||
}
|
||||
|
||||
int Ym2608_Emu::set_rate( int sample_rate, int clock_rate )
|
||||
{
|
||||
if ( opn )
|
||||
{
|
||||
ym2608_shutdown( opn );
|
||||
opn = 0;
|
||||
}
|
||||
|
||||
opn = ym2608_init( this, clock_rate, sample_rate, &psgintf );
|
||||
if ( !opn )
|
||||
return 1;
|
||||
|
||||
this->sample_rate = sample_rate;
|
||||
psg_clock = clock_rate * 2;
|
||||
|
||||
buffer.set_sample_rate( sample_rate );
|
||||
buffer.clock_rate( psg_clock );
|
||||
|
||||
psg.volume( 1.0 );
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ym2608_Emu::reset()
|
||||
{
|
||||
psg.reset();
|
||||
ym2608_reset_chip( opn );
|
||||
mute_voices( 0 );
|
||||
}
|
||||
|
||||
static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL};
|
||||
|
||||
void Ym2608_Emu::write0( int addr, int data )
|
||||
{
|
||||
ym2608_update_one( opn, DUMMYBUF, 0 );
|
||||
ym2608_write( opn, 0, addr );
|
||||
ym2608_write( opn, 1, data );
|
||||
}
|
||||
|
||||
void Ym2608_Emu::write1( int addr, int data )
|
||||
{
|
||||
ym2608_update_one( opn, DUMMYBUF, 0 );
|
||||
ym2608_write( opn, 2, addr );
|
||||
ym2608_write( opn, 3, data );
|
||||
}
|
||||
|
||||
void Ym2608_Emu::write_rom( int rom_id, int size, int start, int length, void * data )
|
||||
{
|
||||
ym2608_write_pcmrom( opn, rom_id, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void Ym2608_Emu::mute_voices( int mask )
|
||||
{
|
||||
ym2608_set_mutemask( opn, mask );
|
||||
for ( unsigned i = 0, j = 1 << 6; i < 3; i++, j <<= 1)
|
||||
{
|
||||
Blip_Buffer * buf = ( mask & j ) ? NULL : &buffer;
|
||||
psg.set_output( i, buf );
|
||||
}
|
||||
}
|
||||
|
||||
void Ym2608_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
blip_sample_t buf[ 1024 ];
|
||||
FMSAMPLE bufL[ 1024 ];
|
||||
FMSAMPLE bufR[ 1024 ];
|
||||
FMSAMPLE * buffers[2] = { bufL, bufR };
|
||||
|
||||
blip_time_t psg_end_time = pair_count * psg_clock / sample_rate;
|
||||
psg.end_frame( psg_end_time );
|
||||
buffer.end_frame( psg_end_time );
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
ym2608_update_one( opn, buffers, todo );
|
||||
|
||||
int sample_count = buffer.read_samples( buf, todo );
|
||||
memset( buf + sample_count, 0, ( todo - sample_count ) * sizeof( blip_sample_t ) );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l, output_r;
|
||||
int output = buf [i];
|
||||
output_l = output + bufL [i];
|
||||
output_r = output + bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
||||
|
||||
void Ym2608_Emu::psg_set_clock( int clock )
|
||||
{
|
||||
psg_clock = clock * 2;
|
||||
buffer.clock_rate( psg_clock );
|
||||
}
|
||||
|
||||
void Ym2608_Emu::psg_write( int addr, int data )
|
||||
{
|
||||
if ( !(addr & 1) ) psg.write_addr( data );
|
||||
else psg.write_data( 0, data );
|
||||
}
|
||||
|
||||
int Ym2608_Emu::psg_read()
|
||||
{
|
||||
return psg.read();
|
||||
}
|
||||
|
||||
void Ym2608_Emu::psg_reset()
|
||||
{
|
||||
psg.reset();
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// YM2608 FM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YM2608_EMU_H
|
||||
#define YM2608_EMU_H
|
||||
|
||||
#include "Ay_Apu.h"
|
||||
|
||||
class Ym2608_Emu {
|
||||
void* opn;
|
||||
Ay_Apu psg;
|
||||
Blip_Buffer buffer;
|
||||
unsigned sample_rate;
|
||||
unsigned psg_clock;
|
||||
public:
|
||||
Ym2608_Emu();
|
||||
~Ym2608_Emu();
|
||||
|
||||
// Sets output chip clock rate, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int sample_rate, int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 9 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write0( int addr, int data );
|
||||
void write1( int addr, int data );
|
||||
|
||||
// Sets ROM type, scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int rom_id, int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
|
||||
// SSG interface
|
||||
inline void psg_set_clock( int clock );
|
||||
inline void psg_write( int addr, int data );
|
||||
inline int psg_read();
|
||||
inline void psg_reset();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,173 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ym2610b_Emu.h"
|
||||
#include "fm.h"
|
||||
#include <string.h>
|
||||
|
||||
static void psg_set_clock(void *param, int clock)
|
||||
{
|
||||
Ym2610b_Emu *info = (Ym2610b_Emu *)param;
|
||||
info->psg_set_clock( clock );
|
||||
}
|
||||
|
||||
static void psg_write(void *param, int address, int data)
|
||||
{
|
||||
Ym2610b_Emu *info = (Ym2610b_Emu *)param;
|
||||
info->psg_write( address, data );
|
||||
}
|
||||
|
||||
static int psg_read(void *param)
|
||||
{
|
||||
Ym2610b_Emu *info = (Ym2610b_Emu *)param;
|
||||
return info->psg_read();
|
||||
}
|
||||
|
||||
static void psg_reset(void *param)
|
||||
{
|
||||
Ym2610b_Emu *info = (Ym2610b_Emu *)param;
|
||||
info->psg_reset();
|
||||
}
|
||||
|
||||
static const ssg_callbacks psgintf =
|
||||
{
|
||||
psg_set_clock,
|
||||
psg_write,
|
||||
psg_read,
|
||||
psg_reset
|
||||
};
|
||||
|
||||
Ym2610b_Emu::Ym2610b_Emu() { opn = 0; }
|
||||
|
||||
Ym2610b_Emu::~Ym2610b_Emu()
|
||||
{
|
||||
if ( opn ) ym2610_shutdown( opn );
|
||||
}
|
||||
|
||||
int Ym2610b_Emu::set_rate( int sample_rate, int clock_rate, bool is_2610b )
|
||||
{
|
||||
if ( opn )
|
||||
{
|
||||
ym2610_shutdown( opn );
|
||||
opn = 0;
|
||||
}
|
||||
|
||||
psg.set_type( is_2610b ? Ay_Apu::Ym2610b : Ay_Apu::Ym2610 );
|
||||
|
||||
opn = ym2610_init( this, clock_rate, sample_rate, &psgintf );
|
||||
if ( !opn )
|
||||
return 1;
|
||||
|
||||
this->sample_rate = sample_rate;
|
||||
psg_clock = clock_rate * 2;
|
||||
this->is_2610b = is_2610b;
|
||||
|
||||
buffer.set_sample_rate( sample_rate );
|
||||
buffer.clock_rate( psg_clock );
|
||||
|
||||
psg.volume( 1.0 );
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::reset()
|
||||
{
|
||||
psg.reset();
|
||||
ym2610_reset_chip( opn );
|
||||
mute_voices( 0 );
|
||||
}
|
||||
|
||||
static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL};
|
||||
|
||||
void Ym2610b_Emu::write0( int addr, int data )
|
||||
{
|
||||
if ( is_2610b ) ym2610b_update_one( opn, DUMMYBUF, 0 );
|
||||
else ym2610_update_one( opn, DUMMYBUF, 0 );
|
||||
ym2610_write( opn, 0, addr );
|
||||
ym2610_write( opn, 1, data );
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::write1( int addr, int data )
|
||||
{
|
||||
if ( is_2610b ) ym2610b_update_one( opn, DUMMYBUF, 0 );
|
||||
else ym2610_update_one( opn, DUMMYBUF, 0 );
|
||||
ym2610_write( opn, 2, addr );
|
||||
ym2610_write( opn, 3, data );
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::write_rom( int rom_id, int size, int start, int length, void * data )
|
||||
{
|
||||
ym2610_write_pcmrom( opn, rom_id, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::mute_voices( int mask )
|
||||
{
|
||||
ym2610_set_mutemask( opn, mask );
|
||||
for ( unsigned i = 0, j = 1 << 6; i < 3; i++, j <<= 1)
|
||||
{
|
||||
Blip_Buffer * buf = ( mask & j ) ? NULL : &buffer;
|
||||
psg.set_output( i, buf );
|
||||
}
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
blip_sample_t buf[ 1024 ];
|
||||
FMSAMPLE bufL[ 1024 ];
|
||||
FMSAMPLE bufR[ 1024 ];
|
||||
FMSAMPLE * buffers[2] = { bufL, bufR };
|
||||
|
||||
blip_time_t psg_end_time = pair_count * psg_clock / sample_rate;
|
||||
psg.end_frame( psg_end_time );
|
||||
buffer.end_frame( psg_end_time );
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
if ( is_2610b ) ym2610b_update_one( opn, buffers, todo );
|
||||
else ym2610_update_one( opn, buffers, todo );
|
||||
|
||||
int sample_count = buffer.read_samples( buf, todo );
|
||||
memset( buf + sample_count, 0, ( todo - sample_count ) * sizeof( blip_sample_t ) );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l, output_r;
|
||||
int output = buf [i];
|
||||
output_l = output + bufL [i];
|
||||
output_r = output + bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::psg_set_clock( int clock )
|
||||
{
|
||||
psg_clock = clock * 2;
|
||||
buffer.clock_rate( psg_clock );
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::psg_write( int addr, int data )
|
||||
{
|
||||
if ( !(addr & 1) ) psg.write_addr( data );
|
||||
else psg.write_data( 0, data );
|
||||
}
|
||||
|
||||
int Ym2610b_Emu::psg_read()
|
||||
{
|
||||
return psg.read();
|
||||
}
|
||||
|
||||
void Ym2610b_Emu::psg_reset()
|
||||
{
|
||||
psg.reset();
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// YM2610B FM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YM2610B_EMU_H
|
||||
#define YM2610B_EMU_H
|
||||
|
||||
#include "Ay_Apu.h"
|
||||
|
||||
class Ym2610b_Emu {
|
||||
void* opn;
|
||||
Ay_Apu psg;
|
||||
Blip_Buffer buffer;
|
||||
unsigned sample_rate;
|
||||
unsigned psg_clock;
|
||||
bool is_2610b;
|
||||
public:
|
||||
Ym2610b_Emu();
|
||||
~Ym2610b_Emu();
|
||||
|
||||
// Sets output chip clock rate, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int sample_rate, int clock_rate, bool is_2610b );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 9 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write0( int addr, int data );
|
||||
void write1( int addr, int data );
|
||||
|
||||
// Sets ROM type, scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int rom_id, int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
|
||||
// SSG interface
|
||||
inline void psg_set_clock( int clock );
|
||||
inline void psg_write( int addr, int data );
|
||||
inline int psg_read();
|
||||
inline void psg_reset();
|
||||
};
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,87 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ym2612_Emu.h"
|
||||
#include "fm.h"
|
||||
|
||||
#include "blargg_errors.h"
|
||||
|
||||
// Ym2612_Emu
|
||||
|
||||
Ym2612_Emu::~Ym2612_Emu()
|
||||
{
|
||||
if ( impl )
|
||||
ym2612_shutdown( impl );
|
||||
}
|
||||
|
||||
const char* Ym2612_Emu::set_rate( double sample_rate, double clock_rate )
|
||||
{
|
||||
if ( impl )
|
||||
{
|
||||
ym2612_shutdown( impl );
|
||||
impl = 0;
|
||||
}
|
||||
|
||||
if ( !clock_rate )
|
||||
clock_rate = sample_rate * 144.;
|
||||
|
||||
impl = ym2612_init( (long) (clock_rate + 0.5), (long) (sample_rate + 0.5) );
|
||||
if ( !impl )
|
||||
return blargg_err_memory;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ym2612_Emu::reset()
|
||||
{
|
||||
ym2612_reset_chip( impl );
|
||||
}
|
||||
|
||||
static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL};
|
||||
|
||||
void Ym2612_Emu::write0( int addr, int data )
|
||||
{
|
||||
ym2612_update_one( impl, DUMMYBUF, 0 );
|
||||
ym2612_write( impl, 0, addr );
|
||||
ym2612_write( impl, 1, data );
|
||||
}
|
||||
|
||||
void Ym2612_Emu::write1( int addr, int data )
|
||||
{
|
||||
ym2612_update_one( impl, DUMMYBUF, 0 );
|
||||
ym2612_write( impl, 2, addr );
|
||||
ym2612_write( impl, 3, data );
|
||||
}
|
||||
|
||||
void Ym2612_Emu::mute_voices( int mask )
|
||||
{
|
||||
ym2612_set_mutemask( impl, mask );
|
||||
}
|
||||
|
||||
void Ym2612_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
ym2612_update_one( impl, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ym3812_Emu.h"
|
||||
|
||||
#include <math.h>
|
||||
#include "dbopl.h"
|
||||
|
||||
Ym3812_Emu::Ym3812_Emu() { opl = 0; }
|
||||
|
||||
Ym3812_Emu::~Ym3812_Emu()
|
||||
{
|
||||
delete opl;
|
||||
}
|
||||
|
||||
int Ym3812_Emu::set_rate( int sample_rate, int clock_rate )
|
||||
{
|
||||
delete opl;
|
||||
opl = 0;
|
||||
|
||||
opl = new DBOPL::Chip;
|
||||
if ( !opl )
|
||||
return 1;
|
||||
|
||||
this->sample_rate = sample_rate;
|
||||
this->clock_rate = clock_rate * 4;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ym3812_Emu::reset()
|
||||
{
|
||||
opl->Setup( clock_rate, sample_rate );
|
||||
}
|
||||
|
||||
void Ym3812_Emu::write( int addr, int data )
|
||||
{
|
||||
opl->WriteReg( addr, data );
|
||||
}
|
||||
|
||||
void Ym3812_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
Bit32s buf[ 1024 ];
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
opl->GenerateBlock2( todo, buf );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l, output_r;
|
||||
int output = buf [i];
|
||||
output_l = output + out [0];
|
||||
output_r = output + out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// YM3812 FM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YM3812_EMU_H
|
||||
#define YM3812_EMU_H
|
||||
|
||||
namespace DBOPL {
|
||||
struct Chip;
|
||||
}
|
||||
|
||||
class Ym3812_Emu {
|
||||
DBOPL::Chip * opl;
|
||||
unsigned sample_rate;
|
||||
unsigned clock_rate;
|
||||
public:
|
||||
Ym3812_Emu();
|
||||
~Ym3812_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int sample_rate, int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,93 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ymf262_Emu.h"
|
||||
|
||||
#include <math.h>
|
||||
#include "dbopl.h"
|
||||
|
||||
Ymf262_Emu::Ymf262_Emu() { opl = 0; }
|
||||
|
||||
Ymf262_Emu::~Ymf262_Emu()
|
||||
{
|
||||
delete opl;
|
||||
}
|
||||
|
||||
int Ymf262_Emu::set_rate( int sample_rate, int clock_rate )
|
||||
{
|
||||
delete opl;
|
||||
opl = 0;
|
||||
|
||||
opl = new DBOPL::Chip;
|
||||
if ( !opl )
|
||||
return 1;
|
||||
|
||||
this->sample_rate = sample_rate;
|
||||
this->clock_rate = clock_rate;
|
||||
|
||||
reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ymf262_Emu::reset()
|
||||
{
|
||||
opl->Setup( clock_rate, sample_rate );
|
||||
}
|
||||
|
||||
void Ymf262_Emu::write0( int addr, int data )
|
||||
{
|
||||
opl->WriteReg( addr, data );
|
||||
}
|
||||
|
||||
void Ymf262_Emu::write1( int addr, int data )
|
||||
{
|
||||
opl->WriteReg( 0x100 + addr, data );
|
||||
}
|
||||
|
||||
void Ymf262_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
Bit32s buf[ 2048 ];
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
|
||||
if ( opl->opl3Active )
|
||||
{
|
||||
opl->GenerateBlock3( todo, buf );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l, output_r;
|
||||
output_l = buf [i * 2];
|
||||
output_r = buf [i * 2 + 1];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
opl->GenerateBlock2( todo, buf );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l, output_r;
|
||||
int output = buf [i];
|
||||
output_l = output + out [0];
|
||||
output_r = output + out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// YMF262 FM sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YMF262_EMU_H
|
||||
#define YMF262_EMU_H
|
||||
|
||||
namespace DBOPL {
|
||||
struct Chip;
|
||||
}
|
||||
|
||||
class Ymf262_Emu {
|
||||
DBOPL::Chip * opl;
|
||||
unsigned sample_rate;
|
||||
unsigned clock_rate;
|
||||
public:
|
||||
Ymf262_Emu();
|
||||
~Ymf262_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int sample_rate, int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Writes data to addr
|
||||
void write0( int addr, int data );
|
||||
void write1( int addr, int data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,78 @@
|
|||
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
||||
|
||||
#include "Ymz280b_Emu.h"
|
||||
#include "ymz280b.h"
|
||||
|
||||
Ymz280b_Emu::Ymz280b_Emu() { chip = 0; }
|
||||
|
||||
Ymz280b_Emu::~Ymz280b_Emu()
|
||||
{
|
||||
if ( chip ) device_stop_ymz280b( chip );
|
||||
}
|
||||
|
||||
int Ymz280b_Emu::set_rate( int clock_rate )
|
||||
{
|
||||
if ( chip )
|
||||
{
|
||||
device_stop_ymz280b( chip );
|
||||
chip = 0;
|
||||
}
|
||||
|
||||
chip = device_start_ymz280b( clock_rate );
|
||||
if ( !chip )
|
||||
return 0;
|
||||
|
||||
reset();
|
||||
return clock_rate * 2 / 384;
|
||||
}
|
||||
|
||||
void Ymz280b_Emu::reset()
|
||||
{
|
||||
device_reset_ymz280b( chip );
|
||||
ymz280b_set_mute_mask( chip, 0 );
|
||||
}
|
||||
|
||||
void Ymz280b_Emu::write( int addr, int data )
|
||||
{
|
||||
ymz280b_w( chip, 0, addr );
|
||||
ymz280b_w( chip, 1, data );
|
||||
}
|
||||
|
||||
void Ymz280b_Emu::write_rom( int size, int start, int length, void * data )
|
||||
{
|
||||
ymz280b_write_rom( chip, size, start, length, (const UINT8 *) data );
|
||||
}
|
||||
|
||||
void Ymz280b_Emu::mute_voices( int mask )
|
||||
{
|
||||
ymz280b_set_mute_mask( chip, mask );
|
||||
}
|
||||
|
||||
void Ymz280b_Emu::run( int pair_count, sample_t* out )
|
||||
{
|
||||
stream_sample_t bufL[ 1024 ];
|
||||
stream_sample_t bufR[ 1024 ];
|
||||
stream_sample_t * buffers[2] = { bufL, bufR };
|
||||
|
||||
while (pair_count > 0)
|
||||
{
|
||||
int todo = pair_count;
|
||||
if (todo > 1024) todo = 1024;
|
||||
ymz280b_update( chip, buffers, todo );
|
||||
|
||||
for (int i = 0; i < todo; i++)
|
||||
{
|
||||
int output_l = bufL [i];
|
||||
int output_r = bufR [i];
|
||||
output_l += out [0];
|
||||
output_r += out [1];
|
||||
if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 );
|
||||
if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 );
|
||||
out [0] = output_l;
|
||||
out [1] = output_r;
|
||||
out += 2;
|
||||
}
|
||||
|
||||
pair_count -= todo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// YMZ280B sound chip emulator interface
|
||||
|
||||
// Game_Music_Emu $vers
|
||||
#ifndef YMZ280B_EMU_H
|
||||
#define YMZ280B_EMU_H
|
||||
|
||||
class Ymz280b_Emu {
|
||||
void* chip;
|
||||
public:
|
||||
Ymz280b_Emu();
|
||||
~Ymz280b_Emu();
|
||||
|
||||
// Sets output sample rate and chip clock rates, in Hz. Returns non-zero
|
||||
// if error.
|
||||
int set_rate( int clock_rate );
|
||||
|
||||
// Resets to power-up state
|
||||
void reset();
|
||||
|
||||
// Mutes voice n if bit n (1 << n) of mask is set
|
||||
enum { channel_count = 8 };
|
||||
void mute_voices( int mask );
|
||||
|
||||
// Writes data to addr
|
||||
void write( int addr, int data );
|
||||
|
||||
// Scales ROM size, then writes length bytes from data at start offset
|
||||
void write_rom( int size, int start, int length, void * data );
|
||||
|
||||
// Runs and writes pair_count*2 samples to output
|
||||
typedef short sample_t;
|
||||
enum { out_chan_count = 2 }; // stereo
|
||||
void run( int pair_count, sample_t* out );
|
||||
};
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue