Updated Game_Music_Emu

CQTexperiment
Chris Moeller 2013-09-27 20:24:46 -07:00
parent 17682d4397
commit 5c0cf35a1a
165 changed files with 47320 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,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 );
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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];
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 );
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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