# HG changeset patch # User nenolod # Date 1130903846 28800 # Node ID 252843aac42f3312720fc8fac4f69162aa294901 # Parent feeda0dda3cebb0572e39bb82941a9b49452ef2f [svn] Import the initial sources for console music support. diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Audacious_Driver.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Audacious_Driver.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,9 @@ +/* + * Audacious: Cross platform multimedia player + * Copyright (c) 2005 Audacious Team + * + * Driver for Game_Music_Emu library. See details at: + * http://www.slack.net/~ant/libs/ + */ + + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Blip_Buffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Blip_Buffer.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,394 @@ + +// Blip_Buffer 0.3.3. http://www.slack.net/~ant/libs/ + +#include "Blip_Buffer.h" + +#include +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Blip_Buffer::Blip_Buffer() +{ + samples_per_sec = 44100; + buffer_ = NULL; + + // try to cause assertion failure if buffer is used before these are set + clocks_per_sec = 0; + factor_ = ~0ul; + offset_ = 0; + buffer_size_ = 0; + length_ = 0; + + bass_freq_ = 16; +} + +void Blip_Buffer::clear( bool entire_buffer ) +{ + long count = (entire_buffer ? buffer_size_ : samples_avail()); + offset_ = 0; + reader_accum = 0; + memset( buffer_, sample_offset & 0xFF, (count + widest_impulse_) * sizeof (buf_t_) ); +} + +blargg_err_t Blip_Buffer::sample_rate( long new_rate, int msec ) +{ + size_t new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) + 1 - widest_impulse_ - 64; + if ( msec != blip_default_length ) + { + size_t s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + require( false ); // requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + delete [] buffer_; + buffer_ = NULL; // allow for exception in allocation below + buffer_size_ = 0; + offset_ = 0; + + buffer_ = new buf_t_ [new_size + widest_impulse_]; + if ( !buffer_ ) + return "Out of memory"; + } + + buffer_size_ = new_size; + length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + + samples_per_sec = new_rate; + if ( clocks_per_sec ) + clock_rate( clocks_per_sec ); // recalculate factor + + bass_freq( bass_freq_ ); // recalculate shift + + clear(); + + return blargg_success; +} + +void Blip_Buffer::clock_rate( long cps ) +{ + clocks_per_sec = cps; + factor_ = (unsigned long) floor( (double) samples_per_sec / cps * + (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + require( factor_ > 0 ); // clock_rate/sample_rate ratio is too large +} + +Blip_Buffer::~Blip_Buffer() +{ + delete [] buffer_; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + if ( freq == 0 ) { + bass_shift = 31; // 32 or greater invokes undefined behavior elsewhere + return; + } + bass_shift = 1 + (int) floor( 1.442695041 * log( 0.124 * samples_per_sec / freq ) ); + if ( bass_shift < 0 ) + bass_shift = 0; + if ( bass_shift > 24 ) + bass_shift = 24; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const { + return (resampled_time( t ) >> BLIP_BUFFER_ACCURACY) - (offset_ >> BLIP_BUFFER_ACCURACY); +} + +void Blip_Impulse_::init( blip_pair_t_* imps, int w, int r, int fb ) +{ + fine_bits = fb; + width = w; + impulses = (imp_t*) imps; + generate = true; + volume_unit_ = -1.0; + res = r; + buf = NULL; + + impulse = &impulses [width * res * 2 * (fine_bits ? 2 : 1)]; + offset = 0; +} + +const int impulse_bits = 15; +const long impulse_amp = 1L << impulse_bits; +const long impulse_offset = impulse_amp / 2; + +void Blip_Impulse_::scale_impulse( int unit, imp_t* imp_in ) const +{ + long offset = ((long) unit << impulse_bits) - impulse_offset * unit + + (1 << (impulse_bits - 1)); + imp_t* imp = imp_in; + imp_t* fimp = impulse; + for ( int n = res / 2 + 1; n--; ) + { + int error = unit; + for ( int nn = width; nn--; ) + { + long a = ((long) *fimp++ * unit + offset) >> impulse_bits; + error -= a - unit; + *imp++ = (imp_t) a; + } + + // add error to middle + imp [-width / 2 - 1] += (imp_t) error; + } + + if ( res > 2 ) { + // second half is mirror-image + const imp_t* rev = imp - width - 1; + for ( int nn = (res / 2 - 1) * width - 1; nn--; ) + *imp++ = *--rev; + *imp++ = (imp_t) unit; + } + + // copy to odd offset + *imp++ = (imp_t) unit; + memcpy( imp, imp_in, (res * width - 1) * sizeof *imp ); +} + +const int max_res = 1 << blip_res_bits_; + +void Blip_Impulse_::fine_volume_unit() +{ + // to do: find way of merging in-place without temporary buffer + + imp_t temp [max_res * 2 * Blip_Buffer::widest_impulse_]; + scale_impulse( (offset & 0xffff) << fine_bits, temp ); + imp_t* imp2 = impulses + res * 2 * width; + scale_impulse( offset & 0xffff, imp2 ); + + // merge impulses + imp_t* imp = impulses; + imp_t* src2 = temp; + for ( int n = res / 2 * 2 * width; n--; ) { + *imp++ = *imp2++; + *imp++ = *imp2++; + *imp++ = *src2++; + *imp++ = *src2++; + } +} + +void Blip_Impulse_::volume_unit( double new_unit ) +{ + if ( new_unit == volume_unit_ ) + return; + + if ( generate ) + treble_eq( blip_eq_t( -8.87, 8800, 44100 ) ); + + volume_unit_ = new_unit; + + offset = 0x10001 * (unsigned long) floor( volume_unit_ * 0x10000 + 0.5 ); + + if ( fine_bits ) + fine_volume_unit(); + else + scale_impulse( offset & 0xffff, impulses ); +} + +static const double pi = 3.1415926535897932384626433832795029L; + +void Blip_Impulse_::treble_eq( const blip_eq_t& new_eq ) +{ + if ( !generate && new_eq.treble == eq.treble && new_eq.cutoff == eq.cutoff && + new_eq.sample_rate == eq.sample_rate ) + return; // already calculated with same parameters + + generate = false; + eq = new_eq; + + double treble = pow( 10.0, 1.0 / 20 * eq.treble ); // dB (-6dB = 0.50) + if ( treble < 0.000005 ) + treble = 0.000005; + + const double treble_freq = 22050.0; // treble level at 22 kHz harmonic + const double sample_rate = eq.sample_rate; + const double pt = treble_freq * 2 / sample_rate; + double cutoff = eq.cutoff * 2 / sample_rate; + if ( cutoff >= pt * 0.95 || cutoff >= 0.95 ) { + cutoff = 0.5; + treble = 1.0; + } + + // DSF Synthesis (See T. Stilson & J. Smith (1996), + // Alias-free digital synthesis of classic analog waveforms) + + // reduce adjacent impulse interference by using small part of wide impulse + const double n_harm = 4096; + const double rolloff = pow( treble, 1.0 / (n_harm * pt - n_harm * cutoff) ); + const double rescale = 1.0 / pow( rolloff, n_harm * cutoff ); + + const double pow_a_n = rescale * pow( rolloff, n_harm ); + const double pow_a_nc = rescale * pow( rolloff, n_harm * cutoff ); + + double total = 0.0; + const double to_angle = pi / 2 / n_harm / max_res; + + float buf [max_res * (Blip_Buffer::widest_impulse_ - 2) / 2]; + const int size = max_res * (width - 2) / 2; + for ( int i = size; i--; ) + { + double angle = (i * 2 + 1) * to_angle; + + // equivalent + //double y = dsf( angle, n_harm * cutoff, 1.0 ); + //y -= rescale * dsf( angle, n_harm * cutoff, rolloff ); + //y += rescale * dsf( angle, n_harm, rolloff ); + + const double cos_angle = cos( angle ); + const double cos_nc_angle = cos( n_harm * cutoff * angle ); + const double cos_nc1_angle = cos( (n_harm * cutoff - 1.0) * angle ); + + double b = 2.0 - 2.0 * cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + double d = 1.0 + rolloff * (rolloff - 2.0 * cos_angle); + double c = pow_a_n * rolloff * cos( (n_harm - 1.0) * angle ) - + pow_a_n * cos( n_harm * angle ) - + pow_a_nc * rolloff * cos_nc1_angle + + pow_a_nc * cos_nc_angle; + + // optimization of a / b + c / d + double y = (a * d + c * b) / (b * d); + + // fixed window which affects wider impulses more + if ( width > 12 ) { + double window = cos( n_harm / 1.25 / Blip_Buffer::widest_impulse_ * angle ); + y *= window * window; + } + + total += (float) y; + buf [i] = (float) y; + } + + // integrate runs of length 'max_res' + double factor = impulse_amp * 0.5 / total; // 0.5 accounts for other mirrored half + imp_t* imp = impulse; + const int step = max_res / res; + int offset = res > 1 ? max_res : max_res / 2; + for ( int n = res / 2 + 1; n--; offset -= step ) + { + for ( int w = -width / 2; w < width / 2; w++ ) + { + double sum = 0; + for ( int i = max_res; i--; ) + { + int index = w * max_res + offset + i; + if ( index < 0 ) + index = -index - 1; + if ( index < size ) + sum += buf [index]; + } + *imp++ = (imp_t) floor( sum * factor + (impulse_offset + 0.5) ); + } + } + + // rescale + double unit = volume_unit_; + if ( unit >= 0 ) { + volume_unit_ = -1; + volume_unit( unit ); + } +} + +void Blip_Buffer::remove_samples( long count ) +{ + require( buffer_ ); // sample rate must have been set + + if ( !count ) // optimization + return; + + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + widest_impulse_; + if ( count >= remain ) + memmove( buffer_, buffer_ + count, remain * sizeof (buf_t_) ); + else + memcpy( buffer_, buffer_ + count, remain * sizeof (buf_t_) ); + memset( buffer_ + remain, sample_offset & 0xFF, count * sizeof (buf_t_) ); +} + +#include BLARGG_ENABLE_OPTIMIZER + +long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, bool stereo ) +{ + require( buffer_ ); // sample rate must have been set + + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( !count ) + return 0; // optimization + + int sample_offset = this->sample_offset; + int bass_shift = this->bass_shift; + buf_t_* buf = buffer_; + long accum = reader_accum; + + if ( !stereo ) { + for ( long n = count; n--; ) { + long s = accum >> accum_fract; + accum -= accum >> bass_shift; + accum += (long (*buf++) - sample_offset) << accum_fract; + *out++ = (blip_sample_t) s; + + // clamp sample + if ( (BOOST::int16_t) s != s ) + out [-1] = blip_sample_t (0x7FFF - (s >> 24)); + } + } + else { + for ( long n = count; n--; ) { + long s = accum >> accum_fract; + accum -= accum >> bass_shift; + accum += (long (*buf++) - sample_offset) << accum_fract; + *out = (blip_sample_t) s; + out += 2; + + // clamp sample + if ( (BOOST::int16_t) s != s ) + out [-2] = blip_sample_t (0x7FFF - (s >> 24)); + } + } + + reader_accum = accum; + + remove_samples( count ); + + return count; +} + +void Blip_Buffer::mix_samples( const blip_sample_t* in, long count ) +{ + buf_t_* buf = &buffer_ [(offset_ >> BLIP_BUFFER_ACCURACY) + (widest_impulse_ / 2 - 1)]; + + int prev = 0; + while ( count-- ) { + int s = *in++; + *buf += s - prev; + prev = s; + ++buf; + } + *buf -= *--in; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Blip_Buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Blip_Buffer.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,248 @@ + +// Buffer of sound samples into which band-limited waveforms can be synthesized +// using Blip_Wave or Blip_Synth. + +// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +#include "blargg_common.h" + +class Blip_Reader; + +// Source time unit. +typedef long blip_time_t; + +// Type of sample produced. Signed 16-bit format. +typedef BOOST::int16_t blip_sample_t; + +// Make buffer as large as possible (currently about 65000 samples) +const int blip_default_length = 0; + +class Blip_Buffer { +public: + // Construct an empty buffer. + Blip_Buffer(); + ~Blip_Buffer(); + + // Set output sample rate and buffer length in milliseconds (1/1000 sec), + // then clear buffer. If length is not specified, make as large as possible. + // If there is insufficient memory for the buffer, sets the buffer length + // to 0 and returns error string (or propagates exception if compiler supports it). + blargg_err_t sample_rate( long samples_per_sec, int msec_length = blip_default_length ); + + // Length of buffer, in milliseconds + int length() const; + + // Current output sample rate + long sample_rate() const; + + // Number of source time units per second + void clock_rate( long ); + long clock_rate() const; + + // Set frequency at which high-pass filter attenuation passes -3dB + void bass_freq( int frequency ); + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clear out any samples waiting rather than the entire buffer. + void clear( bool entire_buffer = true ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begin + // a new time frame at the end of the current frame. All transitions must have + // been added before 'time'. + void end_frame( blip_time_t time ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Return number of samples actually read and removed. If stereo is + // true, increment 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, bool stereo = false ); + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + + // Experimental external buffer mixing support + + // Number of raw samples that can be mixed within frame of specified duration + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( const blip_sample_t* buf, long count ); + + + // not documented yet + + void remove_silence( long count ); + + typedef unsigned long resampled_time_t; + + resampled_time_t resampled_time( blip_time_t t ) const { + return t * resampled_time_t (factor_) + offset_; + } + + resampled_time_t resampled_duration( int t ) const { + return t * resampled_time_t (factor_); + } + +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); + + // Don't use the following members. They are public only for technical reasons. + public: + enum { widest_impulse_ = 24 }; + typedef BOOST::uint16_t buf_t_; + + unsigned long factor_; + resampled_time_t offset_; + buf_t_* buffer_; + unsigned buffer_size_; + private: + long reader_accum; + int bass_shift; + long samples_per_sec; + long clocks_per_sec; + int bass_freq_; + int length_; + + enum { accum_fract = 15 }; // less than 16 to give extra sample range + enum { sample_offset = 0x7F7F }; // repeated byte allows memset to clear buffer + + friend class Blip_Reader; +}; + +// Low-pass equalization parameters (see notes.txt) +class blip_eq_t { +public: + blip_eq_t( double treble = 0 ); + blip_eq_t( double treble, long cutoff, long sample_rate ); +private: + double treble; + long cutoff; + long sample_rate; + friend class Blip_Impulse_; +}; + +// not documented yet (see Multi_Buffer.cpp for an example of use) +class Blip_Reader { + const Blip_Buffer::buf_t_* buf; + long accum; + #ifdef __MWERKS__ + void operator = ( struct foobar ); // helps optimizer + #endif +public: + // avoid anything which might cause optimizer to put object in memory + + int begin( Blip_Buffer& blip_buf ) { + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum; + return blip_buf.bass_shift; + } + + int read() const { + return accum >> Blip_Buffer::accum_fract; + } + + void next( int bass_shift = 9 ) { + accum -= accum >> bass_shift; + accum += ((long) *buf++ - Blip_Buffer::sample_offset) << Blip_Buffer::accum_fract; + } + + void end( Blip_Buffer& blip_buf ) { + blip_buf.reader_accum = accum; + } +}; + + + +// End of public interface + +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +const int blip_res_bits_ = 5; + +typedef BOOST::uint32_t blip_pair_t_; + +class Blip_Impulse_ { + typedef BOOST::uint16_t imp_t; + + blip_eq_t eq; + double volume_unit_; + imp_t* impulses; + imp_t* impulse; + int width; + int fine_bits; + int res; + bool generate; + + void fine_volume_unit(); + void scale_impulse( int unit, imp_t* ) const; +public: + Blip_Buffer* buf; + BOOST::uint32_t offset; + + void init( blip_pair_t_* impulses, int width, int res, int fine_bits = 0 ); + void volume_unit( double ); + void treble_eq( const blip_eq_t& ); +}; + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), cutoff( 0 ), sample_rate( 44100 ) { +} + +inline blip_eq_t::blip_eq_t( double t, long c, long sr ) : + treble( t ), cutoff( c ), sample_rate( sr ) { +} + +inline int Blip_Buffer::length() const { + return length_; +} + +inline long Blip_Buffer::samples_avail() const { + return long (offset_ >> BLIP_BUFFER_ACCURACY); +} + +inline long Blip_Buffer::sample_rate() const { + return samples_per_sec; +} + +inline void Blip_Buffer::end_frame( blip_time_t t ) { + offset_ += t * factor_; + assert(( "Blip_Buffer::end_frame(): Frame went past end of buffer", + samples_avail() <= buffer_size_ )); +} + +inline void Blip_Buffer::remove_silence( long count ) { + assert(( "Blip_Buffer::remove_silence(): Tried to remove more samples than available", + count <= samples_avail() )); + offset_ -= resampled_time_t (count) << BLIP_BUFFER_ACCURACY; +} + +inline int Blip_Buffer::output_latency() const { + return widest_impulse_ / 2; +} + +inline long Blip_Buffer::clock_rate() const { + return clocks_per_sec; +} + +// MSVC6 fix +typedef Blip_Buffer::resampled_time_t blip_resampled_time_t; + +#include "Blip_Synth.h" + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Blip_Synth.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Blip_Synth.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,204 @@ + +// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding +// waveforms to a Blip_Buffer. + +// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BLIP_SYNTH_H +#define BLIP_SYNTH_H + +#ifndef BLIP_BUFFER_H + #include "Blip_Buffer.h" +#endif + +// Quality level. Higher levels are slower, and worse in a few cases. +// Use blip_good_quality as a starting point. +const int blip_low_quality = 1; +const int blip_med_quality = 2; +const int blip_good_quality = 3; +const int blip_high_quality = 4; + +// Blip_Synth is a transition waveform synthesizer which adds band-limited +// offsets (transitions) into a Blip_Buffer. For a simpler interface, use +// Blip_Wave (below). +// +// Range specifies the greatest expected offset that will occur. For a +// waveform that goes between +amp and -amp, range should be amp * 2 (half +// that if it only goes between +amp and 0). When range is large, a higher +// accuracy scheme is used; to force this even when range is small, pass +// the negative of range (i.e. -range). +template +class Blip_Synth { + BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 ); + BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 ); + enum { + abs_range = (range < 0) ? -range : range, + fine_mode = (range > 512 || range < 0), + width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_), + res = 1 << blip_res_bits_, + impulse_size = width / 2 * (fine_mode + 1), + base_impulses_size = width / 2 * (res / 2 + 1), + fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 : + abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 : + abs_range <= 2048 ? 7 : 8) : 0) + }; + blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size]; + Blip_Impulse_ impulse; +public: + Blip_Synth() { impulse.init( impulses, width, res, fine_bits ); } + + // Configure low-pass filter (see notes.txt). Not optimized for real-time control + void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); } + + // Set volume of a transition at amplitude 'range' by setting volume_unit + // to v / range + void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); } + + // Set base volume unit of transitions, where 1.0 is a full swing between the + // positive and negative extremes. Not optimized for real-time control. + void volume_unit( double unit ) { impulse.volume_unit( unit ); } + + // Default Blip_Buffer used for output when none is specified for a given call + Blip_Buffer* output() const { return impulse.buf; } + void output( Blip_Buffer* b ) { impulse.buf = b; } + + // Add an amplitude offset (transition) with an amplitude of delta * volume_unit + // into the specified buffer (default buffer if none specified) at the + // specified source time. Amplitude can be positive or negative. To increase + // performance by inlining code at the call site, use offset_inline(). + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + void offset_resampled( blip_resampled_time_t t, int o ) const { + offset_resampled( t, o, impulse.buf ); + } + void offset( blip_time_t t, int delta ) const { + offset( t, delta, impulse.buf ); + } + void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t time, int delta ) const { + offset_inline( time, delta, impulse.buf ); + } +}; + +// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer. +// A wave is built from a series of delays and new amplitudes. This provides a +// simpler interface than Blip_Synth. +template +class Blip_Wave { + Blip_Synth synth; + blip_time_t time_; + int last_amp; +public: + // Start wave at time 0 and amplitude 0 + Blip_Wave() : time_( 0 ), last_amp( 0 ) { } + + // See Blip_Synth for description + void volume( double v ) { synth.volume( v ); } + void volume_unit( double v ) { synth.volume_unit( v ); } + void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); } + Blip_Buffer* output() const { return synth.output(); } + void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; } + + // Current time in frame + blip_time_t time() const { return time_; } + void time( blip_time_t t ) { time_ = t; } + + // Current amplitude of wave + int amplitude() const { return last_amp; } + void amplitude( int ); + + // Move forward by 't' time units + void delay( blip_time_t t ) { time_ += t; } + + // End time frame of specified duration. Localize time to new frame. + void end_frame( blip_time_t duration ) { + assert(( "Blip_Wave::end_frame(): Wave hadn't yet been run for entire frame", + duration <= time_ )); + time_ -= duration; + } +}; + + + +// End of public interface + + template + void Blip_Wave::amplitude( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + synth.offset_inline( time_, delta ); + } + + template + inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const + { + typedef blip_pair_t_ pair_t; + + unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1; + assert(( "Blip_Synth/Blip_wave: Went past end of buffer", + sample_index < blip_buf->buffer_size_ )); + enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 }; + pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index]; + + enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ }; + enum { mask = res * 2 - 1 }; + const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size]; + + pair_t offset = impulse.offset * delta; + + if ( !fine_bits ) + { + // normal mode + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta; + t1 += imp [1] * delta; + imp += 2; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } + else + { + // fine mode + enum { sub_range = 1 << fine_bits }; + delta += sub_range / 2; + int delta2 = (delta & (sub_range - 1)) - sub_range / 2; + delta >>= fine_bits; + + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta2; + t0 += imp [1] * delta; + + t1 += imp [2] * delta2; + t1 += imp [3] * delta; + + imp += 4; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } + } + + template + void Blip_Synth::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Classic_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Classic_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,116 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Classic_Emu.h" + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Classic_Emu::Classic_Emu() +{ + buf = NULL; + std_buf = NULL; + set_equalizer( equalizer_t( -8.87, 8800 ) ); +} + +Classic_Emu::~Classic_Emu() +{ + delete std_buf; +} + +void Classic_Emu::update_eq_() +{ + update_eq( blip_eq_t( equalizer_.treble, equalizer_.cutoff, buf->sample_rate() ) ); + buf->bass_freq( equalizer_.bass ); +} + +void Classic_Emu::set_equalizer( const equalizer_t& eq ) +{ + equalizer_ = eq; + if ( buf ) + update_eq_(); +} + +blargg_err_t Classic_Emu::init( long sample_rate ) +{ + buf = NULL; + delete std_buf; + std_buf = NULL; + + Stereo_Buffer* sb = new Stereo_Buffer; + if ( !sb ) + return "Out of memory"; + std_buf = sb; + + BLARGG_RETURN_ERR( sb->sample_rate( sample_rate, 1000 / 20 ) ); + + buf = std_buf; + return blargg_success; +} + +void Classic_Emu::mute_voices( int mask ) +{ + require( buf ); // init() must have been called + + mute_mask_ = mask; + for ( int i = voice_count(); i--; ) + { + if ( mask & (1 << i) ) { + set_voice( i, NULL ); + } + else { + Multi_Buffer::channel_t ch = buf->channel( i ); + set_voice( i, ch.center, ch.left, ch.right ); + } + } +} + +blargg_err_t Classic_Emu::setup_buffer( long clock_rate ) +{ + require( buf ); // init() must have been called + + buf->clock_rate( clock_rate ); + update_eq_(); + return buf->set_channel_count( voice_count() ); +} + +void Classic_Emu::starting_track() +{ + require( buf ); // init() must have been called + + mute_voices( 0 ); + buf->clear(); +} + +blargg_err_t Classic_Emu::play( long count, sample_t* out ) +{ + require( buf ); // init() must have been called + + long remain = count; + while ( remain ) + { + remain -= buf->read_samples( &out [count - remain], remain ); + if ( remain ) + { + bool added_stereo = false; + blip_time_t cyc = run( buf->length(), &added_stereo ); + if ( !cyc ) + return "Emulation error"; + buf->end_frame( cyc, added_stereo ); + } + } + return blargg_success; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Classic_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Classic_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,69 @@ + +// Classic game music emulator interface base class for emulators which use Blip_Buffer +// for sound output. + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef CLASSIC_EMU_H +#define CLASSIC_EMU_H + +#include "Music_Emu.h" +class Blip_Buffer; +class blip_eq_t; +class Multi_Buffer; + +class Classic_Emu : public Music_Emu { +public: + Classic_Emu(); + ~Classic_Emu(); + + // Initialize emulator with specified sample rate. Sample output is in stereo. + virtual blargg_err_t init( long sample_rate ); + + // Initialize emulator using custom output buffer + blargg_err_t init( Multi_Buffer* buf ); + + // Frequency equalizer parameters (see notes.txt) + struct equalizer_t { + double treble; // treble level at 22kHz, in dB (-3.0dB = 0.50) + long cutoff; // beginning of low-pass rolloff, in Hz + long bass; // high-pass breakpoint, in Hz + equalizer_t( double treble_ = 0, long cutoff_ = 0, int bass_ = 33 ) : + treble( treble_ ), cutoff( cutoff_ ), bass( bass_ ) { } + }; + + // Current frequency equalizater parameters + const equalizer_t& equalizer() const; + + // Set frequency equalizer parameters + void set_equalizer( const equalizer_t& ); + + // See Music_Emu.h + void mute_voices( int ); + blargg_err_t play( long, sample_t* ); + + +// End of public interface +protected: + virtual void starting_track(); + virtual blargg_err_t setup_buffer( long clock_rate ); + virtual void set_voice( int index, Blip_Buffer* center, + Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ) = 0; + virtual long run( int msec, bool* added_stereo = NULL ) = 0; + virtual void update_eq( blip_eq_t const& ) = 0; +private: + Multi_Buffer* buf; + Multi_Buffer* std_buf; // owned + equalizer_t equalizer_; + void update_eq_(); +}; + +inline blargg_err_t Classic_Emu::init( Multi_Buffer* buf_ ) { + buf = buf_; + return blargg_success; +} +inline const Classic_Emu::equalizer_t& Classic_Emu::equalizer() const { + return equalizer_; +} +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Effects_Buffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Effects_Buffer.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,466 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Effects_Buffer.h" + +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +typedef long fixed_t; + +#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) +#define FMUL( x, y ) (((x) * (y)) >> 15) + +const unsigned echo_size = 4096; +const unsigned echo_mask = echo_size - 1; +BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2 + +const unsigned reverb_size = 8192 * 2; +const unsigned reverb_mask = reverb_size - 1; +BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2 + +Effects_Buffer::config_t::config_t() +{ + pan_1 = 0.0; + pan_2 = 0.0; + reverb_delay = 88; + reverb_level = 0.10; + echo_delay = 61; + echo_level = 0.12; + delay_variance = 18; + effects_enabled = false; +} + +Effects_Buffer::Effects_Buffer() +{ + echo_buf = NULL; + echo_pos = 0; + + reverb_buf = NULL; + reverb_pos = 0; + + stereo_remain = 0; + effect_remain = 0; + effects_enabled = false; + config( config_t() ); +} + +Effects_Buffer::~Effects_Buffer() +{ + delete [] echo_buf; + delete [] reverb_buf; +} + +blargg_err_t Effects_Buffer::sample_rate( long rate, int msec ) +{ + if ( !echo_buf ) + { + echo_buf = new blip_sample_t [echo_size]; + if ( !echo_buf ) + return "Out of memory"; + } + + if ( !reverb_buf ) + { + reverb_buf = new blip_sample_t [reverb_size]; + if ( !reverb_buf ) + return "Out of memory"; + } + + for ( int i = 0; i < buf_count; i++ ) + BLARGG_RETURN_ERR( bufs [i].sample_rate( rate, msec ) ); + + length_ = msec; + sample_rate_ = rate; + + config( config_ ); + + return blargg_success; +} + +void Effects_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Effects_Buffer::bass_freq( int freq ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( freq ); +} + +void Effects_Buffer::clear() +{ + stereo_remain = 0; + effect_remain = 0; + memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); + memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +inline int pin_range( int n, int max, int min = 0 ) +{ + if ( n < min ) + return min; + if ( n > max ) + return max; + return n; +} + +void Effects_Buffer::config( const config_t& cfg ) +{ + // clear echo and reverb buffers + if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf ) { + memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); + memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); + } + + config_ = cfg; + + if ( config_.effects_enabled ) + { + // convert to internal format + + chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); + chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; + + chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); + chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; + + chans.reverb_level = TO_FIXED( config_.reverb_level ); + chans.echo_level = TO_FIXED( config_.echo_level ); + + const int delay_offset = config_.delay_variance * sample_rate_ / (1000 * 2); + + const int reverb_sample_delay = config_.reverb_delay * sample_rate_ / 1000; + chans.reverb_delay_l = pin_range( reverb_size - + (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); + chans.reverb_delay_r = pin_range( reverb_size + 1 - + (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); + + const int echo_sample_delay = config_.echo_delay * sample_rate_ / 1000; + chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), + echo_size - 1 ); + chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), + echo_size - 1 ); + + // set up outputs + for ( unsigned i = 0; i < chan_count; i++ ) { + channel_t& o = channels [i]; + if ( i < 2 ) { + o.center = &bufs [i]; + o.left = &bufs [3]; + o.right = &bufs [4]; + } + else { + o.center = &bufs [2]; + o.left = &bufs [5]; + o.right = &bufs [6]; + } + } + + } + else { + // set up outputs + for ( unsigned i = 0; i < chan_count; i++ ) { + channel_t& o = channels [i]; + o.center = &bufs [0]; + o.left = &bufs [1]; + o.right = &bufs [2]; + } + } +} + +void Effects_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + if ( stereo ) + stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + if ( effects_enabled || config_.effects_enabled ) + effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + effects_enabled = config_.effects_enabled; +} + +#include BLARGG_ENABLE_OPTIMIZER + +long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) +{ + require( total_samples % 2 == 0 ); // count must be even + + long remain = bufs [0].samples_avail(); + if ( remain > (unsigned) total_samples / 2 ) + remain = (unsigned) total_samples / 2; + total_samples = remain; + while ( remain ) + { + int active_bufs = buf_count; + long count = remain; + + if ( effect_remain ) { + if ( count > effect_remain ) + count = effect_remain; + + if ( stereo_remain ) { + mix_enhanced( out, count ); + } + else { + mix_mono_enhanced( out, count ); + active_bufs = 3; + } + } + else if ( stereo_remain ) { + mix_stereo( out, count ); + active_bufs = 3; + } + else { + mix_mono( out, count ); + active_bufs = 1; + } + + out += count * 2; + remain -= count; + + stereo_remain -= count; + if ( stereo_remain < 0 ) + stereo_remain = 0; + + effect_remain -= count; + if ( effect_remain < 0 ) + effect_remain = 0; + + for ( int i = 0; i < buf_count; i++ ) { + if ( i < active_bufs ) + bufs [i].remove_samples( count ); + else + bufs [i].remove_silence( count ); // keep time synchronized + } + } + + return total_samples * 2; +} + +void Effects_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader c; + int shift = c.begin( bufs [0] ); + + // unrolled loop + for ( long n = count >> 1; n--; ) + { + long cs0 = c.read(); + c.next( shift ); + + long cs1 = c.read(); + c.next( shift ); + + if ( (BOOST::int16_t) cs0 != cs0 ) + cs0 = 0x7FFF - (cs0 >> 24); + ((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16); + + if ( (BOOST::int16_t) cs1 != cs1 ) + cs1 = 0x7FFF - (cs1 >> 24); + ((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16); + out += 4; + } + + if ( count & 1 ) { + int s = c.read(); + c.next( shift ); + out [0] = s; + out [1] = s; + if ( (BOOST::int16_t) s != s ) { + s = 0x7FFF - (s >> 24); + out [0] = s; + out [1] = s; + } + } + + c.end( bufs [0] ); +} + +void Effects_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader l; l.begin( bufs [1] ); + Blip_Reader r; r.begin( bufs [2] ); + Blip_Reader c; + int shift = c.begin( bufs [0] ); + + while ( count-- ) { + int cs = c.read(); + c.next( shift ); + int left = cs + l.read(); + int right = cs + r.read(); + l.next( shift ); + r.next( shift ); + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + + c.end( bufs [0] ); + r.end( bufs [2] ); + l.end( bufs [1] ); +} + +void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out, long count ) +{ + Blip_Reader sq1; sq1.begin( bufs [0] ); + Blip_Reader sq2; sq2.begin( bufs [1] ); + Blip_Reader center; + int shift = center.begin( bufs [2] ); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next( shift ); + sq2.next( shift ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next( shift ); + + int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end( bufs [0] ); + sq2.end( bufs [1] ); + center.end( bufs [2] ); +} + +void Effects_Buffer::mix_enhanced( blip_sample_t* out, long count ) +{ + Blip_Reader l1; l1.begin( bufs [3] ); + Blip_Reader r1; r1.begin( bufs [4] ); + Blip_Reader l2; l2.begin( bufs [5] ); + Blip_Reader r2; r2.begin( bufs [6] ); + Blip_Reader sq1; sq1.begin( bufs [0] ); + Blip_Reader sq2; sq2.begin( bufs [1] ); + Blip_Reader center; + int shift = center.begin( bufs [2] ); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next( shift ); + sq2.next( shift ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + l1.read() + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + r1.read() + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + l1.next( shift ); + r1.next( shift ); + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next( shift ); + + int left = new_reverb_l + sum3_s + l2.read() + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + r2.read() + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + l2.next( shift ); + r2.next( shift ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end( bufs [0] ); + sq2.end( bufs [1] ); + center.end( bufs [2] ); + l1.end( bufs [3] ); + r1.end( bufs [4] ); + l2.end( bufs [5] ); + r2.end( bufs [6] ); +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Effects_Buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Effects_Buffer.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,91 @@ + +// Multi-channel effects buffer with panning, echo and reverb effects + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef EFFECTS_BUFFER_H +#define EFFECTS_BUFFER_H + +#include "Multi_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Effects_Buffer : public Multi_Buffer { +public: + Effects_Buffer(); + ~Effects_Buffer(); + + // Channel Effect Center Pan + // --------------------------------- + // 0 reverb pan_1 + // 1 reverb pan_2 + // 2 echo - + // 3 echo - + // 4 echo - + + // Channel configuration + struct config_t { + double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right + double pan_2; + double echo_delay; // msec + double echo_level; // 0.0 to 1.0 + double reverb_delay; // msec + double delay_variance; // difference between left/right delays (msec) + double reverb_level; // 0.0 to 1.0 + bool effects_enabled; // if false, use optimized simple mixer + config_t(); + }; + + // Set configuration of buffer + void config( const config_t& ); + + // See Multi_Buffer.h + blargg_err_t sample_rate( long samples_per_sec, int msec ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool was_stereo = true ); + long read_samples( blip_sample_t*, long ); + + +// End of public interface +private: + typedef long fixed_t; + + enum { buf_count = 7 }; + Blip_Buffer bufs [buf_count]; + enum { chan_count = 5 }; + channel_t channels [chan_count]; + config_t config_; + long stereo_remain; + long effect_remain; + bool effects_enabled; + + blip_sample_t* reverb_buf; + blip_sample_t* echo_buf; + int reverb_pos; + int echo_pos; + + struct { + fixed_t pan_1_levels [2]; + fixed_t pan_2_levels [2]; + int echo_delay_l; + int echo_delay_r; + fixed_t echo_level; + int reverb_delay_l; + int reverb_delay_r; + fixed_t reverb_level; + } chans; + + void mix_mono( blip_sample_t*, long ); + void mix_stereo( blip_sample_t*, long ); + void mix_enhanced( blip_sample_t*, long ); + void mix_mono_enhanced( blip_sample_t*, long ); +}; + + inline Effects_Buffer::channel_t Effects_Buffer::channel( int i ) { + return channels [i % chan_count]; + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Fir_Resampler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Fir_Resampler.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,273 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Fir_Resampler.h" + +#include +#include +#include +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +static const double pi = 3.1415926535897932384626433832795029L; + +const bool show_impulse = 0; + +class Dsf { + double rolloff; + double factor; +public: + Dsf( double r ) : rolloff( r ) { + factor = 1.0; + //if ( rolloff < 1.0 ) + // factor = 1.0 / (*this)( 0 ); + } + + double operator () ( double angle ) const + { + double const n_harm = 256; + angle /= n_harm; + double pow_a_n = pow( rolloff, n_harm ); + double rescale = 1.0 / n_harm; + + double num = 1.0 - rolloff * cos( angle ) - + pow_a_n * cos( n_harm * angle ) + + pow_a_n * rolloff * cos( (n_harm - 1) * angle ); + double den = 1 + rolloff * (rolloff - 2 * cos( angle )); + + return (num / den - 1) / n_harm * factor; + } +}; + +template +void gen_sinc( int width, double offset, double spacing, int count, double scale, T* p, + const Sinc& sinc ) +{ + double range = pi * (width / 2); + double step = pi * spacing; + double a = -step * (count / 2 - 1); + a -= offset * step; + + while ( count-- ) + { + double w = a / range; + double y = 0.0; + if ( fabs( w ) < 1.0 ) + { + y = cos( pi * w ) * 0.5 + 0.5; + y *= sinc( a ); + } + + *p++ = y * scale; + a += step; + } +} + +static double plain_sinc( double a ) { + return fabs( a ) < 0.00001 ? 1.0 : sin( a ) / a; +} + +Fir_Resampler::Fir_Resampler() +{ + res = 1; + skip_bits = 0; + step = 2; + buf = NULL; + write_pos = NULL; + buf_size = 0; +} + +Fir_Resampler::~Fir_Resampler() { + free( buf ); +} + +void Fir_Resampler::clear() +{ + imp = 0; + if ( buf ) { + write_pos = buf + latency; + memset( buf, 0, (write_pos - buf) * sizeof *buf ); + } +} + +blargg_err_t Fir_Resampler::buffer_size( int new_size ) +{ + new_size += latency; + void* new_buf = realloc( buf, new_size * sizeof *buf ); + if ( !new_buf ) + return "Out of memory"; + buf = (sample_t*) new_buf; + buf_size = new_size; + clear(); + return blargg_success; +} + +double Fir_Resampler::time_ratio( double ratio, double rolloff, double volume ) +{ + this->ratio = ratio; + + double fstep = 0.0; + { + double least_error = 2; + double pos = 0; + res = -1; + for ( int r = 1; r <= max_res; r++ ) { + pos += ratio; + double nearest = floor( pos + 0.5 ); + double error = fabs( pos - nearest ); + if ( error < least_error ) { + res = r; + fstep = nearest / res; + least_error = error; + } + } + } + + skip_bits = 0; + + step = 2 * (int) floor( fstep ); + + ratio = fstep; + fstep = fmod( fstep, 1.0 ); + + double filter = (ratio < 1.0) ? 1.0 : 1.0 / ratio; + double pos = 0.0; + Dsf dsf( rolloff ); + for ( int i = 0; i < res; i++ ) + { + if ( show_impulse ) + printf( "pos = %f\n", pos ); + + gen_sinc( int (width * filter + 1) & ~1, pos, filter, (int) width, + double (0x7fff * volume * filter), impulses [i], dsf ); + + if ( show_impulse ) { + for ( int j = 0; j < width; j++ ) + printf( "%d ", (int) impulses [i] [j] ); + printf( "\n" ); + } + + pos += fstep; + if ( pos >= 0.9999999 ) { + pos -= 1.0; + skip_bits |= 1 << i; + } + } + + if ( show_impulse ) { + printf( "skip = %X\n", skip_bits ); + printf( "step = %d\n", step ); + } + + clear(); + + return ratio; +} + +#include BLARGG_ENABLE_OPTIMIZER + +int Fir_Resampler::read( sample_t* out_begin, int count ) +{ + sample_t* out = out_begin; + const sample_t* in = buf; + sample_t* end_pos = write_pos; + unsigned long skip = skip_bits >> imp; + sample_t const* imp = impulses [this->imp]; + int remain = res - this->imp; + int const step = this->step; + + count = (count >> 1) + 1; + + // to do: optimize loop to use a single counter rather than 'in' and 'count' + + if ( end_pos - in >= width * 2 ) + { + end_pos -= width * 2; + do + { + count--; + + // accumulate in extended precision + long l = 0; + long r = 0; + + const sample_t* i = in; + if ( !count ) + break; + + for ( int n = width / 2; n--; ) + { + int pt0 = imp [0]; + int pt1 = imp [1]; + imp += 2; + l += (pt0 * i [0]) + (pt1 * i [2]); + r += (pt0 * i [1]) + (pt1 * i [3]); + i += 4; + } + + remain--; + + l >>= 15; + r >>= 15; + + in += step + ((skip * 2) & 2); + skip >>= 1; + + if ( !remain ) { + imp = impulses [0]; + skip = skip_bits; + remain = res; + } + + out [0] = l; + out [1] = r; + out += 2; + } + while ( in <= end_pos ); + } + + this->imp = res - remain; + + int left = write_pos - in; + write_pos = buf + left; + assert( unsigned (write_pos - buf) <= buf_size ); + memmove( buf, in, left * sizeof *in ); + + return out - out_begin; +} + +int Fir_Resampler::skip_input( int count ) +{ + int remain = write_pos - buf; + int avail = remain - width * 2; + if ( count > avail ) + count = avail; + + remain -= count; + write_pos = buf + remain; + assert( unsigned (write_pos - buf) <= buf_size ); + memmove( buf, buf + count, remain * sizeof *buf ); + + return count; +} + +/* +int Fir_Resampler::skip( int count ) +{ + count = int (count * ratio) & ~1; + count = skip_input( count ); + return int (count / ratio) & ~1; +} +*/ diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Fir_Resampler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Fir_Resampler.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,76 @@ + +// Finite Impulse Response (FIR) Resampler + +// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. + +#ifndef FIR_RESAMPLER_H +#define FIR_RESAMPLER_H + +#include "blargg_common.h" + +class Fir_Resampler { + enum { width = 24 }; + enum { latency = (width - 1) * 2 }; +public: + typedef short sample_t; + + Fir_Resampler(); + ~Fir_Resampler(); + + // interface hasn't been stabilized yet + + // Set size of buffer. Return true if out of memory. + blargg_err_t buffer_size( int ); + + // Set input/output resampling ratio and frequency rolloff. Return + // actual (rounded) ratio used. + double time_ratio( double ratio, double rolloff = 0.999, double volume = 1.0 ); + + // Remove any buffered samples and clear buffer + void clear(); + + // Pointer to buffer to write input samples to + sample_t* buffer() { return write_pos; } + + // Maximum number of samples that can be written to buffer at current position + int max_write() const { return buf_size - (write_pos - buf); } + + // Number of unread input samples + int written() const { return write_pos - buf - latency; } + + // Advance buffer position by 'count' samples. Call after writing 'count' samples + // to buffer(). + void write( int count ); + + // True if there are there aren't enough input samples to read at least one + // output sample. + bool empty() const { return (write_pos - buf) <= latency; } + + // Resample and output at most 'count' into 'buf', then remove input samples from + // buffer. Return number of samples written into 'buf'. + int read( sample_t* buf, int count ); + + // Skip at most 'count' *input* samples. Return number of samples actually skipped. + int skip_input( int count ); + +private: + enum { max_res = 32 }; + + sample_t* buf; + sample_t* write_pos; + double ratio; + int buf_size; + int res; + int imp; + unsigned long skip_bits; + int step; + sample_t impulses [max_res] [width]; +}; + + inline void Fir_Resampler::write( int count ) { + write_pos += count; + assert( unsigned (write_pos - buf) <= buf_size ); + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gb_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Apu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,227 @@ + +// Gb_Snd_Emu 0.1.3. http://www.slack.net/~ant/libs/ + +#include "Gb_Apu.h" + +#include +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Gb_Apu::Gb_Apu() +{ + square1.synth = &square_synth; + square2.synth = &square_synth; + square1.has_sweep = true; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = &wave; + oscs [3] = &noise; + + volume( 1.0 ); + reset(); +} + +Gb_Apu::~Gb_Apu() +{ +} + +void Gb_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + wave.synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); +} + +void Gb_Apu::volume( double vol ) +{ + vol *= 0.60 / osc_count; + square_synth.volume( vol ); + wave.synth.volume( vol ); + noise.synth.volume( vol ); +} + +void Gb_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Gb_Apu::reset() +{ + next_frame_time = 0; + last_time = 0; + frame_count = 0; + stereo_found = false; + + square1.reset(); + square2.reset(); + wave.reset(); + noise.reset(); + + memset( regs, 0, sizeof regs ); +} + +void Gb_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + + Gb_Osc& osc = *oscs [index]; + if ( center && !left && !right ) + { + // mono + left = center; + right = center; + } + else + { + // must be silenced or stereo + require( (!left && !right) || (left && right) ); + } + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Gb_Apu::run_until( gb_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + if ( end_time == last_time ) + return; + + while ( true ) + { + gb_time_t time = next_frame_time; + if ( time > end_time ) + time = end_time; + + // run oscillators + for ( int i = 0; i < osc_count; ++i ) { + Gb_Osc& osc = *oscs [i]; + if ( osc.output ) { + if ( osc.output != osc.outputs [3] ) + stereo_found = true; + osc.run( last_time, time ); + } + } + last_time = time; + + if ( time == end_time ) + break; + + next_frame_time += 4194304 / 256; // 256 Hz + + // 256 Hz actions + square1.clock_length(); + square2.clock_length(); + wave.clock_length(); + noise.clock_length(); + + frame_count = (frame_count + 1) & 3; + if ( frame_count == 0 ) { + // 64 Hz actions + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } + + if ( frame_count & 1 ) + square1.clock_sweep(); // 128 Hz action + } +} + +bool Gb_Apu::end_frame( gb_time_t end_time ) +{ + run_until( end_time ); + + next_frame_time -= end_time; + assert( next_frame_time >= 0 ); + last_time = 0; + + bool result = stereo_found; + stereo_found = false; + return result; +} + +void Gb_Apu::write_register( gb_time_t time, gb_addr_t addr, int data ) +{ + // function now takes actual address, i.e. 0xFFXX + require( start_addr <= addr && addr <= end_addr ); + require( (unsigned) data <= 0xff ); + + int reg = addr - start_addr; + if ( (unsigned) reg >= register_count ) + return; + + run_until( time ); + + regs [reg] = data; + + if ( addr < 0xff24 ) + { + // oscillator + int index = reg / 5; + oscs [index]->write_register( reg - index * 5, data ); + } + else if ( addr == 0xff25 || addr == 0xff26 ) + { + int flags = (regs [0xff26 - start_addr] & 0x80) ? regs [0xff25 - start_addr] : 0; + + // left/right assignments + for ( int i = 0; i < osc_count; i++ ) + { + Gb_Osc& osc = *oscs [i]; + int bits = flags >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = ((bits >> 3) & 2) | (bits & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output && osc.last_amp ) { + if ( old_output ) + square_synth.offset( time, -osc.last_amp, old_output ); + osc.last_amp = 0; + } + } + } + else if ( addr >= 0xff30 ) + { + // separate samples now (simplifies oscillator) + int index = (addr & 0x0f) * 2; + wave.wave [index] = data >> 4; + wave.wave [index + 1] = data & 0x0f; + } +} + +int Gb_Apu::read_register( gb_time_t time, gb_addr_t addr ) +{ + // function now takes actual address, i.e. 0xFFXX + require( start_addr <= addr && addr <= end_addr ); + + run_until( time ); + + int data = regs [addr - start_addr]; + + if ( addr == 0xff26 ) { // status + data &= 0xf0; + for ( int i = 0; i < osc_count; i++ ) { + const Gb_Osc& osc = *oscs [i]; + if ( osc.enabled && (osc.length || !osc.length_enabled) ) + data |= 1 << i; + } + } + + return data; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gb_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Apu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,91 @@ + +// Nintendo Game Boy PAPU sound chip emulator + +// Gb_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef GB_APU_H +#define GB_APU_H + +typedef long gb_time_t; // clock cycle count +typedef unsigned gb_addr_t; // 16-bit address + +#include "Gb_Oscs.h" + +class Gb_Apu { +public: + Gb_Apu(); + ~Gb_Apu(); + + // Overall volume of all oscillators, where 1.0 is full volume. + void volume( double ); + + // Treble equalization (see notes.txt). + void treble_eq( const blip_eq_t& ); + + // Reset oscillators and internal state. + void reset(); + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silence all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign oscillator output to buffer(s). Valid indicies are 0 to + // osc_count - 1, which refer to Square 1, Square 2, Wave, and + // Noise, respectively. If buffer is NULL, silence oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reads and writes at addr must satisfy start_addr <= addr <= end_addr + enum { start_addr = 0xff10 }; + enum { end_addr = 0xff3f }; + enum { register_count = end_addr - start_addr + 1 }; + + // Write 'data' to address at specified time. Previous writes and reads + // within the current frame must not have specified a later time. + void write_register( gb_time_t, gb_addr_t, int data ); + + // Read from address at specified time. Previous writes and reads within + // the current frame must not have specified a later time. + int read_register( gb_time_t, gb_addr_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new frame at time 0. Return true if any oscillators added + // sound to one of the left/right buffers, false if they only added + // to the center buffer. + bool end_frame( gb_time_t ); + + static void begin_debug_log(); +private: + // noncopyable + Gb_Apu( const Gb_Apu& ); + Gb_Apu& operator = ( const Gb_Apu& ); + + Gb_Osc* oscs [osc_count]; + gb_time_t next_frame_time; + gb_time_t last_time; + int frame_count; + bool stereo_found; + + Gb_Square square1; + Gb_Square square2; + Gb_Wave wave; + Gb_Noise noise; + Gb_Square::Synth square_synth; // shared between squares + BOOST::uint8_t regs [register_count]; + + void run_until( gb_time_t ); + friend class Gb_Apu_Reflector; +}; + + inline void Gb_Apu::output( Blip_Buffer* mono ) { + output( mono, NULL, NULL ); + } + + inline void Gb_Apu::osc_output( int index, Blip_Buffer* mono ) { + osc_output( index, mono, NULL, NULL ); + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gb_Cpu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Cpu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,1117 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Gb_Cpu.h" + +#include +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Common instructions: +// +// 365880 FA LD A,IND16 +// 355863 20 JR NZ +// 313655 21 LD HL,IMM +// 274580 28 JR Z +// 252878 FE CMP IMM +// 230541 7E LD A,(HL) +// 226209 2A LD A,(HL+) +// 217467 CD CALL +// 212034 C9 RET +// 208376 CB CB prefix +// +// 27486 CB 7E BIT 7,(HL) +// 15925 CB 76 BIT 6,(HL) +// 13035 CB 19 RR C +// 11557 CB 7F BIT 7,A +// 10898 CB 37 SWAP A +// 10208 CB 66 BIT 4,(HL) + +static void write_unmapped( Gbs_Emu*, gb_addr_t, int ) +{ + check( false ); +} + +static int read_unmapped( Gbs_Emu*, gb_addr_t ) +{ + check( false ); + return 0; +} + +Gb_Cpu::Gb_Cpu() +{ + rst_base = 0; + callback_data = NULL; + data_writer [page_count] = write_unmapped; + data_reader [page_count] = read_unmapped; + reset(); +} + +void Gb_Cpu::reset( const void* unmapped_code_page ) +{ + interrupts_enabled = false; + remain_ = 0; + + r.pc = 0; + r.sp = 0; + r.flags = 0; + r.a = 0; + r.b = 0; + r.c = 0; + r.d = 0; + r.e = 0; + r.h = 0; + r.l = 0; + + for ( int i = 0; i < page_count + 1; i++ ) + code_map [i] = (const uint8_t*) unmapped_code_page; + + map_memory( 0, 0x10000, read_unmapped, write_unmapped ); +} + +void Gb_Cpu::map_code( gb_addr_t start, unsigned long size, const void* data ) +{ + // start end end must fall on page bounadries + require( start % page_size == 0 && size % page_size == 0 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + code_map [first_page + i] = (uint8_t*) data + i * page_size; +} + +void Gb_Cpu::map_memory( gb_addr_t start, unsigned long size, reader_t read, writer_t write ) +{ + // start end end must fall on page bounadries + require( start % page_size == 0 && size % page_size == 0 ); + + if ( !read ) + read = read_unmapped; + if ( !write ) + write = write_unmapped; + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) { + data_reader [first_page + i] = read; + data_writer [first_page + i] = write; + } +} + +// Note: 'addr' is evaulated more than once in the following macros, so it +// must not contain side-effects. + +#define READ( addr ) (data_reader [(addr) >> page_bits]( callback_data, addr )) +#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data )) + +#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [(addr) & (page_size - 1)]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +int Gb_Cpu::read( gb_addr_t addr ) { + return READ( addr ); +} + +BOOST::uint8_t* Gb_Cpu::get_code( gb_addr_t addr ) { + return (uint8_t*) &READ_PROG( addr ); +} + +void Gb_Cpu::write( gb_addr_t addr, int data ) { + WRITE( addr, data ); +} + +#ifndef GB_CPU_GLUE_ONLY + +const unsigned z_flag = 0x80; +const unsigned n_flag = 0x40; +const unsigned h_flag = 0x20; +const unsigned c_flag = 0x10; + +#include BLARGG_ENABLE_OPTIMIZER + +Gb_Cpu::result_t Gb_Cpu::run( long cycle_count ) +{ + const int cycles_per_instruction = 4; + + remain_ = cycle_count + cycles_per_instruction; + + Gb_Cpu::result_t result = result_cycles; + +#if BLARGG_CPU_POWERPC + const reader_t* data_reader = this->data_reader; // cache + const writer_t* data_writer = this->data_writer; // cache +#endif + + union { + struct { +#if BLARGG_BIG_ENDIAN + uint8_t b, c, d, e, h, l, unused, a; +# define R8( n ) (r8_ [n]) +#elif BLARGG_LITTLE_ENDIAN + uint8_t c, b, e, d, l, h, a, unused; +# define R8( n ) (r8_ [(n) ^ 1]) +#else +# error "Byte order of CPU must be known." +#endif + } rg; // registers + struct { + BOOST::uint16_t bc, de, hl, unused; // pairs + } rp; + uint8_t r8_ [8]; // indexed registers (use R8 macro due to endian dependence) + BOOST::uint16_t r16 [4]; // indexed pairs + }; + BOOST_STATIC_ASSERT( sizeof rg == 8 && sizeof rp == 8 ); + + rg.a = r.a; + rg.b = r.b; + rg.c = r.c; + rg.d = r.d; + rg.e = r.e; + rg.h = r.h; + rg.l = r.l; + unsigned pc = r.pc; + unsigned sp = r.sp; + unsigned flags = r.flags; + + goto loop; + + unsigned data; + +jr_taken: + pc += (BOOST::int8_t) data; +inc_pc_loop: + pc++; +loop: + + check( (unsigned) pc < 0x10000 ); + check( (unsigned) sp < 0x10000 ); + check( (flags & ~0xf0) == 0 ); + + // Read opcode and first operand. Optimize if processor's byte order is known + // and non-portable constructs are allowed. +#if BLARGG_NONPORTABLE && BLARGG_BIG_ENDIAN + data = *(BOOST::uint16_t*) &READ_PROG( pc ); + pc++; + unsigned op = data >> 8; + data = (uint8_t) data; + +#elif BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN + data = *(BOOST::uint16_t*) &READ_PROG( pc ); + pc++; + unsigned op = (uint8_t) data; + data >>= 8; + +#else + unsigned op = READ_PROG( pc ); + pc++; + data = READ_PROG( pc ); + +#endif + + if ( (remain_ -= cycles_per_instruction) <= 0 ) + goto stop; + + switch ( op ) + { + +// Most Common + + case 0x20: // JR NZ + if ( !(flags & z_flag) ) + goto jr_taken; + goto inc_pc_loop; + + case 0x21: // LD HL,IMM (common) + rp.hl = READ_PROG16( pc ); + pc += 2; + goto loop; + + case 0x28: // JR Z + if ( flags & z_flag ) + goto jr_taken; + goto inc_pc_loop; + + { + unsigned temp; + + case 0xF0: // LD A,(0xff00+imm) + temp = data + 0xff00; + pc++; + goto ld_a_ind_comm; + + case 0xF2: // LD A,(0xff00+C) + temp = rg.c + 0xff00; + goto ld_a_ind_comm; + + case 0x0A: // LD A,(BC) + temp = rp.bc; + goto ld_a_ind_comm; + + case 0x3A: // LD A,(HL-) + temp = rp.hl; + rp.hl = temp - 1; + goto ld_a_ind_comm; + + case 0x1A: // LD A,(DE) + temp = rp.de; + goto ld_a_ind_comm; + + case 0x2A: // LD A,(HL+) (common) + temp = rp.hl; + rp.hl = temp + 1; + goto ld_a_ind_comm; + + case 0xFA: // LD A,IND16 (common) + temp = READ_PROG16( pc ); + pc += 2; + ld_a_ind_comm: + rg.a = READ( temp ); + goto loop; + } + + case 0xBE: // CMP (HL) + data = READ( rp.hl ); + goto cmp_comm; + + case 0xB8: // CMP B + case 0xB9: // CMP C + case 0xBA: // CMP D + case 0xBB: // CMP E + case 0xBC: // CMP H + case 0xBD: // CMP L + data = R8( op & 7 ); + goto cmp_comm; + + case 0xFE: // CMP IMM + pc++; + cmp_comm: + op = rg.a; + data = op - data; + sub_set_flags: + flags = ((op & 15) - (data & 15)) & h_flag; + flags |= (data >> 4) & c_flag; + flags |= n_flag; + if ( data & 0xff ) + goto loop; + flags |= z_flag; + goto loop; + + case 0x46: // LD B,(HL) + case 0x4E: // LD C,(HL) + case 0x56: // LD D,(HL) + case 0x5E: // LD E,(HL) + case 0x66: // LD H,(HL) + case 0x6E: // LD L,(HL) + case 0x7E: // LD A,(HL) + R8( (op >> 3) & 7 ) = READ( rp.hl ); + goto loop; + + case 0xC4: // CNZ (next-most-common) + pc += 2; + if ( flags & z_flag ) + goto loop; + call: + pc -= 2; + case 0xCD: // CALL (most-common) + data = pc + 2; + pc = READ_PROG16( pc ); + push: + sp--; + WRITE( sp, data >> 8 ); + sp--; + WRITE( sp, data & 0xff ); + goto loop; + + case 0xC8: // RNZ (next-most-common) + if ( !(flags & z_flag) ) + goto loop; + case 0xC9: // RET (most common) + ret: + pc = READ( sp ); + pc += 0x100 * READ( sp + 1 ); + sp += 2; + goto loop; + + case 0x00: // NOP + case 0x40: // LD B,B + case 0x49: // LD C,C + case 0x52: // LD D,D + case 0x5B: // LD E,E + case 0x64: // LD H,H + case 0x6D: // LD L,L + case 0x7F: // LD A,A + goto loop; + +// CB Instructions + + case 0xCB: + pc++; + // now data is the opcode + switch ( data ) { + + { + int temp; + + case 0x46: // BIT b,(HL) + case 0x4E: + case 0x56: + case 0x5E: + case 0x66: + case 0x6E: + case 0x76: + case 0x7E: + temp = READ( rp.hl ); + goto bit_comm; + + case 0x40: case 0x41: case 0x42: case 0x43: // BIT b,r + case 0x44: case 0x45: case 0x47: case 0x48: + case 0x49: case 0x4A: case 0x4B: case 0x4C: + case 0x4D: case 0x4F: case 0x50: case 0x51: + case 0x52: case 0x53: case 0x54: case 0x55: + case 0x57: case 0x58: case 0x59: case 0x5A: + case 0x5B: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: + case 0x64: case 0x65: case 0x67: case 0x68: + case 0x69: case 0x6A: case 0x6B: case 0x6C: + case 0x6D: case 0x6F: case 0x70: case 0x71: + case 0x72: case 0x73: case 0x74: case 0x75: + case 0x77: case 0x78: case 0x79: case 0x7A: + case 0x7B: case 0x7C: case 0x7D: case 0x7F: + temp = R8( data & 7 ); + bit_comm: + int bit = (~data >> 3) & 7; + flags &= ~n_flag; + flags |= h_flag | z_flag; + flags ^= (temp << bit) & z_flag; + goto loop; + } + + case 0x86: // RES b,(HL) + case 0x8E: + case 0x96: + case 0x9E: + case 0xA6: + case 0xAE: + case 0xB6: + case 0xBE: + case 0xC6: // SET b,(HL) + case 0xCE: + case 0xD6: + case 0xDE: + case 0xE6: + case 0xEE: + case 0xF6: + case 0xFE: { + int temp = READ( rp.hl ); + int bit = 1 << ((data >> 3) & 7); + temp &= ~bit; + if ( !(data & 0x40) ) + bit = 0; + WRITE( rp.hl, temp | bit ); + goto loop; + } + + case 0xC0: case 0xC1: case 0xC2: case 0xC3: // SET b,r + case 0xC4: case 0xC5: case 0xC7: case 0xC8: + case 0xC9: case 0xCA: case 0xCB: case 0xCC: + case 0xCD: case 0xCF: case 0xD0: case 0xD1: + case 0xD2: case 0xD3: case 0xD4: case 0xD5: + case 0xD7: case 0xD8: case 0xD9: case 0xDA: + case 0xDB: case 0xDC: case 0xDD: case 0xDF: + case 0xE0: case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE7: case 0xE8: + case 0xE9: case 0xEA: case 0xEB: case 0xEC: + case 0xED: case 0xEF: case 0xF0: case 0xF1: + case 0xF2: case 0xF3: case 0xF4: case 0xF5: + case 0xF7: case 0xF8: case 0xF9: case 0xFA: + case 0xFB: case 0xFC: case 0xFD: case 0xFF: + R8( data & 7 ) |= 1 << ((data >> 3) & 7); + goto loop; + + case 0x80: case 0x81: case 0x82: case 0x83: // RES b,r + case 0x84: case 0x85: case 0x87: case 0x88: + case 0x89: case 0x8A: case 0x8B: case 0x8C: + case 0x8D: case 0x8F: case 0x90: case 0x91: + case 0x92: case 0x93: case 0x94: case 0x95: + case 0x97: case 0x98: case 0x99: case 0x9A: + case 0x9B: case 0x9C: case 0x9D: case 0x9F: + case 0xA0: case 0xA1: case 0xA2: case 0xA3: + case 0xA4: case 0xA5: case 0xA7: case 0xA8: + case 0xA9: case 0xAA: case 0xAB: case 0xAC: + case 0xAD: case 0xAF: case 0xB0: case 0xB1: + case 0xB2: case 0xB3: case 0xB4: case 0xB5: + case 0xB7: case 0xB8: case 0xB9: case 0xBA: + case 0xBB: case 0xBC: case 0xBD: case 0xBF: + R8( data & 7 ) &= ~(1 << ((data >> 3) & 7)); + goto loop; + + { + int temp; + case 0x36: // SWAP (HL) + temp = READ( rp.hl ); + goto swap_comm; + + case 0x30: // SWAP B + case 0x31: // SWAP C + case 0x32: // SWAP D + case 0x33: // SWAP E + case 0x34: // SWAP H + case 0x35: // SWAP L + case 0x37: // SWAP A + temp = R8( data & 7 ); + swap_comm: + op = (temp >> 4) | (temp << 4); + flags = 0; + goto shift_comm; + } + +// Shift/Rotate + + case 0x06: // RLC (HL) + case 0x16: // RL (HL) + case 0x26: // SLA (HL) + op = READ( rp.hl ); + goto rl_comm; + + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x27: // SLA A + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x07: // RLC A + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x17: // RL A + op = R8( data & 7 ); + goto rl_comm; + + case 0x3E: // SRL (HL) + data += 0x10; // bump up to 0x4n to avoid preserving sign bit + case 0x1E: // RR (HL) + case 0x0E: // RRC (HL) + case 0x2E: // SRA (HL) + op = READ( rp.hl ); + goto rr_comm; + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3F: // SRL A + data += 0x10; // bump up to 0x4n + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1F: // RR A + case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0F: // RRC A + case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2F: // SRA A + op = R8( data & 7 ); + goto rr_comm; + + } // CB op + assert( false ); // unhandled CB op + + case 0x07: // RLCA + case 0x17: // RLA + data = op; + op = rg.a; + rl_comm: + op <<= 1; + op |= ((data & flags) >> 4) & 1; // RL and carry is set + flags = (op >> 4) & c_flag; // C = bit shifted out + if ( data < 0x10 ) // RLC + op |= op >> 8; + // SLA doesn't fill lower bit + goto shift_comm; + + case 0x0F: // RRCA + case 0x1F: // RRA + data = op; + op = rg.a; + rr_comm: + op |= (data & flags) << 4; // RR and carry is set + flags = (op << 4) & c_flag; // C = bit shifted out + if ( data < 0x10 ) // RRC + op |= op << 8; + op >>= 1; + if ( data & 0x20 ) // SRA propagates sign bit + op |= (op << 1) & 0x80; + shift_comm: + data &= 7; + if ( !(op & 0xff) ) + flags |= z_flag; + if ( data == 6 ) + goto write_hl_op_ff; + R8( data ) = op; + goto loop; + +// Load + + case 0x70: // LD (HL),B + case 0x71: // LD (HL),C + case 0x72: // LD (HL),D + case 0x73: // LD (HL),E + case 0x74: // LD (HL),H + case 0x75: // LD (HL),L + case 0x77: // LD (HL),A + op = R8( op & 7 ); + write_hl_op_ff: + WRITE( rp.hl, op & 0xff ); + goto loop; + + case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47: // LD r,r + case 0x48: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4F: + case 0x50: case 0x51: case 0x53: case 0x54: case 0x55: case 0x57: + case 0x58: case 0x59: case 0x5A: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x65: case 0x67: + case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6F: + case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: + R8( (op >> 3) & 7 ) = R8( op & 7 ); + goto loop; + + case 0x08: // LD IND16,SP + data = READ_PROG16( pc ); + pc += 2; + WRITE( data, sp&0xff ); + data++; + WRITE( data, sp >> 8 ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + + case 0x31: // LD SP,IMM + sp = READ_PROG16( pc ); + pc += 2; + goto loop; + + case 0x01: // LD BC,IMM + case 0x11: // LD DE,IMM + r16 [op >> 4] = READ_PROG16( pc ); + pc += 2; + goto loop; + + { + unsigned temp; + case 0xE0: // LD (0xff00+imm),A + temp = data + 0xff00; + pc++; + goto write_data_rg_a; + + case 0xE2: // LD (0xff00+C),A + temp = rg.c + 0xff00; + goto write_data_rg_a; + + case 0x32: // LD (HL-),A + temp = rp.hl; + rp.hl = temp - 1; + goto write_data_rg_a; + + case 0x02: // LD (BC),A + temp = rp.bc; + goto write_data_rg_a; + + case 0x12: // LD (DE),A + temp = rp.de; + goto write_data_rg_a; + + case 0x22: // LD (HL+),A + temp = rp.hl; + rp.hl = temp + 1; + goto write_data_rg_a; + + case 0xEA: // LD IND16,A (common) + temp = READ_PROG16( pc ); + pc += 2; + write_data_rg_a: + WRITE( temp, rg.a ); + goto loop; + } + + case 0x06: // LD B,IMM + rg.b = data; + goto inc_pc_loop; + + case 0x0E: // LD C,IMM + rg.c = data; + goto inc_pc_loop; + + case 0x16: // LD D,IMM + rg.d = data; + goto inc_pc_loop; + + case 0x1E: // LD E,IMM + rg.e = data; + goto inc_pc_loop; + + case 0x26: // LD H,IMM + rg.h = data; + goto inc_pc_loop; + + case 0x2E: // LD L,IMM + rg.l = data; + goto inc_pc_loop; + + case 0x36: // LD (HL),IMM + WRITE( rp.hl, data ); + goto inc_pc_loop; + + case 0x3E: // LD A,IMM + rg.a = data; + goto inc_pc_loop; + +// Increment/Decrement + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + r16 [op >> 4]++; + goto loop; + + case 0x33: // INC SP + sp++; + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + r16 [op >> 4]--; + goto loop; + + case 0x3B: // DEC SP + sp--; + goto loop; + + case 0x34: // INC (HL) + op = rp.hl; + data = READ( op ); + data++; + WRITE( op, data & 0xff ); + goto inc_comm; + + case 0x04: // INC B + case 0x0C: // INC C (common) + case 0x14: // INC D + case 0x1C: // INC E + case 0x24: // INC H + case 0x2C: // INC L + case 0x3C: // INC A + op = (op >> 3) & 7; + R8( op ) = data = R8( op ) + 1; + inc_comm: + flags = (flags & c_flag) | (((data & 15) - 1) & h_flag) | ((data >> 1) & z_flag); + goto loop; + + case 0x35: // DEC (HL) + op = rp.hl; + data = READ( op ); + data--; + WRITE( op, data & 0xff ); + goto dec_comm; + + case 0x05: // DEC B + case 0x0D: // DEC C + case 0x15: // DEC D + case 0x1D: // DEC E + case 0x25: // DEC H + case 0x2D: // DEC L + case 0x3D: // DEC A + op = (op >> 3) & 7; + data = R8( op ) - 1; + R8( op ) = data; + dec_comm: + flags = (flags & c_flag) | n_flag | (((data & 15) + 0x31) & h_flag); + if ( data & 0xff ) + goto loop; + flags |= z_flag; + goto loop; + +// Add 16-bit + + { + unsigned long temp; // need more than 16 bits for carry + unsigned prev; + + case 0xF8: // LD HL,SP+imm + temp = BOOST::int8_t (data) & 0xffff; // sign-extend to 16 bits + pc++; + flags = 0; + temp += sp; + prev = sp; + goto add_16_hl; + + case 0xE8: // ADD SP,IMM + temp = BOOST::int8_t (data) & 0xffff; // sign-extend to 16 bits + pc++; + flags = 0; + temp += sp; + prev = sp; + sp = temp; + goto add_16_comm; + + case 0x39: // ADD HL,SP + temp = sp; + goto add_hl_comm; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + temp = r16 [op >> 4]; + add_hl_comm: + prev = rp.hl; + temp += prev; + flags &= z_flag; + add_16_hl: + rp.hl = temp; + add_16_comm: + flags |= (temp >> 12) & c_flag; + flags |= (((temp & 0x0fff) - (prev & 0x0fff)) >> 7) & h_flag; + goto loop; + } + + case 0x86: // ADD (HL) + data = READ( rp.hl ); + goto add_comm; + + case 0x80: // ADD B + case 0x81: // ADD C + case 0x82: // ADD D + case 0x83: // ADD E + case 0x84: // ADD H + case 0x85: // ADD L + case 0x87: // ADD A + data = R8( op & 7 ); + goto add_comm; + + case 0xC6: // ADD IMM + pc++; + add_comm: + flags = rg.a; + data += flags; + flags = ((data & 15) - (flags & 15)) & h_flag; + flags |= (data >> 4) & c_flag; + rg.a = data; + if ( data & 0xff ) + goto loop; + flags |= z_flag; + goto loop; + +// Add/Subtract + + case 0x8E: // ADC (HL) + data = READ( rp.hl ); + goto adc_comm; + + case 0x88: // ADC B + case 0x89: // ADC C + case 0x8A: // ADC D + case 0x8B: // ADC E + case 0x8C: // ADC H + case 0x8D: // ADC L + case 0x8F: // ADC A + data = R8( op & 7 ); + goto adc_comm; + + case 0xCE: // ADC IMM + pc++; + adc_comm: + data += (flags >> 4) & 1; + data &= 0xff; // to do: does carry get set when sum + carry = 0x100? + goto add_comm; + + case 0x96: // SUB (HL) + data = READ( rp.hl ); + goto sub_comm; + + case 0x90: // SUB B + case 0x91: // SUB C + case 0x92: // SUB D + case 0x93: // SUB E + case 0x94: // SUB H + case 0x95: // SUB L + case 0x97: // SUB A + data = R8( op & 7 ); + goto sub_comm; + + case 0xD6: // SUB IMM + pc++; + sub_comm: + op = rg.a; + data = op - data; + rg.a = data; + goto sub_set_flags; + + case 0x9E: // SBC (HL) + data = READ( rp.hl ); + goto sbc_comm; + + case 0x98: // SBC B + case 0x99: // SBC C + case 0x9A: // SBC D + case 0x9B: // SBC E + case 0x9C: // SBC H + case 0x9D: // SBC L + case 0x9F: // SBC A + data = R8( op & 7 ); + goto sbc_comm; + + case 0xDE: // SBC IMM + pc++; + sbc_comm: + data += (flags >> 4) & 1; + data &= 0xff; // to do: does carry get set when sum + carry = 0x100? + goto sub_comm; + +// Logical + + case 0xA0: // AND B + case 0xA1: // AND C + case 0xA2: // AND D + case 0xA3: // AND E + case 0xA4: // AND H + case 0xA5: // AND L + data = R8( op & 7 ); + goto and_comm; + + case 0xA6: // AND (HL) + data = READ( rp.hl ); + pc--; + case 0xE6: // AND IMM + pc++; + and_comm: + rg.a &= data; + case 0xA7: // AND A + flags = h_flag | (((rg.a - 1) >> 1) & z_flag); + goto loop; + + case 0xB0: // OR B + case 0xB1: // OR C + case 0xB2: // OR D + case 0xB3: // OR E + case 0xB4: // OR H + case 0xB5: // OR L + data = R8( op & 7 ); + goto or_comm; + + case 0xB6: // OR (HL) + data = READ( rp.hl ); + pc--; + case 0xF6: // OR IMM + pc++; + or_comm: + rg.a |= data; + case 0xB7: // OR A + flags = ((rg.a - 1) >> 1) & z_flag; + goto loop; + + case 0xA8: // XOR B + case 0xA9: // XOR C + case 0xAA: // XOR D + case 0xAB: // XOR E + case 0xAC: // XOR H + case 0xAD: // XOR L + data = R8( op & 7 ); + goto xor_comm; + + case 0xAE: // XOR (HL) + data = READ( rp.hl ); + pc--; + case 0xEE: // XOR IMM + pc++; + xor_comm: + data ^= rg.a; + rg.a = data; + data--; + flags = (data >> 1) & z_flag; + goto loop; + + case 0xAF: // XOR A + rg.a = 0; + flags = z_flag; + goto loop; + +// Stack + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1:{// POP HL (common) + int temp = READ( sp ); + r16 [(op >> 4) & 3] = temp + 0x100 * READ( sp + 1 ); + sp += 2; + goto loop; + } + + case 0xF1: // POP FA + rg.a = READ( sp ); + flags = READ( sp + 1 ) & 0xf0; + sp += 2; + goto loop; + + case 0xC5: // PUSH BC + data = rp.bc; + goto push; + + case 0xD5: // PUSH DE + data = rp.de; + goto push; + + case 0xE5: // PUSH HL + data = rp.hl; + goto push; + + case 0xF5: // PUSH FA + data = (flags << 8) | rg.a; + goto push; + +// Flow control + + case 0xC7: case 0xCF: case 0xD7: case 0xDF: // RST + case 0xE7: case 0xEF: case 0xF7: case 0xFF: + data = pc; + pc = (op & 0x38) + rst_base; + goto push; + + case 0xCC: // CZ + pc += 2; + if ( flags & z_flag ) + goto call; + goto loop; + + case 0xD4: // CNC + pc += 2; + if ( !(flags & c_flag) ) + goto call; + goto loop; + + case 0xDC: // CC + pc += 2; + if ( flags & c_flag ) + goto call; + goto loop; + + case 0xD9: // RETI + Gb_Cpu::interrupts_enabled = 1; + goto ret; + + case 0xC0: // RZ + if ( !(flags & z_flag) ) + goto ret; + goto loop; + + case 0xD0: // RNC + if ( !(flags & c_flag) ) + goto ret; + goto loop; + + case 0xD8: // RC + if ( flags & c_flag ) + goto ret; + goto loop; + + case 0x18: // JR + goto jr_taken; + + case 0x30: // JR NC + if ( !(flags & c_flag) ) + goto jr_taken; + goto inc_pc_loop; + + case 0x38: // JR C + if ( flags & c_flag ) + goto jr_taken; + goto inc_pc_loop; + + case 0xE9: // JP_HL + pc = rp.hl; + goto loop; + + case 0xC3: // JP (next-most-common) + pc = READ_PROG16( pc ); + goto loop; + + case 0xC2: // JP NZ + pc += 2; + if ( !(flags & z_flag) ) + goto jp_taken; + goto loop; + + case 0xCA: // JP Z (most common) + pc += 2; + if ( !(flags & z_flag) ) + goto loop; + jp_taken: + pc -= 2; + pc = READ_PROG16( pc ); + goto loop; + + case 0xD2: // JP NC + pc += 2; + if ( !(flags & c_flag) ) + goto jp_taken; + goto loop; + + case 0xDA: // JP C + pc += 2; + if ( flags & c_flag ) + goto jp_taken; + goto loop; + +// Flags + + case 0x2F: // CPL + rg.a = ~rg.a; + flags |= n_flag | h_flag; + goto loop; + + case 0x3F: // CCF + flags = (flags ^ c_flag) & ~(n_flag | h_flag); + goto loop; + + case 0x37: // SCF + flags = (flags | c_flag) & ~(n_flag | h_flag); + goto loop; + + case 0xF3: // DI + interrupts_enabled = 0; + goto loop; + + case 0xFB: // EI + interrupts_enabled = 1; + goto loop; + +// Special + + case 0xDD: case 0xD3: case 0xDB: case 0xE3: case 0xE4: // ? + case 0xEB: case 0xEC: case 0xF4: case 0xFD: case 0xFC: + case 0x10: // STOP + case 0x27: // DAA (I'll have to implement this eventually...) + case 0xBF: + case 0xED: // Z80 prefix + result = Gb_Cpu::result_badop; + goto stop; + + case 0x76: // HALT + result = Gb_Cpu::result_halt; + goto stop; + } + + assert( false ); // all opcodes should end with a goto + +stop: + pc--; + + // copy state back + r.pc = pc; + r.sp = sp; + r.flags = flags; + r.a = rg.a; + r.b = rg.b; + r.c = rg.c; + r.d = rg.d; + r.e = rg.e; + r.h = rg.h; + r.l = rg.l; + + return result; +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gb_Cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Cpu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,97 @@ + +// Nintendo Game Boy CPU emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef GB_CPU_H +#define GB_CPU_H + +#include "blargg_common.h" + +typedef unsigned gb_addr_t; // 16-bit address + +class Gbs_Emu; + +// Game Boy CPU emulator. Currently treats every instruction as taking 4 cycles. +class Gb_Cpu { + typedef BOOST::uint8_t uint8_t; + enum { page_bits = 8 }; + enum { page_count = 0x10000 >> page_bits }; + const uint8_t* code_map [page_count + 1]; + long remain_; +public: + Gb_Cpu(); + + // Set all registers to 0, unmap all memory, and map all code pages + // to unmapped_page. + void reset( const void* unmapped_page = NULL ); + + // Memory read/write function types. Memory reader return value must be 0 to 255. + Gbs_Emu* callback_data; // passed to memory read/write functions + typedef int (*reader_t)( Gbs_Emu* callback_data, gb_addr_t ); + typedef void (*writer_t)( Gbs_Emu* callback_data, gb_addr_t, int ); + + // Memory mapping functions take a block of memory of specified 'start' address + // and 'size' in bytes. Both start address and size must be a multiple of page_size. + enum { page_size = 1L << page_bits }; + + // Map code memory to 'code' (memory accessed via the program counter) + void map_code( gb_addr_t start, unsigned long size, const void* code ); + + // Map data memory to read and write functions + void map_memory( gb_addr_t start, unsigned long size, reader_t, writer_t ); + + // Access memory as the emulated CPU does. + int read( gb_addr_t ); + void write( gb_addr_t, int data ); + uint8_t* get_code( gb_addr_t ); // for use in a debugger + + // Game Boy Z80 registers. *Not* kept updated during a call to run(). + struct registers_t { + BOOST::uint16_t pc; + BOOST::uint16_t sp; + uint8_t flags; + uint8_t a; + uint8_t b; + uint8_t c; + uint8_t d; + uint8_t e; + uint8_t h; + uint8_t l; + } r; + + // Interrupt enable flag set by EI and cleared by DI. + bool interrupts_enabled; + + // Base address for RST vectors (normally 0). + gb_addr_t rst_base; + + // Reasons that run() returns + enum result_t { + result_cycles, // Requested number of cycles (or more) were executed + result_halt, // PC is at HALT instruction + result_badop // PC is at bad (unimplemented) instruction + }; + + // Run CPU for at least 'count' cycles, or until one of the above conditions + // arises. Return reason for stopping. + result_t run( long count ); + + // Number of clock cycles remaining for current run() call. + long remain() const; + +private: + // noncopyable + Gb_Cpu( const Gb_Cpu& ); + Gb_Cpu& operator = ( const Gb_Cpu& ); + + reader_t data_reader [page_count + 1]; // extra entry to catch overflow addresses + writer_t data_writer [page_count + 1]; +}; + + inline long Gb_Cpu::remain() const { + return remain_; + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gb_Oscs.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Oscs.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,404 @@ + +// Gb_Snd_Emu 0.1.3. http://www.slack.net/~ant/libs/ + +#include "Gb_Apu.h" + +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +const int trigger = 0x80; + +// Gb_Osc + +Gb_Osc::Gb_Osc() +{ + output = NULL; + outputs [0] = NULL; + outputs [1] = NULL; + outputs [2] = NULL; + outputs [3] = NULL; +} + +void Gb_Osc::reset() +{ + delay = 0; + last_amp = 0; + period = 2048; + volume = 0; + frequency = 0; + length = 0; + enabled = false; + length_enabled = false; + output_select = 3; + output = outputs [output_select]; +} + +void Gb_Osc::clock_length() +{ + if ( length_enabled && length ) + --length; +} + +void Gb_Osc::write_register( int reg, int value ) +{ + if ( reg == 4 ) + length_enabled = value & 0x40; +} + +// Gb_Env + +void Gb_Env::reset() +{ + env_period = 0; + env_dir = 0; + env_delay = 0; + new_env_period = 0; + new_env_dir = 0; + new_volume = 0; + Gb_Osc::reset(); +} + +Gb_Env::Gb_Env() { +} + +void Gb_Env::clock_envelope() +{ + if ( env_delay && !--env_delay ) { + env_delay = env_period; + if ( env_dir ) { + if ( volume < 15 ) + ++volume; + } + else if ( volume > 0 ) { + --volume; + } + } +} + +void Gb_Env::write_register( int reg, int value ) +{ + if ( reg == 2 ) { + new_env_period = value & 7; + new_env_dir = value & 8; + new_volume = value >> 4; + if ( value == 0 && volume ) + // to do: find correct behavior + volume = 0; + //enabled = new_volume != 0; + enabled = true; + } + else if ( reg == 4 && (value & trigger) ) { + env_period = new_env_period; + env_delay = new_env_period; + env_dir = new_env_dir; + volume = new_volume; + } + Gb_Osc::write_register( reg, value ); +} + +// Gb_Square + +void Gb_Square::reset() +{ + phase = 1; + duty = 1; + + sweep_period = 0; + sweep_delay = 0; + sweep_shift = 0; + sweep_dir = 0; + sweep_freq = 0; + + Gb_Env::reset(); +} + +Gb_Square::Gb_Square() +{ + has_sweep = false; +} + +void Gb_Square::clock_sweep() +{ + if ( sweep_period && sweep_delay && !--sweep_delay ) { + sweep_delay = sweep_period; + frequency = sweep_freq; + period = (2048 - frequency) * 4; + + int offset = sweep_freq >> sweep_shift; + if ( sweep_dir ) + offset = -offset; + sweep_freq += offset; + if ( sweep_freq < 0 || sweep_freq >= 2048 ) { + sweep_delay = 0; + sweep_freq = 2048; // stop sound output + } + } +} + +void Gb_Square::write_register( int reg, int value ) +{ + switch ( reg ) { + case 0: + sweep_period = (value >> 4) & 3; + sweep_shift = value & 7; + sweep_dir = value & 0x08; + break; + + case 1: + length = 64 - (value & 0x3f); + duty = (value >> 5) & 6; // duty = { 1, 2, 4, 6 } + if ( !duty ) + duty = 1; + break; + + case 3: + frequency = (frequency & ~0xFF) + value; + break; + + case 4: + frequency = (value & 7) * 0x100 + (frequency & 0xFF); + if ( value & trigger ) { + sweep_freq = frequency; + if ( has_sweep && sweep_period && sweep_shift ) { + sweep_delay = 1; + clock_sweep(); + sweep_delay = sweep_period; + } + enabled = true; + } + break; + } + + period = (2048 - frequency) * 4; + + Gb_Env::write_register( reg, value ); +} + +void Gb_Square::run( gb_time_t time, gb_time_t end_time ) +{ + if ( !enabled || (!length && length_enabled) || !volume || sweep_freq == 2048 ) { + if ( last_amp ) { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + int amp = (phase < duty) ? volume : -volume; + if ( amp != last_amp ) { + synth->offset( time, amp - last_amp, output ); + last_amp = amp; + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const int duty = this->duty; + int phase = this->phase; + amp *= 2; + do { + phase = (phase + 1) & 7; + if ( phase == 0 || phase == duty ) { + amp = -amp; + synth->offset_inline( time, amp, output ); + } + time += period; + } + while ( time < end_time ); + + this->phase = phase; + last_amp = amp >> 1; + } + delay = time - end_time; + } +} + + +// Gb_Wave + +void Gb_Wave::reset() +{ + volume_shift = 0; + wave_pos = 0; + memset( wave, 0, sizeof wave ); + Gb_Osc::reset(); +} + +Gb_Wave::Gb_Wave() { +} + +void Gb_Wave::write_register( int reg, int value ) +{ + switch ( reg ) { + case 0: + enabled = value & 0x80; + break; + + case 1: + length = 256 - value; + break; + + case 2: + volume = ((value >> 5) & 3); + volume_shift = (volume - 1) & 7; // silence = 7 + break; + + case 3: + frequency = (frequency & ~0xFF) + value; + break; + + case 4: + frequency = (value & 7) * 0x100 + (frequency & 0xFF); + //if ( value & trigger ) + // wave_pos = 0; + break; + + } + + period = (2048 - frequency) * 2; + + Gb_Osc::write_register( reg, value ); +} + +void Gb_Wave::run( gb_time_t time, gb_time_t end_time ) +{ + if ( !enabled || (!length && length_enabled) || !volume ) { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + // wave data or shift may have changed + int diff = (wave [wave_pos] >> volume_shift) * 2 - last_amp; + if ( diff ) { + last_amp += diff; + synth.offset( time, diff, output ); + } + + time += delay; + if ( time < end_time ) + { + unsigned wave_pos = this->wave_pos; + + do { + wave_pos = (wave_pos + 1) % wave_size; + int amp = (wave [wave_pos] >> volume_shift) * 2; + int diff = amp - last_amp; + if ( diff ) { + last_amp = amp; + synth.offset_inline( time, diff, output ); + } + time += period; + } + while ( time < end_time ); + + this->wave_pos = wave_pos; + } + delay = time - end_time; + } +} + + +// Gb_Noise + +void Gb_Noise::reset() +{ + bits = 1; + tap = 14; + Gb_Env::reset(); +} + +Gb_Noise::Gb_Noise() { +} + +void Gb_Noise::write_register( int reg, int value ) +{ + if ( reg == 1 ) { + length = 64 - (value & 0x3f); + } + else if ( reg == 3 ) { + tap = 14 - (value & 8); + // noise formula and frequency tested against Metroid 2 and Zelda LA + int divisor = (value & 7) * 16; + if ( !divisor ) + divisor = 8; + period = divisor << (value >> 4); + } + else if ( reg == 4 && value & trigger ) { + bits = ~0u; + } + + Gb_Env::write_register( reg, value ); +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Gb_Noise::run( gb_time_t time, gb_time_t end_time ) +{ + if ( !enabled || (!length && length_enabled) || !volume ) { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + int amp = bits & 1 ? -volume : volume; + if ( amp != last_amp ) { + synth.offset( time, amp - last_amp, output ); + last_amp = amp; + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + // keep parallel resampled time to eliminate multiplication in the loop + const Blip_Buffer::resampled_time_t resampled_period = + output->resampled_duration( period ); + Blip_Buffer::resampled_time_t resampled_time = output->resampled_time( time ); + const unsigned mask = ~(1u << tap); + unsigned bits = this->bits; + amp *= 2; + + do { + unsigned feedback = bits; + bits >>= 1; + feedback = 1 & (feedback ^ bits); + time += period; + bits = (feedback << tap) | (bits & mask); + // feedback just happens to be true only when the level needs to change + // (the previous and current bits are different) + if ( feedback ) { + amp = -amp; + synth.offset_resampled( resampled_time, amp, output ); + } + resampled_time += resampled_period; + } + while ( time < end_time ); + + this->bits = bits; + last_amp = amp >> 1; + } + delay = time - end_time; + } +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gb_Oscs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gb_Oscs.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,97 @@ + +// Private oscillators used by Gb_Apu + +// Gb_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef GB_OSCS_H +#define GB_OSCS_H + +#include "Blip_Buffer.h" + +struct Gb_Osc { + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + + int delay; + int last_amp; + int period; + int volume; + int frequency; + int length; + bool enabled; + bool length_enabled; + + Gb_Osc(); + + void clock_length(); + void reset(); + virtual void run( gb_time_t begin, gb_time_t end ) = 0; + virtual void write_register( int reg, int value ); +}; + +struct Gb_Env : Gb_Osc { + int env_period; + int env_dir; + int env_delay; + int new_env_period; + int new_env_dir; + int new_volume; + + Gb_Env(); + void reset(); + void clock_envelope(); + void write_register( int, int ); +}; + +struct Gb_Square : Gb_Env { + int phase; + int duty; + + int sweep_period; + int sweep_delay; + int sweep_shift; + int sweep_dir; + int sweep_freq; + bool has_sweep; + + typedef Blip_Synth Synth; + const Synth* synth; + + Gb_Square(); + void reset(); + void run( gb_time_t, gb_time_t ); + void write_register( int, int ); + void clock_sweep(); +}; + +struct Gb_Wave : Gb_Osc { + int volume_shift; + unsigned wave_pos; + enum { wave_size = 32 }; + BOOST::uint8_t wave [wave_size]; + + typedef Blip_Synth Synth; + Synth synth; + + Gb_Wave(); + void reset(); + void run( gb_time_t, gb_time_t ); + void write_register( int, int ); +}; + +struct Gb_Noise : Gb_Env { + unsigned bits; + int tap; + + typedef Blip_Synth Synth; + Synth synth; + + Gb_Noise(); + void reset(); + void run( gb_time_t, gb_time_t ); + void write_register( int, int ); +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gbs_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gbs_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,352 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Gbs_Emu.h" + +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +const long clock_rate = 4194304; +const long bank_size = 0x4000; +const gb_addr_t ram_addr = 0xa000; +const gb_addr_t halt_addr = 0x9eff; +static BOOST::uint8_t unmapped_code [Gb_Cpu::page_size]; + +// RAM + +int Gbs_Emu::read_ram( Gbs_Emu* emu, gb_addr_t addr ) +{ + return emu->ram [addr - ram_addr]; +} + +void Gbs_Emu::write_ram( Gbs_Emu* emu, gb_addr_t addr, int data ) +{ + emu->ram [addr - ram_addr] = data; +} + +// Unmapped + +int Gbs_Emu::read_unmapped( Gbs_Emu*, gb_addr_t addr ) +{ + dprintf( "Read from unmapped memory $%.4x\n", (unsigned) addr ); + return 0xff; // open bus value (probably due to pull-up resistors) +} + +void Gbs_Emu::write_unmapped( Gbs_Emu*, gb_addr_t addr, int ) +{ + dprintf( "Wrote to unmapped memory $%.4x\n", (unsigned) addr ); +} + +// ROM + +int Gbs_Emu::read_rom( Gbs_Emu* emu, gb_addr_t addr ) +{ + return emu->rom [addr]; +} + +int Gbs_Emu::read_bank( Gbs_Emu* emu, gb_addr_t addr ) +{ + return emu->rom_bank [addr & (bank_size - 1)]; +} + +void Gbs_Emu::set_bank( int n ) +{ + if ( n >= bank_count ) { + n = 0; + dprintf( "Set to non-existent bank %d\n", (int) n ); + } + if ( n == 0 && bank_count > 1 ) + dprintf( "Selected ROM bank 0\n" ); + rom_bank = &rom [n * bank_size]; + cpu.map_code( bank_size, bank_size, rom_bank ); +} + +void Gbs_Emu::write_rom( Gbs_Emu* emu, gb_addr_t addr, int data ) +{ + if ( unsigned (addr - 0x2000) < 0x2000 ) + emu->set_bank( data & 0x1f ); +} + +// I/O: Timer, APU + +void Gbs_Emu::set_timer( int modulo, int rate ) +{ + if ( timer_mode ) + play_period = gb_time_t (256 - modulo) << (((rate - 1) & 3) * 2 + 4 - double_speed); +} + +inline gb_time_t Gbs_Emu::clock() const +{ + return cpu_time - cpu.remain(); +} + +int Gbs_Emu::read_io( Gbs_Emu* emu, gb_addr_t addr ) +{ + // hi_page is accessed most + if ( addr >= 0xff80 ) + return emu->hi_page [addr & 0xff]; + + if ( unsigned (addr - Gb_Apu::start_addr) <= Gb_Apu::register_count ) + return emu->apu.read_register( emu->clock(), addr ); + + if ( addr == 0xff00 ) + return 0; // joypad + + dprintf( "Unhandled I/O read 0x%4x\n", (unsigned) addr ); + + return 0xff; +} + +void Gbs_Emu::write_io( Gbs_Emu* emu, gb_addr_t addr, int data ) +{ + // apu is accessed most + if ( unsigned (addr - Gb_Apu::start_addr) < Gb_Apu::register_count ) { + emu->apu.write_register( emu->clock(), addr, data ); + } + else { + emu->hi_page [addr & 0xff] = data; + + if ( addr == 0xff06 || addr == 0xff07 ) + emu->set_timer( emu->hi_page [6], emu->hi_page [7] ); + + if ( addr == 0xffff ) + dprintf( "Wrote interrupt mask\n" ); + } +} + +Gbs_Emu::Gbs_Emu( double gain ) +{ + rom = NULL; + + apu.volume( gain ); + + // to do: decide on equalization parameters + set_equalizer( equalizer_t( -32, 8000, 90 ) ); + + // unmapped code is all HALT instructions + memset( unmapped_code, 0x76, sizeof unmapped_code ); + + // cpu + cpu.callback_data = this; + cpu.reset( unmapped_code ); + cpu.map_memory( 0x0000, 0x4000, read_rom, write_rom ); + cpu.map_memory( 0x4000, 0x4000, read_bank, write_rom ); + cpu.map_memory( 0x8000, 0x8000, read_unmapped, write_unmapped ); + cpu.map_memory( ram_addr, 0x4000, read_ram, write_ram ); + cpu.map_code( ram_addr, 0x4000, ram ); + cpu.map_code( 0xff00, 0x0100, hi_page ); + cpu.map_memory( 0xff00, 0x0100, read_io, write_io ); +} + +Gbs_Emu::~Gbs_Emu() +{ + unload(); +} + +void Gbs_Emu::unload() +{ + delete [] rom; + rom = NULL; + cpu.r.pc = halt_addr; +} + +void Gbs_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + apu.osc_output( i, c, l, r ); +} + +void Gbs_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +blargg_err_t Gbs_Emu::load( const header_t& h, Emu_Reader& in ) +{ + unload(); + + // check compatibility + if ( 0 != memcmp( h.tag, "GBS", 3 ) ) + return "Not a GBS file"; + if ( h.vers != 1 ) + return "Unsupported GBS format"; + + // gather relevant fields + load_addr = get_le16( h.load_addr ); + init_addr = get_le16( h.init_addr ); + play_addr = get_le16( h.play_addr ); + stack_ptr = get_le16( h.stack_ptr ); + double_speed = (h.timer_mode & 0x80) != 0; + timer_modulo_init = h.timer_modulo; + timer_mode = h.timer_mode; + if ( !(timer_mode & 0x04) ) + timer_mode = 0; // using vbl + + #ifndef NDEBUG + { + if ( h.timer_mode & 0x78 ) + dprintf( "TAC field has extra bits set: 0x%02x\n", (unsigned) h.timer_mode ); + + if ( load_addr < 0x400 || load_addr >= 0x8000 || + init_addr < 0x400 || init_addr >= 0x8000 || + play_addr < 0x400 || play_addr >= 0x8000 ) + dprintf( "Load/init/play address violates GBS spec.\n" ); + } + #endif + + // rom + bank_count = (load_addr + in.remain() + bank_size - 1) / bank_size; + long rom_size = bank_count * bank_size; + rom = new BOOST::uint8_t [rom_size]; + if ( !rom ) + return "Out of memory"; + memset( rom, 0, rom_size ); + blargg_err_t err = in.read( &rom [load_addr], in.remain() ); + if ( err ) { + unload(); + return err; + } + + // cpu + cpu.rst_base = load_addr; + cpu.map_code( 0x0000, 0x4000, rom ); + + voice_count_ = Gb_Apu::osc_count; + track_count_ = h.track_count; + + return setup_buffer( clock_rate ); +} + +const char** Gbs_Emu::voice_names() const +{ + static const char* names [] = { "Square 1", "Square 2", "Wave", "Noise" }; + return names; +} + +// Emulation + +static const BOOST::uint8_t sound_data [Gb_Apu::register_count] = { + 0x80, 0xbf, 0x00, 0x00, 0xbf, // square 1 + 0x00, 0x3f, 0x00, 0x00, 0xbf, // square 2 + 0x7f, 0xff, 0x9f, 0x00, 0xbf, // wave + 0x00, 0xff, 0x00, 0x00, 0xbf, // noise + + 0x77, 0xf3, 0xf1, // vin/volume, status, power mode + + 0, 0, 0, 0, 0, 0, 0, 0, 0, // unused + + 0xac, 0xdd, 0xda, 0x48, 0x36, 0x02, 0xcf, 0x16, // waveform data + 0x2c, 0x04, 0xe5, 0x2c, 0xac, 0xdd, 0xda, 0x48 +}; + +void Gbs_Emu::cpu_jsr( gb_addr_t addr ) +{ + cpu.write( --cpu.r.sp, cpu.r.pc >> 8 ); + cpu.write( --cpu.r.sp, cpu.r.pc&0xff ); + cpu.r.pc = addr; +} + +blargg_err_t Gbs_Emu::start_track( int track_index ) +{ + require( rom ); // file must be loaded + require( (unsigned) track_index < track_count() ); + + starting_track(); + + apu.reset(); + + memset( ram, 0, sizeof ram ); + memset( hi_page, 0, sizeof hi_page ); + + // configure hardware + set_bank( bank_count > 1 ); + for ( int i = 0; i < sizeof sound_data; i++ ) + apu.write_register( 0, i + apu.start_addr, sound_data [i] ); + play_period = 70224; // 59.73 Hz + set_timer( timer_modulo_init, timer_mode ); // ignored if using vbl + next_play = play_period; + + // set up init call + cpu.r.a = track_index; + cpu.r.b = 0; + cpu.r.c = 0; + cpu.r.d = 0; + cpu.r.e = 0; + cpu.r.h = 0; + cpu.r.l = 0; + cpu.r.flags = 0; + cpu.r.pc = halt_addr; + cpu.r.sp = stack_ptr; + cpu_jsr( init_addr ); + + return blargg_success; +} + +blip_time_t Gbs_Emu::run( int msec, bool* added_stereo ) +{ + require( rom ); // file must be loaded + + gb_time_t duration = clock_rate * (1.0 / 1000.0) * msec; + cpu_time = 0; + while ( cpu_time < duration ) + { + // check for idle cpu + if ( cpu.r.pc == halt_addr ) + { + if ( next_play > duration ) { + cpu_time = duration; + break; + } + + if ( cpu_time < next_play ) + cpu_time = next_play; + next_play += play_period; + cpu_jsr( play_addr ); + } + + long count = duration - cpu_time; + cpu_time = duration; + Gb_Cpu::result_t result = cpu.run( count ); + cpu_time -= cpu.remain(); + + if ( (result == Gb_Cpu::result_halt && cpu.r.pc != halt_addr) || + result == Gb_Cpu::result_badop ) + { + if ( result == Gb_Cpu::result_halt && cpu.r.pc < cpu.page_size ) + { + dprintf( "PC wrapped around\n" ); + } + else + { + dprintf( "Bad opcode $%.2x at $%.4x\n", + (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); + return 0; // error + } + } + } + + // end time frame + + next_play -= cpu_time; + if ( next_play < 0 ) // could go negative if routine is taking too long to return + next_play = 0; + + if ( apu.end_frame( cpu_time ) && added_stereo ) + *added_stereo = true; + + return cpu_time; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gbs_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gbs_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,95 @@ + +// Game Boy GBS-format game music file emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef GBS_EMU_H +#define GBS_EMU_H + +#include "Classic_Emu.h" +#include "Gb_Apu.h" +#include "Gb_Cpu.h" + +class Gbs_Emu : public Classic_Emu { +public: + // Sets internal gain, where 1.0 results in almost no clamping. Default gain + // roughly matches volume of other emulators. + Gbs_Emu( double gain = 1.3 ); + ~Gbs_Emu(); + + struct header_t { + char tag [3]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte stack_ptr [2]; + byte timer_modulo; + byte timer_mode; + char game [32]; + char author [32]; + char copyright [32]; + + enum { song = 0 }; // no song titles + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 112 ); + + // Load GBS, given its header and reader for remaining data + blargg_err_t load( const header_t&, Emu_Reader& ); + + const char** voice_names() const; + blargg_err_t start_track( int ); + + +// End of public interface +protected: + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + blip_time_t run( int, bool* ); +private: + // rom + const byte* rom_bank; + byte* rom; + void unload(); + int bank_count; + void set_bank( int ); + static void write_rom( Gbs_Emu*, gb_addr_t, int ); + static int read_rom( Gbs_Emu*, gb_addr_t ); + static int read_bank( Gbs_Emu*, gb_addr_t ); + + // state + gb_addr_t load_addr; + gb_addr_t init_addr; + gb_addr_t play_addr; + gb_addr_t stack_ptr; + int timer_modulo_init; + int timer_mode; + + // timer + gb_time_t cpu_time; + gb_time_t play_period; + gb_time_t next_play; + int double_speed; + + // hardware + Gb_Apu apu; + byte hi_page [0x100]; + void set_timer( int tma, int tmc ); + static int read_io( Gbs_Emu*, gb_addr_t ); + static void write_io( Gbs_Emu*, gb_addr_t, int ); + static int read_unmapped( Gbs_Emu*, gb_addr_t ); + static void write_unmapped( Gbs_Emu*, gb_addr_t, int ); + + // cpu and ram + Gb_Cpu cpu; + void cpu_jsr( gb_addr_t ); + gb_time_t clock() const; + byte ram [0x4000]; + static int read_ram( Gbs_Emu*, gb_addr_t ); + static void write_ram( Gbs_Emu*, gb_addr_t, int ); +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gym_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gym_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,441 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Gym_Emu.h" + +#include "ym2612.h" + +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +const long base_clock = 53700300; +const long clock_rate = base_clock / 15; + +Gym_Emu::Gym_Emu() +{ + data = NULL; + pos = NULL; + mem = NULL; + pairs_per_frame = 0; +} + +Gym_Emu::~Gym_Emu() +{ + unload(); +} + +void Gym_Emu::unload() +{ + delete [] mem; + mem = NULL; + data = NULL; + pos = NULL; + track_ended_ = false; +} + +blargg_err_t Gym_Emu::init( long sample_rate, double gain, double oversample_ ) +{ + require( oversample_ <= 4.0 ); + + blip_eq_t eq( -32, 8000, sample_rate ); + apu.treble_eq( eq ); + apu.volume( 0.27 * gain ); + dac_synth.treble_eq( eq ); + dac_synth.volume( 0.25 * gain ); + oversample = resampler.time_ratio( oversample_, 0.990, gain ); + + pairs_per_frame = sample_rate / 60; + oversamples_per_frame = int (pairs_per_frame * oversample) * 2 + 2; + clocks_per_sample = (double) clock_rate / sample_rate; + + BLARGG_RETURN_ERR( resampler.buffer_size( oversamples_per_frame + 256 ) ); + + BLARGG_RETURN_ERR( blip_buf.sample_rate( sample_rate, 1000 / 30 ) ); + + BLARGG_RETURN_ERR( fm.set_rate( sample_rate * oversample, base_clock / 7 ) ); + + blip_buf.clock_rate( clock_rate ); + + return blargg_success; +} + +void Gym_Emu::mute_voices( int mask ) +{ + fm.mute_voices( mask ); + dac_disabled = mask & 0x40; + apu.output( (mask & 0x80) ? NULL : &blip_buf ); +} + +const char** Gym_Emu::voice_names() const +{ + static const char* names [] = { + "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "SN76489" + }; + return names; +} + +static blargg_err_t check_header( const Gym_Emu::header_t& h, int* data_offset = NULL ) +{ + if ( memcmp( h.tag, "GYMX", 4 ) == 0 ) + { + if ( memcmp( h.packed, "\0\0\0\0", 4 ) != 0 ) + return "Packed GYM file not supported"; + + if ( data_offset ) + *data_offset = sizeof h; + } + else if ( h.tag [0] != 0 && h.tag [0] != 1 ) { + // not a headerless GYM + // to do: more thorough check, or just require a damn header + return "Not a GYM file"; + } + + return blargg_success; +} + +blargg_err_t Gym_Emu::load_( const void* file, long data_offset, long file_size ) +{ + require( pairs_per_frame ); + + data = (const byte*) file + data_offset; + data_end = (const byte*) file + file_size; + + loop_begin = NULL; + loop_offset = 0; + if ( data_offset ) + { + const header_t& h = *(header_t*) file; + loop_offset = + h.loop [3] * 0x1000000L + + h.loop [2] * 0x10000L + + h.loop [1] * 0x100L + + h.loop [0]; + } + + track_count_ = 1; + voice_count_ = 8; + mute_voices( 0 ); + + return blargg_success; +} + +blargg_err_t Gym_Emu::load( const void* file, long file_size ) +{ + unload(); + + if ( file_size < sizeof (header_t) ) + return "Not a GYM file"; + + int data_offset = 0; + BLARGG_RETURN_ERR( check_header( *(header_t*) file, &data_offset ) ); + + return load_( file, data_offset, file_size ); +} + +blargg_err_t Gym_Emu::load( const header_t& h, Emu_Reader& in ) +{ + unload(); + + int data_offset = 0; + BLARGG_RETURN_ERR( check_header( h, &data_offset ) ); + + long file_size = sizeof h + in.remain(); + mem = new byte [file_size]; + if ( !mem ) + return "Out of memory"; + memcpy( mem, &h, sizeof h ); + BLARGG_RETURN_ERR( in.read( mem + sizeof h, file_size - sizeof h ) ); + + return load_( mem, data_offset, file_size ); +} + +int Gym_Emu::track_length() const +{ + if ( loop_offset || loop_begin ) + return 0; + + long time = 0; // 1/60 sec frames + const byte* p = data; + while ( p < data_end ) + { + switch ( *p++ ) + { + case 0: + time++; + break; + + case 1: + case 2: + ++p; + case 3: + ++p; + break; + + default: + dprintf( "Bad command: %02X\n", (int) p [-1] ); + break; + } + } + + return (time + 30 + 59) / 60; +} + +blargg_err_t Gym_Emu::start_track( int ) +{ + require( data ); + + pos = &data [0]; + extra_pos = 0; + loop_remain = loop_offset; + + prev_dac_count = 0; + dac_enabled = false; + last_dac = -1; + + fm.reset(); + apu.reset(); + blip_buf.clear( false ); + resampler.clear(); + + track_ended_ = false; + + return blargg_success; +} + +void Gym_Emu::play_frame( sample_t* out ) +{ + parse_frame(); + + // run SMS APU and buffer + blip_time_t clock_count = (pairs_per_frame + 1 - blip_buf.samples_avail()) * + clocks_per_sample; + apu.end_frame( clock_count ); + blip_buf.end_frame( clock_count ); + assert( unsigned (blip_buf.samples_avail() - pairs_per_frame) <= 4 ); + + // run fm + const int sample_count = oversamples_per_frame - resampler.written(); + sample_t* buf = resampler.buffer(); + memset( buf, 0, sample_count * sizeof *buf ); + fm.run( buf, sample_count ); + resampler.write( sample_count ); + int count = resampler.read( sample_buf, pairs_per_frame * 2 ); + assert( count <= sample_buf_size ); + assert( unsigned (count - pairs_per_frame * 2) < 32 ); + + // mix outputs + mix_samples( out ); + blip_buf.remove_samples( pairs_per_frame ); +} + +blargg_err_t Gym_Emu::play( long count, sample_t* out ) +{ + require( pos ); + + const int samples_per_frame = pairs_per_frame * 2; + + // empty extra buffer + if ( extra_pos ) { + int n = samples_per_frame - extra_pos; + if ( n > count ) + n = count; + memcpy( out, sample_buf + extra_pos, n * sizeof *out ); + out += n; + count -= n; + extra_pos = (extra_pos + n) % samples_per_frame; + } + + // entire frames + while ( count >= samples_per_frame ) { + play_frame( out ); + out += samples_per_frame; + count -= samples_per_frame; + } + + // extra + if ( count ) { + play_frame( sample_buf ); + extra_pos = count; + memcpy( out, sample_buf, count * sizeof *out ); + out += count; + } + + return blargg_success; +} + +blargg_err_t Gym_Emu::skip( long count ) +{ + // to do: figure out why total muting generated access violation on MorphOS + const int buf_size = 1024; + sample_t buf [buf_size]; + + while ( count ) + { + int n = buf_size; + if ( n > count ) + n = count; + count -= n; + BLARGG_RETURN_ERR( play( n, buf ) ); + } + + return blargg_success; +} + +void Gym_Emu::run_dac( int dac_count ) +{ + if ( !dac_disabled ) + { + // Guess beginning and end of sample and adjust rate and buffer position accordingly. + + // count dac samples in next frame + int next_dac_count = 0; + const byte* p = this->pos; + int cmd; + while ( (cmd = *p++) != 0 ) { + int data = *p++; + if ( cmd <= 2 ) + ++p; + if ( cmd == 1 && data == 0x2A ) + next_dac_count++; + } + + // adjust + int rate_count = dac_count; + int start = 0; + if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count ) { + rate_count = next_dac_count; + start = next_dac_count - dac_count; + } + else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count ) { + rate_count = prev_dac_count; + } + + // Evenly space samples within buffer section being used + Blip_Buffer::resampled_time_t period = + blip_buf.resampled_duration( clock_rate / 60 ) / rate_count; + + Blip_Buffer::resampled_time_t time = blip_buf.resampled_time( 0 ) + + period * start + (period >> 1); + + int last_dac = this->last_dac; + if ( last_dac < 0 ) + last_dac = dac_buf [0]; + + for ( int i = 0; i < dac_count; i++ ) + { + int diff = dac_buf [i] - last_dac; + last_dac += diff; + dac_synth.offset_resampled( time, diff, &blip_buf ); + time += period; + } + this->last_dac = last_dac; + } + + int const step = 6 * oversample; + int remain = pairs_per_frame * oversample; + while ( remain ) { + int n = step; + if ( n > remain ) + n = remain; + remain -= n; + fm.run_timer( n ); + } +} + +void Gym_Emu::parse_frame() +{ + if ( track_ended_ ) + return; + + int dac_count = 0; + + const byte* pos = this->pos; + if ( loop_remain && !--loop_remain ) + loop_begin = pos; // find loop on first time through sequence + int cmd; + while ( (cmd = *pos++) != 0 ) + { + int data = *pos++; + if ( cmd == 1 ) { + int data2 = *pos++; + if ( data == 0x2A ) { + if ( dac_count < sizeof dac_buf ) { + dac_buf [dac_count] = data2; + dac_count += dac_enabled; + } + } + else { + if ( data == 0x2B ) + dac_enabled = (data2 & 0x80) != 0; + + fm.write( 0, data ); + fm.write( 1, data2 ); + } + } + else if ( cmd == 2 ) { + fm.write( 2, data ); + fm.write( 3, *pos++ ); + } + else if ( cmd == 3 ) { + apu.write_data( 0, data ); + } + else { + dprintf( "Bad command: %02X\n", (int) cmd ); + --pos; // put data back + } + } + // loop + if ( pos >= data_end ) { + if ( loop_begin ) + pos = loop_begin; + else + track_ended_ = true; + } + this->pos = pos; + + // dac + if ( dac_count ) + run_dac( dac_count ); + prev_dac_count = dac_count; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Gym_Emu::mix_samples( sample_t* out ) +{ + // Mix one frame of Blip_Buffer (SMS APU and PCM) and resampled YM audio + Blip_Reader sn; + int bass = sn.begin( blip_buf ); + const sample_t* ym = sample_buf; + + for ( int n = pairs_per_frame; n--; ) + { + int s = sn.read(); + long l = ym [0] * 2 + s; + sn.next( bass ); + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + long r = ym [1] * 2 + s; + ym += 2; + out [0] = l; + out [1] = r; + out += 2; + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + sn.end( blip_buf ); +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Gym_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Gym_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,101 @@ + +// Sega Genesis GYM music file emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. + +#ifndef GYM_EMU_H +#define GYM_EMU_H + +#include "Fir_Resampler.h" +#include "Blip_Buffer.h" +#include "Music_Emu.h" +#include "Sms_Apu.h" +#include "ym2612.h" + +class Gym_Emu : public Music_Emu { +public: + Gym_Emu(); + ~Gym_Emu(); + + // Initialize emulator with given sample rate, gain, and oversample. A gain of 1.0 + // results in almost no clamping. Default gain roughly matches volume of other emulators. + // The FM chip is synthesized at an increased rate governed by the oversample factor, + // where 1.0 results in no oversampling and > 1.0 results in oversampling. + blargg_err_t init( long sample_rate, double gain = 1.5, double oversample = 5 / 3.0 ); + + struct header_t { + char tag [4]; + char song [32]; + char game [32]; + char copyright [32]; + char emulator [32]; + char dumper [32]; + char comment [256]; + byte loop [4]; + byte packed [4]; + + enum { track_count = 1 }; // one track per file + enum { author = 0 }; // no author field + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 428 ); + + // Load GYM, given its header and reader for remaining data + blargg_err_t load( const header_t&, Emu_Reader& ); + + // Load GYM, given pointer to complete file data. Keeps reference + // to data, but doesn't free it. + blargg_err_t load( const void*, long size ); + + // Length of track, in seconds (0 if looped) + int track_length() const; + + void mute_voices( int ); + blargg_err_t start_track( int ); + blargg_err_t play( long count, sample_t* ); + const char** voice_names() const; + blargg_err_t skip( long count ); + +// End of public interface +private: + // sequence data begin, loop begin, current position, end + const byte* data; + const byte* loop_begin; + const byte* pos; + const byte* data_end; + long loop_offset; + long loop_remain; // frames remaining until loop beginning has been located + byte* mem; + blargg_err_t load_( const void* file, long data_offset, long file_size ); + + // frames + double oversample; + double clocks_per_sample; + int pairs_per_frame; + int oversamples_per_frame; + void parse_frame(); + void play_frame( sample_t* ); + void mix_samples( sample_t* ); + + // dac (pcm) + int last_dac; + int prev_dac_count; + bool dac_enabled; + bool dac_disabled; + void run_dac( int ); + + // sound + int extra_pos; // extra samples remaining from last read + Blip_Buffer blip_buf; + YM2612_Emu fm; + Blip_Synth dac_synth; + Sms_Apu apu; + Fir_Resampler resampler; + byte dac_buf [1024]; + enum { sample_buf_size = 4096 }; + sample_t sample_buf [sample_buf_size]; + + void unload(); +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Makefile.am Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,37 @@ +noinst_LTLIBRARIES = libconsole.la + +libdir = $(plugin_dir)/$(INPUT_PLUGIN_DIR) + +libconsole_la_SOURCES = \ + Blip_Buffer.cpp \ + Classic_Emu.cpp \ + Effects_Buffer.cpp \ + Fir_Resampler.cpp \ + Gb_Abu.cpp \ + Gb_Cpu.cpp \ + Gb_Oscs.cpp \ + Gbs_Emu.cpp \ + Gym_Emu.cpp \ + Multi_Buffer.cpp \ + Music_Emu.cpp \ + Nes_Apu.cpp \ + Nes_Cpu.cpp \ + Nes_Namco.cpp \ + Nes_Oscs.cpp \ + Nes_Vrc6.cpp \ + Nsf_Emu.cpp \ + Panning_Buffer.cpp \ + Sms_Apu.cpp \ + Snes_Spc.cpp \ + Spc_Cpu.cpp \ + Spc_Dsp.cpp \ + Spc_Emu.cpp \ + Vgm_Emu.cpp \ + abstract_file.cpp \ + ym2612.cpp \ + Audacious_Driver.cpp + +libconsole_la_LDFLAGS = $(PLUGIN_LDFLAGS) +libconsole_la_LIBADD = $(GTK_LIBS) $(top_builddir)/libaudacious/libaudacious.la +INCLUDES = $(GTK_CFLAGS) $(ARCH_DEFINES) -I$(top_srcdir)/intl -I$(top_srcdir) -Iboost/ + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Multi_Buffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Multi_Buffer.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,205 @@ + +// Blip_Buffer 0.3.3. http://www.slack.net/~ant/libs/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Multi_Buffer::Multi_Buffer() +{ + length_ = 0; + sample_rate_ = 0; +} + +blargg_err_t Multi_Buffer::set_channel_count( int ) +{ + return blargg_success; +} + +Mono_Buffer::Mono_Buffer() +{ +} + +Mono_Buffer::~Mono_Buffer() +{ +} + +blargg_err_t Mono_Buffer::sample_rate( long rate, int msec ) +{ + BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) ); + length_ = buf.length(); + sample_rate_ = buf.sample_rate(); + return blargg_success; +} + +Mono_Buffer::channel_t Mono_Buffer::channel( int index ) +{ + channel_t ch; + ch.center = &buf; + ch.left = &buf; + ch.right = &buf; + return ch; +} + +void Mono_Buffer::end_frame( blip_time_t t, bool ) +{ + buf.end_frame( t ); +} + +Stereo_Buffer::Stereo_Buffer() +{ + chan.center = &bufs [0]; + chan.left = &bufs [1]; + chan.right = &bufs [2]; +} + +Stereo_Buffer::~Stereo_Buffer() +{ +} + +blargg_err_t Stereo_Buffer::sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + BLARGG_RETURN_ERR( bufs [i].sample_rate( rate, msec ) ); + length_ = msec; + sample_rate_ = rate; + return blargg_success; +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + stereo_added = false; + was_stereo = false; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + stereo_added |= stereo; +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +{ + require( !(count & 1) ); // count must be even + count = (unsigned) count / 2; + + long avail = bufs [0].samples_avail(); + if ( count > avail ) + count = avail; + if ( count ) + { + if ( stereo_added || was_stereo ) + { + mix_stereo( out, count ); + + bufs [0].remove_samples( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + else + { + mix_mono( out, count ); + + bufs [0].remove_samples( count ); + + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + + // to do: this might miss opportunities for optimization + if ( !bufs [0].samples_avail() ) { + was_stereo = stereo_added; + stereo_added = false; + } + } + + return count * 2; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader left; + Blip_Reader right; + Blip_Reader center; + + left.begin( bufs [1] ); + right.begin( bufs [2] ); + int bass = center.begin( bufs [0] ); + + while ( count-- ) + { + int c = center.read(); + long l = c + left.read(); + long r = c + right.read(); + center.next( bass ); + out [0] = l; + out [1] = r; + out += 2; + + if ( (BOOST::int16_t) l != l ) + out [-2] = 0x7FFF - (l >> 24); + + left.next( bass ); + right.next( bass ); + + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + center.end( bufs [0] ); + right.end( bufs [2] ); + left.end( bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader in; + int bass = in.begin( bufs [0] ); + + while ( count-- ) + { + long s = in.read(); + in.next( bass ); + out [0] = s; + out [1] = s; + out += 2; + + if ( (BOOST::int16_t) s != s ) { + s = 0x7FFF - (s >> 24); + out [-2] = s; + out [-1] = s; + } + } + + in.end( bufs [0] ); +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Multi_Buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Multi_Buffer.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,157 @@ + +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "Blip_Buffer.h" + +// Multi_Buffer is an interface to one or more Blip_Buffers mapped to one or +// more channels consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer(); + virtual ~Multi_Buffer() { } + + // Set the number of channels available + virtual blargg_err_t set_channel_count( int ); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + virtual channel_t channel( int index ) = 0; + + // See Blip_Buffer.h + //virtual blargg_err_t sample_rate( long rate, int msec = 0 ); + virtual void clock_rate( long ) = 0; + virtual void bass_freq( int ) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' + // if nothing was added to the left and right buffers of any channel for + // this time frame. + virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) = 0; + +protected: + // Derived classes must set these to appropriate values, which are returned + // by the corresponding public function. + long sample_rate_; + int length_; +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); +}; + +// Mono_Buffer uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; +public: + Mono_Buffer(); + ~Mono_Buffer(); + + // Buffer used for all channels + Blip_Buffer* center(); + + // See Multi_Buffer + blargg_err_t sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long read_samples( blip_sample_t*, long ); +}; + +// Stereo_Buffer uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + + // Buffers used for all channels + Blip_Buffer* center(); + Blip_Buffer* left(); + Blip_Buffer* right(); + + // See Multi_Buffer + blargg_err_t sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int index ); + void end_frame( blip_time_t, bool added_stereo = true ); + long read_samples( blip_sample_t*, long ); + +private: + enum { buf_count = 3 }; + Blip_Buffer bufs [buf_count]; + channel_t chan; + bool stereo_added; + bool was_stereo; + + void mix_stereo( blip_sample_t*, long ); + void mix_mono( blip_sample_t*, long ); +}; + + +// End of public interface + +inline Blip_Buffer* Stereo_Buffer::left() { + return &bufs [1]; +} + +inline Blip_Buffer* Stereo_Buffer::center() { + return &bufs [0]; +} + +inline Blip_Buffer* Stereo_Buffer::right() { + return &bufs [2]; +} + +inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int index ) { + return chan; +} + +inline long Multi_Buffer::sample_rate() const { + return sample_rate_; +} + +inline int Multi_Buffer::length() const { + return length_; +} + +inline Blip_Buffer* Mono_Buffer::center() { + return &buf; +} + +inline void Mono_Buffer::clock_rate( long rate ) { + buf.clock_rate( rate ); +} + +inline void Mono_Buffer::clear() { + buf.clear(); +} + +inline void Mono_Buffer::bass_freq( int freq ) { + buf.bass_freq( freq ); +} + +inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { + return buf.read_samples( p, s ); +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Music_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Music_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,77 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Music_Emu.h" + +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Music_Emu::Music_Emu() +{ + mute_mask_ = 0; + track_count_ = 0; + voice_count_ = 0; + track_ended_ = false; +} + +Music_Emu::~Music_Emu() +{ +} + +void Music_Emu::mute_voices( int ) +{ + // empty +} + +blargg_err_t Music_Emu::skip( long count ) +{ + const int buf_size = 1024; + sample_t buf [buf_size]; + + const long threshold = 30000; + if ( count > threshold ) + { + int saved_mute = mute_mask_; + mute_voices( ~0 ); + + while ( count > threshold / 2 ) { + BLARGG_RETURN_ERR( play( buf_size, buf ) ); + count -= buf_size; + } + + mute_voices( saved_mute ); + } + + while ( count ) + { + int n = buf_size; + if ( n > count ) + n = count; + count -= n; + BLARGG_RETURN_ERR( play( n, buf ) ); + } + + return blargg_success; +} + +const char** Music_Emu::voice_names() const +{ + static const char* names [] = { + "Voice 1", "Voice 2", "Voice 3", "Voice 4", + "Voice 5", "Voice 6", "Voice 7", "Voice 8" + }; + return names; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Music_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Music_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,66 @@ + +// Game music emulator interface base class + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef MUSIC_EMU_H +#define MUSIC_EMU_H + +#include "blargg_common.h" + +#include "abstract_file.h" +typedef Data_Reader Emu_Reader; // File reader base class +typedef Std_File_Reader Emu_Std_Reader; // Read from standard file +typedef Mem_File_Reader Emu_Mem_Reader; // Read from block of memory + +class Music_Emu { +public: + Music_Emu(); + virtual ~Music_Emu(); + + // Number of voices used by currently loaded file + int voice_count() const; + + // Names of voices + virtual const char** voice_names() const; + + // Number of tracks. Zero if file hasn't been loaded yet. + int track_count() const; + + // Start a track, where 0 is the first track. Might un-mute any muted voices. + virtual blargg_err_t start_track( int ) = 0; + + // Mute voice n if bit n (1 << n) of mask is set + virtual void mute_voices( int mask ); + + // Generate 'count' samples info 'buf' + typedef short sample_t; + virtual blargg_err_t play( long count, sample_t* buf ) = 0; + + // Skip 'count' samples + virtual blargg_err_t skip( long count ); + + // True if a track was started and has since ended. Currently only dumped + // format tracks (VGM, GYM) without loop points have an ending. + bool track_ended() const; + + +// End of public interface +protected: + typedef BOOST::uint8_t byte; // used often + int track_count_; + int voice_count_; + int mute_mask_; + bool track_ended_; +private: + // noncopyable + Music_Emu( const Music_Emu& ); + Music_Emu& operator = ( const Music_Emu& ); +}; + +inline int Music_Emu::voice_count() const { return voice_count_; } +inline int Music_Emu::track_count() const { return track_count_; } +inline bool Music_Emu::track_ended() const { return track_ended_; } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Apu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,329 @@ + +// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Apu::Nes_Apu() +{ + dmc.apu = this; + dmc.rom_reader = NULL; + square1.synth = &square_synth; + square2.synth = &square_synth; + irq_notifier_ = NULL; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = ▵ + oscs [3] = &noise; + oscs [4] = &dmc; + + output( NULL ); + volume( 1.0 ); + reset( false ); +} + +Nes_Apu::~Nes_Apu() +{ +} + +void Nes_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + triangle.synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); + dmc.synth.treble_eq( eq ); +} + +void Nes_Apu::volume( double v, bool nonlinear ) +{ + dmc.nonlinear = nonlinear; + if ( nonlinear ) + { + square_synth.volume( 0.25751258 * 0.25 * v ); + + const double tnd = 1.0 / 202 * 0.48; + triangle.synth.volume_unit( 3 * tnd ); + noise.synth.volume_unit( 2 * tnd ); + dmc.synth.volume_unit( tnd ); + } + else + { + square_synth.volume( 0.1128 * v ); + triangle.synth.volume( 0.12765 * v ); + noise.synth.volume( 0.0741 * v ); + dmc.synth.volume( 0.42545 * v ); + } +} + +void Nes_Apu::output( Blip_Buffer* buffer ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buffer ); +} + +void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) +{ + // to do: time pal frame periods exactly + frame_period = pal_mode ? 8314 : 7458; + dmc.pal_mode = pal_mode; + + square1.reset(); + square2.reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); + + last_time = 0; + osc_enables = 0; + irq_flag = false; + earliest_irq_ = no_irq; + frame_delay = 1; + write_register( 0, 0x4017, 0x00 ); + write_register( 0, 0x4015, 0x00 ); + + for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ ) + write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); + + dmc.dac = initial_dmc_dac; + if ( !dmc.nonlinear ) + dmc.last_amp = initial_dmc_dac; // prevent output transition +} + +void Nes_Apu::irq_changed() +{ + nes_time_t new_irq = dmc.next_irq; + if ( dmc.irq_flag | irq_flag ) { + new_irq = 0; + } + else if ( new_irq > next_irq ) { + new_irq = next_irq; + } + + if ( new_irq != earliest_irq_ ) { + earliest_irq_ = new_irq; + if ( irq_notifier_ ) + irq_notifier_( irq_data ); + } +} + +// frames + +void Nes_Apu::run_until( nes_time_t end_time ) +{ + require( end_time >= last_time ); + + if ( end_time == last_time ) + return; + + while ( true ) + { + // earlier of next frame time or end time + nes_time_t time = last_time + frame_delay; + if ( time > end_time ) + time = end_time; + frame_delay -= time - last_time; + + // run oscs to present + square1.run( last_time, time ); + square2.run( last_time, time ); + triangle.run( last_time, time ); + noise.run( last_time, time ); + dmc.run( last_time, time ); + last_time = time; + + if ( time == end_time ) + break; // no more frames to run + + // take frame-specific actions + frame_delay = frame_period; + switch ( frame++ ) + { + case 0: + // set interrupt in mode 0 + if ( !(frame_mode & 0xc0) ) { + irq_flag = true; + next_irq = time + frame_period * 4 + 1; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + square1.clock_length( 0x20 ); + square2.clock_length( 0x20 ); + noise.clock_length( 0x20 ); + triangle.clock_length( 0x80 ); // different bit for halt flag on triangle + + square1.clock_sweep( -1 ); + square2.clock_sweep( 0 ); + break; + + case 1: + // frame 1 is slightly shorter + frame_delay -= 2; + break; + + case 3: + frame = 0; + + // frame 3 is almost twice as long in mode 1 + if ( frame_mode & 0x80 ) + frame_delay += frame_period - 6; + break; + } + + // clock envelopes and linear counter every frame + triangle.clock_linear_counter(); + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } +} + +void Nes_Apu::end_frame( nes_time_t end_time ) +{ + run_until( end_time ); + + // make times relative to new frame + last_time = 0; + if ( next_irq != no_irq ) { + next_irq -= end_time; + assert( next_irq >= 0 ); + } + if ( dmc.next_irq != no_irq ) { + dmc.next_irq -= end_time; + assert( dmc.next_irq >= 0 ); + } + if ( earliest_irq_ != no_irq ) { + earliest_irq_ -= end_time; + if ( earliest_irq_ < 0 ) + earliest_irq_ = 0; + } +} + +// registers + +static const unsigned char length_table [0x20] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) +{ + require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) + require( (unsigned) data <= 0xff ); + + // Ignore addresses outside range + if ( addr < start_addr || end_addr < addr ) + return; + + run_until( time ); + + if ( addr < 0x4014 ) + { + // Write to channel + int osc_index = (addr - start_addr) >> 2; + Nes_Osc* osc = oscs [osc_index]; + + int reg = addr & 3; + osc->regs [reg] = data; + osc->reg_written [reg] = true; + + if ( osc_index == 4 ) + { + // handle DMC specially + dmc.write_register( reg, data ); + } + else if ( reg == 3 ) + { + // load length counter + if ( (osc_enables >> osc_index) & 1 ) + osc->length_counter = length_table [(data >> 3) & 0x1f]; + + // reset square phase + if ( osc_index < 2 ) + ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; + } + } + else if ( addr == 0x4015 ) + { + // Channel enables + for ( int i = osc_count; i--; ) + if ( !((data >> i) & 1) ) + oscs [i]->length_counter = 0; + + bool recalc_irq = dmc.irq_flag; + dmc.irq_flag = false; + + int old_enables = osc_enables; + osc_enables = data; + if ( !(data & 0x10) ) { + dmc.next_irq = no_irq; + recalc_irq = true; + } + else if ( !(old_enables & 0x10) ) { + dmc.start(); // dmc just enabled + } + + if ( recalc_irq ) + irq_changed(); + } + else if ( addr == 0x4017 ) + { + // Frame mode + frame_mode = data; + + bool irq_enabled = !(data & 0x40); + irq_flag &= irq_enabled; + next_irq = no_irq; + + // mode 1 + frame_delay = (frame_delay & 1); + frame = 0; + + if ( !(data & 0x80) ) + { + // mode 0 + frame = 1; + frame_delay += frame_period; + if ( irq_enabled ) + next_irq = time + frame_delay + frame_period * 3; + } + + irq_changed(); + } +} + +int Nes_Apu::read_status( nes_time_t time ) +{ + run_until( time - 1 ); + + int result = (dmc.irq_flag << 7) | (irq_flag << 6); + + for ( int i = 0; i < osc_count; i++ ) + if ( oscs [i]->length_counter ) + result |= 1 << i; + + run_until( time ); + + if ( irq_flag ) { + irq_flag = false; + irq_changed(); + } + + return result; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Apu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,155 @@ + +// NES 2A03 APU sound chip emulator + +// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_APU_H +#define NES_APU_H + +typedef long nes_time_t; // CPU clock cycle count +typedef unsigned nes_addr_t; // 16-bit memory address + +#include "Nes_Oscs.h" + +class Tagged_Data; + +class Nes_Apu { +public: + Nes_Apu(); + ~Nes_Apu(); + +// Initialization + + // Reset internal frame counter, registers, and all oscillators. + // Use PAL timing if pal_timing is true, otherwise use NTSC timing. + // Set the DMC oscillator's initial DAC value to initial_dmc_dac without + // any audible click. + void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); + + // Set memory reader callback used by DMC oscillator to fetch samples. + // When callback is invoked, 'user_data' is passed unchanged as the + // first parameter. + void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL ); + + // Set IRQ time callback that is invoked when the time of earliest IRQ + // may have changed, or NULL to disable. When callback is invoked, + // 'user_data' is passed unchanged as the first parameter. + void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); + + // Reflect complete state between tagged data container. + void reflect_state( Tagged_Data& ); + +// Sound output + + // Set overall volume (default is 1.0). Set nonlinear to true only when + // using special Nes_Nonlinearizer on output. + void volume( double, bool nonlinear = false ); + + // Set treble equalization (see notes.txt). + void treble_eq( const blip_eq_t& ); + + // Set sound output of all oscillators to buffer. If buffer is NULL, no + // sound is generated and emulation accuracy is reduced. + void output( Blip_Buffer* ); + + // Set sound output of specific oscillator to buffer. If buffer is NULL, + // the specified oscillator is muted and emulation accuracy is reduced. + // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, + // 2) Triangle, 3) Noise, 4) DMC. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* buffer ); + +// Emulation + + // All time values are the number of CPU clock cycles relative to the + // beginning of the current time frame. + + // Emulate memory write at specified time. Address must be in the range + // start_addr to end_addr. + enum { start_addr = 0x4000 }; + enum { end_addr = 0x4017 }; + void write_register( nes_time_t, nes_addr_t, int data ); + + // Emulate memory read of the status register at specified time. + enum { status_addr = 0x4015 }; + int read_status( nes_time_t ); + + // Get time that APU-generated IRQ will occur if no further register reads + // or writes occur. If IRQ is already pending, returns irq_waiting. If no + // IRQ will occur, returns no_irq. + enum { no_irq = LONG_MAX / 2 + 1 }; + enum { irq_waiting = 0 }; + nes_time_t earliest_irq() const; + + // Run APU until specified time, so that any DMC memory reads can be + // accounted for (i.e. inserting CPU wait states). + void run_until( nes_time_t ); + + // Count number of DMC reads that would occur if 'run_until( t )' were executed. + // If last_read is not NULL, set *last_read to the earliest time that + // 'count_dmc_reads( time )' would result in the same result. + int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; + + // Run all oscillators up to specified time, end current time frame, then + // start a new time frame at time 0. Time frames have no effect on emulation + // and each can be whatever length is convenient. + void end_frame( nes_time_t ); + +// End of public interface. + +private: + // noncopyable + Nes_Apu( const Nes_Apu& ); + Nes_Apu& operator = ( const Nes_Apu& ); + + Nes_Osc* oscs [osc_count]; + Nes_Square square1; + Nes_Square square2; + Nes_Noise noise; + Nes_Triangle triangle; + Nes_Dmc dmc; + + nes_time_t last_time; // has been run until this time in current frame + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int osc_enables; + int frame_mode; + bool irq_flag; + void (*irq_notifier_)( void* user_data ); + void* irq_data; + Nes_Square::Synth square_synth; // shared by squares + + void irq_changed(); + void state_restored(); + + friend struct Nes_Dmc; +}; + + inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) { + assert(( "Nes_Apu::osc_output(): Index out of range", 0 <= osc && osc < osc_count )); + oscs [osc]->output = buf; + } + + inline nes_time_t Nes_Apu::earliest_irq() const { + return earliest_irq_; + } + + inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) { + dmc.rom_reader_data = user_data; + dmc.rom_reader = func; + } + + inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) { + irq_notifier_ = func; + irq_data = user_data; + } + + inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const { + return dmc.count_reads( time, last_read ); + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Cpu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Cpu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,915 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Nes_Cpu.h" + +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Common instructions: +// 7.40% D0 BNE +// 6.05% F0 BEQ +// 5.46% BD LDA abs,X +// 4.44% C8 INY +// 4.32% 85 STA zp +// 4.29% C9 CMP imm +// 3.74% 20 JSR +// 3.66% 60 RTS +// 3.23% 8D STA abs +// 3.02% AD LDA abs + +#ifndef NES_CPU_FIXED_CYCLES + #define NES_CPU_FIXED_CYCLES 0 +#endif + +static void write_unmapped( Nsf_Emu*, nes_addr_t, int ) +{ + check( false ); +} + +static int read_unmapped( Nsf_Emu*, nes_addr_t ) +{ + check( false ); + return 0; +} + +Nes_Cpu::Nes_Cpu() +{ + callback_data = NULL; + data_writer [page_count] = write_unmapped; + data_reader [page_count] = read_unmapped; + reset(); +} + +void Nes_Cpu::reset( const void* unmapped_code_page ) +{ + r.status = 0; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + cycle_count = 0; + base_time = 0; + #if NES_CPU_IRQ_SUPPORT + cycle_limit = 0; + #endif + + for ( int i = 0; i < page_count + 1; i++ ) + code_map [i] = (uint8_t*) unmapped_code_page; + + map_memory( 0, 0x10000, read_unmapped, write_unmapped ); +} + +void Nes_Cpu::map_code( nes_addr_t start, unsigned long size, const void* data ) +{ + // start end end must fall on page bounadries + require( start % page_size == 0 && size % page_size == 0 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + code_map [first_page + i] = (uint8_t*) data + i * page_size; +} + +void Nes_Cpu::map_memory( nes_addr_t start, unsigned long size, reader_t read, writer_t write ) +{ + // start end end must fall on page bounadries + require( start % page_size == 0 && size % page_size == 0 ); + + if ( !read ) + read = read_unmapped; + if ( !write ) + write = write_unmapped; + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) { + data_reader [first_page + i] = read; + data_writer [first_page + i] = write; + } +} + +// Note: 'addr' is evaulated more than once in some of the macros, so it +// must not contain side-effects. + +#define LOW_MEM( a ) (low_mem [int (a)]) + +#define READ( addr ) (data_reader [(addr) >> page_bits]( callback_data, addr )) +#define WRITE( addr, data ) (data_writer [(addr) >> page_bits]( callback_data, addr, data )) + +#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [(addr) & (page_size - 1)]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +#define PUSH( v ) (*--sp = (v)) +#define POP() (*sp++) + +int Nes_Cpu::read( nes_addr_t addr ) { + return READ( addr ); +} + +void Nes_Cpu::write( nes_addr_t addr, int value ) { + WRITE( addr, value ); +} + +#ifndef NES_CPU_GLUE_ONLY + +static const unsigned char cycle_table [256] = { + 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6, + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6, + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6, + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6, + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, + 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5, + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, + 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4, + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, + 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6, + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 +}; + +#include BLARGG_ENABLE_OPTIMIZER + +// on machines with relatively few registers: +// use local temporaries (if no function calls) over all other variables +// these are most likely to be in registers, so use them over others if possible: +// pc +// nz (so calculate with nz first, then assign to other variable if necessary) +// data +// this +Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end ) +{ + #if NES_CPU_IRQ_SUPPORT + end_time( end ); + #else + cycle_count -= (end - base_time); + base_time = end; + #endif + + result_t result = result_cycles; + +#if BLARGG_CPU_POWERPC + // cache commonly-used values in registers + long cycle_count = this->cycle_count; + writer_t* const data_writer = this->data_writer; + reader_t* const data_reader = this->data_reader; + uint8_t* const low_mem = this->low_mem; +#endif + + // stack pointer is kept one greater than usual 6502 stack pointer + #define SET_SP( v ) (sp = low_mem + 0x101 + (v)) + #define GET_SP() (sp - 0x101 - low_mem) + uint8_t* sp; + SET_SP( r.sp ); + + // registers + unsigned pc = r.pc; + int a = r.a; + int x = r.x; + int y = r.y; + + // status flags + + #define IS_NEG (int ((nz + 0x800) | (nz << (CHAR_BIT * sizeof (int) - 8))) < 0) + + const int st_n = 0x80; + const int st_v = 0x40; + const int st_r = 0x20; + const int st_b = 0x10; + const int st_d = 0x08; + const int st_i = 0x04; + const int st_z = 0x02; + const int st_c = 0x01; + + #define CALC_STATUS( out ) do { \ + out = status & ~(st_n | st_z | st_c); \ + out |= (c >> 8) & st_c; \ + if ( IS_NEG ) out |= st_n; \ + if ( (nz & 0xFF) == 0 ) out |= st_z; \ + } while ( false ) + + #define SET_STATUS( in ) do { \ + status = in & ~(st_n | st_z | st_c | st_b | st_r); \ + c = in << 8; \ + nz = in << 4; \ + nz &= 0x820; \ + nz ^= ~0xDF; \ + } while ( false ) + + uint8_t status; + int c; // store C as 'c' & 0x100. + int nz; // store Z as 'nz' & 0xFF == 0. see above for encoding of N. + { + int temp = r.status; + SET_STATUS( temp ); + } + + goto loop; + + unsigned data; + +branch_taken: { + unsigned old_pc = pc; + pc += (BOOST::int8_t) data; + if ( !NES_CPU_FIXED_CYCLES ) + cycle_count += 1 + (((old_pc ^ (pc + 1)) >> 8) & 1); +} +inc_pc_loop: + pc++; +loop: + + check( (unsigned) pc < 0x10000 ); + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + // Read opcode and first operand. Optimize if processor's byte order is known + // and non-portable constructs are allowed. +#if BLARGG_NONPORTABLE && BLARGG_BIG_ENDIAN + data = *(BOOST::uint16_t*) &READ_PROG( pc ); + pc++; + unsigned opcode = data >> 8; + data = (uint8_t) data; + +#elif BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN + data = *(BOOST::uint16_t*) &READ_PROG( pc ); + pc++; + unsigned opcode = (uint8_t) data; + data >>= 8; + +#else + unsigned opcode = READ_PROG( pc ); + pc++; + data = READ_PROG( pc ); + +#endif + + if ( cycle_count >= cycle_limit ) + goto stop; + + cycle_count += NES_CPU_FIXED_CYCLES ? NES_CPU_FIXED_CYCLES : cycle_table [opcode]; + + #if BLARGG_CPU_POWERPC + this->cycle_count = cycle_count; + #endif + + switch ( opcode ) + { +#define ADD_PAGE (pc++, data += 0x100 * READ_PROG( pc )); +#define GET_ADDR() READ_PROG16( pc ) + +#if NES_CPU_FIXED_CYCLES + #define HANDLE_PAGE_CROSSING( lsb ) +#else + #define HANDLE_PAGE_CROSSING( lsb ) cycle_count += (lsb) >> 8; +#endif + +#define INC_DEC_XY( reg, n ) \ + reg = uint8_t (nz = reg + n); \ + goto loop; + +#define IND_Y { \ + int temp = LOW_MEM( data ) + y; \ + data = temp + 0x100 * LOW_MEM( uint8_t (data + 1) ); \ + HANDLE_PAGE_CROSSING( temp ); \ + } + +#define IND_X { \ + int temp = data + x; \ + data = LOW_MEM( uint8_t (temp) ) + 0x100 * LOW_MEM( uint8_t (temp + 1) ); \ + } + +#define ARITH_ADDR_MODES( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X \ + goto ptr##op; \ +case op + 0x0C: /* (ind),y */ \ + IND_Y \ + goto ptr##op; \ +case op + 0x10: /* zp,X */ \ + data = uint8_t (data + x); \ +case op + 0x00: /* zp */ \ + data = LOW_MEM( data ); \ + goto imm##op; \ +case op + 0x14: /* abs,Y */ \ + data += y; \ + goto ind##op; \ +case op + 0x18: /* abs,X */ \ + data += x; \ +ind##op: \ + HANDLE_PAGE_CROSSING( data ); \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +ptr##op: \ + data = READ( data ); \ +case op + 0x04: /* imm */ \ +imm##op: \ + +// Often-Used + + case 0xD0: // BNE + if ( (uint8_t) nz ) + goto branch_taken; + goto inc_pc_loop; + + case 0xF0: // BEQ + if ( !(uint8_t) nz ) + goto branch_taken; + goto inc_pc_loop; + + case 0xBD: // LDA abs,X + data += x; + lda_ind_common: + HANDLE_PAGE_CROSSING( data ); + case 0xAD: // LDA abs + ADD_PAGE + lda_ptr: + nz = a = READ( data ); + goto inc_pc_loop; + + case 0xC8: INC_DEC_XY( y, 1 ) // INY + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + LOW_MEM( data ) = a; + goto inc_pc_loop; + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + compare_common: + c = ~nz; + goto inc_pc_loop; + + case 0x4C: // JMP abs + pc++; + goto jmp_common; + + case 0x20: // JSR + pc++; + PUSH( pc >> 8 ); + PUSH( pc ); + jmp_common: + pc = data + 0x100 * READ_PROG( pc ); + goto loop; + + case 0x60: // RTS + // often-used instruction + pc = POP(); + pc += 0x100 * POP(); + goto inc_pc_loop; + + case 0x9D: // STA abs,X + data += x; + sta_ind_common: + HANDLE_PAGE_CROSSING( data ); + case 0x8D: // STA abs + ADD_PAGE + sta_ptr: + WRITE( data, a ); + goto inc_pc_loop; + + case 0xB5: // LDA zp,x + data = uint8_t (data + x); + case 0xA5: // LDA zp + data = LOW_MEM( data ); + case 0xA9: // LDA #imm + a = nz = data; + goto inc_pc_loop; + + case 0xA8: // TAY + y = a; + case 0x98: // TYA + a = nz = y; + goto loop; + +// Branch + +#define BRANCH( cond ) \ + if ( cond ) \ + goto branch_taken; \ + goto inc_pc_loop; + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + case 0x30: // BMI + BRANCH( IS_NEG ) + +// Load/store + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + LOW_MEM( data ) = y; + goto inc_pc_loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + LOW_MEM( data ) = x; + goto inc_pc_loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = LOW_MEM( data ); + case 0xA2: // LDX imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = LOW_MEM( data ); + case 0xA0: // LDY imm + y = data; + nz = data; + goto inc_pc_loop; + + case 0xB1: // LDA (ind),Y + IND_Y + goto lda_ptr; + + case 0xA1: // LDA (ind,X) + IND_X + goto lda_ptr; + + case 0xB9: // LDA abs,Y + data += y; + goto lda_ind_common; + + case 0x91: // STA (ind),Y + IND_Y + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X + goto sta_ptr; + + case 0x99: // STA abs,Y + data += y; + goto sta_ind_common; + + case 0xBC: // LDY abs,X + data += x; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + pc++; + unsigned addr = data + 0x100 * READ_PROG( pc ); + y = nz = READ( addr ); + goto inc_pc_loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + pc++; + unsigned addr = data + 0x100 * READ_PROG( pc ); + x = nz = READ( addr ); + goto inc_pc_loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + int addr = GET_ADDR(); + WRITE( addr, temp ); + pc += 2; + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = LOW_MEM( data ); + case 0xE0: // CPX imm + cpx_data: + nz = x - data; + goto compare_common; + + case 0xCC:{// CPY abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = LOW_MEM( data ); + case 0xC0: // CPY imm + cpy_data: + nz = y - data; + goto compare_common; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + goto inc_pc_loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + goto inc_pc_loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + goto inc_pc_loop; + +// Add/Subtract + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + int carry = (c >> 8) & 1; + int ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + c = nz = a + data + carry; + a = (uint8_t) nz; + status = (status & ~st_v) | ((ov >> 2) & 0x40); + goto inc_pc_loop; + } + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc++; + nz = READ( addr ); + goto bit_common; + } + + case 0x24: // BIT zp + nz = LOW_MEM( data ); + bit_common: + status = (status & ~st_v) | (nz & st_v); + if ( !(a & nz) ) // use special encoding since N and Z might both be set + nz = ((nz & 0x80) << 4) ^ ~0xFF; + goto inc_pc_loop; + +// Shift/Rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = (c >> 1) & 0x80; // could use bit insert macro here + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = (c >> 8) & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + HANDLE_PAGE_CROSSING( data ); + ADD_PAGE + nz = (c >> 8) & 1; + nz |= (c = READ( data ) << 1); + rotate_common: + WRITE( data, (uint8_t) nz ); + goto inc_pc_loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + HANDLE_PAGE_CROSSING( data ); + ADD_PAGE + int temp = READ( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = LOW_MEM( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = (c >> 8) & 1; + nz |= (c = LOW_MEM( data ) << 1); + goto write_nz_zp; + +// Increment/Decrement + + case 0xE8: INC_DEC_XY( x, 1 ) // INX + case 0xCA: INC_DEC_XY( x, -1 ) // DEX + case 0x88: INC_DEC_XY( y, -1 ) // DEY + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += LOW_MEM( data ); + write_nz_zp: + LOW_MEM( data ) = nz; + goto inc_pc_loop; + + case 0xFE: // INC abs,x + HANDLE_PAGE_CROSSING( data + x ); + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + HANDLE_PAGE_CROSSING( data + x ); + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: + nz += READ( data ); + pc += 2; + WRITE( data, (uint8_t) nz ); + goto loop; + +// Transfer + + case 0xAA: // TAX + x = a; + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = POP(); + goto loop; + + { + int temp; + case 0x40: // RTI + temp = POP(); + pc = POP(); + pc |= POP() << 8; + goto set_status; + case 0x28: // PLP + temp = POP(); + set_status: + #if !NES_CPU_IRQ_SUPPORT + SET_STATUS( temp ); + goto loop; + #endif + data = status & st_i; + SET_STATUS( temp ); + if ( !(data & ~status) ) + goto loop; + result = result_cli; // I flag was just cleared + goto end; + } + + case 0x08: { // PHP + int temp; + CALC_STATUS( temp ); + temp |= st_b | st_r; + PUSH( temp ); + goto loop; + } + + case 0x6C: // JMP (ind) + data = GET_ADDR(); + pc = READ( data ); + data++; + pc |= READ( data ) << 8; + goto loop; + + case 0x00: { // BRK + pc += 2; // verified + PUSH( pc >> 8 ); + PUSH( pc ); + status |= st_i; + int temp; + CALC_STATUS( temp ); + PUSH( temp | st_b | st_r ); + pc = READ_PROG16( 0xFFFE ); + goto loop; + } + +// Flags + + case 0x38: // SEC + c = ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + #if !NES_CPU_IRQ_SUPPORT + status &= ~st_i; + goto loop; + #endif + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + result = result_cli; + goto end; + + case 0x78: // SEI + status |= st_i; + goto loop; + +// undocumented + + case 0x0C: case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: // SKW + pc++; + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: // SKB + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + goto inc_pc_loop; + + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP + goto loop; + +// unimplemented + + case 0x9B: // TAS + case 0x9C: // SAY + case 0x9E: // XAS + case 0x93: // AXA + case 0x9F: // AXA + case 0x0B: // ANC + case 0x2B: // ANC + case 0xBB: // LAS + case 0x4B: // ALR + case 0x6B: // AAR + case 0x8B: // XAA + case 0xAB: // OAL + case 0xCB: // SAX + case 0x02: case 0x12: case 0x22: case 0x32: // ? + case 0x42: case 0x52: case 0x62: case 0x72: + case 0x92: case 0xB2: case 0xD2: case 0xF2: + case 0x83: case 0x87: case 0x8F: case 0x97: // AXS + case 0xA3: case 0xA7: case 0xAF: case 0xB3: case 0xB7: case 0xBF: // LAX + case 0xE3: case 0xE7: case 0xEF: case 0xF3: case 0xF7: case 0xFB: case 0xFF: // INS + case 0xC3: case 0xC7: case 0xCF: case 0xD3: case 0xD7: case 0xDB: case 0xDF: // DCM + case 0x63: case 0x67: case 0x6F: case 0x73: case 0x77: case 0x7B: case 0x7F: // RRA + case 0x43: case 0x47: case 0x4F: case 0x53: case 0x57: case 0x5B: case 0x5F: // LSE + case 0x23: case 0x27: case 0x2F: case 0x33: case 0x37: case 0x3B: case 0x3F: // RLA + case 0x03: case 0x07: case 0x0F: case 0x13: case 0x17: case 0x1B: case 0x1F: // ASO + result = result_badop; + goto stop; + } + assert( false ); + +stop: + pc--; +end: + + { + int temp; + CALC_STATUS( temp ); + r.status = temp; + } + + base_time += cycle_count; + #if NES_CPU_IRQ_SUPPORT + this->cycle_limit -= cycle_count; + #endif + this->cycle_count = 0; + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + + return result; +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Cpu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,123 @@ + +// Nintendo Entertainment System (NES) 6502 CPU emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_CPU_H +#define NES_CPU_H + +#include "blargg_common.h" + +typedef long nes_time_t; // clock cycle count +typedef unsigned nes_addr_t; // 16-bit address + +class Nsf_Emu; + +#ifndef NES_CPU_IRQ_SUPPORT + #define NES_CPU_IRQ_SUPPORT 0 +#endif + +class Nes_Cpu { + typedef BOOST::uint8_t uint8_t; + enum { page_bits = 11 }; + enum { page_count = 0x10000 >> page_bits }; + const uint8_t* code_map [page_count + 1]; +public: + Nes_Cpu(); + + // Clear registers, unmap memory, and map code pages to unmapped_page. + void reset( const void* unmapped_page = NULL ); + + // Memory read/write function types. Reader must return value from 0 to 255. + Nsf_Emu* callback_data; + typedef int (*reader_t)( Nsf_Emu* callback_data, nes_addr_t ); + typedef void (*writer_t)( Nsf_Emu* callback_data, nes_addr_t, int value ); + + // Memory mapping functions take a block of memory of specified 'start' address + // and 'size' in bytes. Both start address and size must be a multiple of page_size. + enum { page_size = 1L << page_bits }; + + // Map code memory to 'code' (memory accessed via the program counter) + void map_code( nes_addr_t start, unsigned long size, const void* code ); + + // Map data memory to read and write functions + void map_memory( nes_addr_t start, unsigned long size, reader_t, writer_t ); + + // Access memory as the emulated CPU does. + int read( nes_addr_t ); + void write( nes_addr_t, int value ); + uint8_t* get_code( nes_addr_t ); + + // NES 6502 registers. *Not* kept updated during a call to run(). + struct registers_t { + BOOST::uint16_t pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + } r; + + // Reasons that run() returns + enum result_t { + result_cycles, // Requested number of cycles (or more) were executed + result_cli, // I flag cleared + result_badop // unimplemented/illegal instruction + }; + + // Run CPU to or after end_time, or until a stop reason from above + // is encountered. Return the reason for stopping. + result_t run( nes_time_t end_time ); + + nes_time_t time() const { return base_time + cycle_count; } + void time( nes_time_t t ); + nes_time_t end_time() const { return base_time + cycle_limit; } + void end_frame( nes_time_t ); +#if NES_CPU_IRQ_SUPPORT + void end_time( nes_time_t t ) { cycle_limit = t - base_time; } +#endif + +private: + // noncopyable + Nes_Cpu( const Nes_Cpu& ); + Nes_Cpu& operator = ( const Nes_Cpu& ); + + nes_time_t cycle_count; + nes_time_t base_time; +#if NES_CPU_IRQ_SUPPORT + nes_time_t cycle_limit; +#else + enum { cycle_limit = 0 }; +#endif + + reader_t data_reader [page_count + 1]; // extra entry to catch address overflow + writer_t data_writer [page_count + 1]; + +public: + // low_mem is a full page size so it can be mapped with code_map + uint8_t low_mem [page_size > 0x800 ? page_size : 0x800]; +}; + + inline BOOST::uint8_t* Nes_Cpu::get_code( nes_addr_t addr ) { + return (uint8_t*) &code_map [(addr) >> page_bits] [(addr) & (page_size - 1)]; + } + +#if NES_CPU_IRQ_SUPPORT + inline void Nes_Cpu::time( nes_time_t t ) { + t -= time(); + cycle_limit -= t; + base_time += t; + } +#else + inline void Nes_Cpu::time( nes_time_t t ) { + cycle_count = t - base_time; + } +#endif + + inline void Nes_Cpu::end_frame( nes_time_t end_time ) { + base_time -= end_time; + assert( time() >= 0 ); + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Namco.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Namco.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,146 @@ + +// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ + +#include "Nes_Namco.h" + +#include "Tagged_Data.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Namco::Nes_Namco() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Namco::~Nes_Namco() { +} + +void Nes_Namco::reset() +{ + addr_reg = 0; + + int i; + for ( i = 0; i < reg_count; i++ ) + reg [i] = 0; + + for ( i = 0; i < osc_count; i++ ) { + Namco_Osc& osc = oscs [i]; + osc.delay = 0; + osc.last_amp = 0; + osc.wave_pos = 0; + } +} + +void Nes_Namco::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +BOOST::uint8_t& Nes_Namco::access() +{ + int addr = addr_reg & 0x7f; + if ( addr_reg & 0x80 ) + addr_reg = (addr + 1) | 0x80; + return reg [addr]; +} + +void Nes_Namco::reflect_state( Tagged_Data& data ) +{ + reflect_int16( data, 'ADDR', &addr_reg ); + + static const char hex [17] = "0123456789ABCDEF"; + int i; + for ( i = 0; i < reg_count; i++ ) + reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); + + for ( i = 0; i < osc_count; i++ ) { + reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); + reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); + } +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Nes_Namco::run_until( nes_time_t nes_end_time ) +{ + int active_oscs = ((reg [0x7f] >> 4) & 7) + 1; + for ( int i = osc_count - active_oscs; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + Blip_Buffer* output = osc.output; + if ( !output ) + continue; + + Blip_Buffer::resampled_time_t time = + output->resampled_time( last_time ) + osc.delay; + Blip_Buffer::resampled_time_t end_time = output->resampled_time( nes_end_time ); + osc.delay = 0; + if ( time < end_time ) + { + const BOOST::uint8_t* osc_reg = ® [i * 8 + 0x40]; + if ( !(osc_reg [4] & 0xe0) ) + continue; + + int volume = osc_reg [7] & 15; + if ( !volume ) + continue; + + long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + if ( !freq ) + continue; + Blip_Buffer::resampled_time_t period = + output->resampled_duration( 983040 ) / freq * active_oscs; + + int wave_size = (8 - ((osc_reg [4] >> 2) & 7)) * 4; + if ( !wave_size ) + continue; + + int last_amp = osc.last_amp; + int wave_pos = osc.wave_pos; + + do { + // read wave sample + int addr = wave_pos + osc_reg [6]; + int sample = reg [addr >> 1]; + wave_pos++; + if ( addr & 1 ) + sample >>= 4; + sample = (sample & 15) * volume; + + // output impulse if amplitude changed + int delta = sample - last_amp; + if ( delta ) { + last_amp = sample; + synth.offset_resampled( time, delta, output ); + } + + // next sample + time += period; + if ( wave_pos >= wave_size ) + wave_pos = 0; + } + while ( time < end_time ); + + osc.wave_pos = wave_pos; + osc.last_amp = last_amp; + } + osc.delay = time - end_time; + } + + last_time = nes_end_time; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Namco.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Namco.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,99 @@ + +// Namco 106 sound chip emulator + +// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_NAMCO_H +#define NES_NAMCO_H + +#include "Nes_Apu.h" + +class Tagged_Data; + +class Nes_Namco { +public: + Nes_Namco(); + ~Nes_Namco(); + + // See Nes_Apu.h for reference. + + void volume( double ); + void treble_eq( const blip_eq_t& ); + void output( Blip_Buffer* ); + enum { osc_count = 8 }; + void osc_output( int index, Blip_Buffer* ); + void reset(); + + // Read/write data register is at $4800 + enum { data_reg_addr = 0x4800 }; + void write_data( nes_time_t, int ); + int read_data(); + + // Write-only address register is at $F800 + enum { addr_reg_addr = 0xF800 }; + void write_addr( int ); + + void end_frame( nes_time_t ); + void reflect_state( Tagged_Data& ); + +// End of public interface + +private: + // noncopyable + Nes_Namco( const Nes_Namco& ); + Nes_Namco& operator = ( const Nes_Namco& ); + + struct Namco_Osc { + long delay; + Blip_Buffer* output; + short last_amp; + short wave_pos; + }; + + Namco_Osc oscs [osc_count]; + + nes_time_t last_time; + int addr_reg; + + enum { reg_count = 0x80 }; + BOOST::uint8_t reg [reg_count]; + Blip_Synth synth; + + BOOST::uint8_t& access(); + void run_until( nes_time_t ); +}; + + inline void Nes_Namco::volume( double v ) { + synth.volume( 0.10 / osc_count * v ); + } + + inline void Nes_Namco::treble_eq( const blip_eq_t& eq ) { + synth.treble_eq( eq ); + } + + inline void Nes_Namco::osc_output( int i, Blip_Buffer* buf ) { + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; + } + + inline void Nes_Namco::write_addr( int v ) { + addr_reg = v; + } + + inline int Nes_Namco::read_data() { + return access(); + } + + inline void Nes_Namco::write_data( nes_time_t time, int data ) { + run_until( time ); + access() = data; + } + + inline void Nes_Namco::end_frame( nes_time_t time ) { + run_until( time ); + last_time -= time; + assert( last_time >= 0 ); + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Oscs.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Oscs.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,485 @@ + +// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Nes_Osc + +void Nes_Osc::clock_length( int halt_mask ) +{ + if ( length_counter && !(regs [0] & halt_mask) ) + length_counter--; +} + +void Nes_Envelope::clock_envelope() +{ + int period = regs [0] & 15; + if ( reg_written [3] ) { + reg_written [3] = false; + env_delay = period; + envelope = 15; + } + else if ( --env_delay < 0 ) { + env_delay = period; + if ( envelope | (regs [0] & 0x20) ) + envelope = (envelope - 1) & 15; + } +} + +int Nes_Envelope::volume() const +{ + return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope; +} + +// Nes_Square + +void Nes_Square::clock_sweep( int negative_adjust ) +{ + int sweep = regs [1]; + + if ( --sweep_delay < 0 ) + { + reg_written [1] = true; + + int period = this->period(); + int shift = sweep & shift_mask; + if ( shift && (sweep & 0x80) && period >= 8 ) + { + int offset = period >> shift; + + if ( sweep & negate_flag ) + offset = negative_adjust - offset; + + if ( period + offset < 0x800 ) + { + period += offset; + // rewrite period + regs [2] = period & 0xff; + regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); + } + } + } + + if ( reg_written [1] ) { + reg_written [1] = false; + sweep_delay = (sweep >> 4) & 7; + } +} + +void Nes_Square::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + const int volume = this->volume(); + const int period = this->period(); + int offset = period >> (regs [1] & shift_mask); + if ( regs [1] & negate_flag ) + offset = 0; + + const int timer_period = (period + 1) * 2; + if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) + { + if ( last_amp ) { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + + time += delay; + if ( time < end_time ) + { + // maintain proper phase + int count = (end_time - time + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (long) count * timer_period; + } + } + else + { + // handle duty select + int duty_select = (regs [0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if ( duty_select == 3 ) { + duty = 2; // negated 25% + amp = volume; + } + if ( phase < duty ) + amp ^= volume; + + int delta = update_amp( amp ); + if ( delta ) + synth->offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const Synth* synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (phase_range - 1); + if ( phase == 0 || phase == duty ) { + delta = -delta; + synth->offset_inline( time, delta, output ); + } + time += timer_period; + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + delay = time - end_time; +} + +// Nes_Triangle + +void Nes_Triangle::clock_linear_counter() +{ + if ( reg_written [3] ) + linear_counter = regs [0] & 0x7f; + else if ( linear_counter ) + linear_counter--; + + if ( !(regs [0] & 0x80) ) + reg_written [3] = false; +} + +void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + time += delay; + const int timer_period = period() + 1; + if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) + { + time = end_time; + } + else if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + + int phase = this->phase; + int volume = 1; + if ( phase > phase_range ) { + phase -= phase_range; + volume = -volume; + } + + do { + if ( --phase == 0 ) { + phase = phase_range; + volume = -volume; + } + else { + synth.offset_inline( time, volume, output ); + } + + time += timer_period; + } + while ( time < end_time ); + + if ( volume < 0 ) + phase += phase_range; + this->phase = phase; + } + delay = time - end_time; +} + +// Nes_Dmc + +void Nes_Dmc::reset() +{ + address = 0; + dac = 0; + buf = 0; + bits_remain = 1; + bits = 0; + buf_empty = true; + silence = true; + next_irq = Nes_Apu::no_irq; + irq_flag = false; + irq_enabled = false; + + Nes_Osc::reset(); + period = 0x036; +} + +void Nes_Dmc::recalc_irq() +{ + nes_time_t irq = Nes_Apu::no_irq; + if ( irq_enabled && length_counter ) + irq = apu->last_time + delay + + ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; + if ( irq != next_irq ) { + next_irq = irq; + apu->irq_changed(); + } +} + +int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const +{ + if ( last_read ) + *last_read = time; + + if ( length_counter == 0 ) + return 0; // not reading + + long first_read = apu->last_time + delay + long (bits_remain - 1) * period; + long avail = time - first_read; + if ( avail <= 0 ) + return 0; + + int count = (avail - 1) / (period * 8) + 1; + if ( !(regs [0] & loop_flag) && count > length_counter ) + count = length_counter; + + if ( last_read ) { + *last_read = first_read + (count - 1) * (period * 8) + 1; + assert( *last_read <= time ); + assert( count == count_reads( *last_read, NULL ) ); + assert( count - 1 == count_reads( *last_read - 1, NULL ) ); + } + + return count; +} + +static const short dmc_period_table [2] [16] = { + 0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC + 0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036, + + 0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) + 0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032 // to do: verify PAL periods +}; + +inline void Nes_Dmc::reload_sample() +{ + address = 0x4000 + regs [2] * 0x40; + length_counter = regs [3] * 0x10 + 1; +} + +static const unsigned char dac_table [128] = { + 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, + 10, 10, 11, 11, 12, 13, 13, 14, 14, 15, 15, 16, 17, 17, 18, 18, + 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, + 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 32, 33, 33, 34, + 34, 35, 35, 35, 36, 36, 37, 37, 38, 38, 38, 39, 39, 40, 40, 40, + 41, 41, 42, 42, 42, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 47, + 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, + 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57 +}; + +void Nes_Dmc::write_register( int addr, int data ) +{ + if ( addr == 0 ) { + period = dmc_period_table [pal_mode] [data & 15]; + irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled + irq_flag &= irq_enabled; + recalc_irq(); + } + else if ( addr == 1 ) + { + if ( !nonlinear ) + { + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int old_amp = dac_table [dac]; + dac = data & 0x7F; + int diff = dac_table [dac] - old_amp; + last_amp = dac - diff; + } + + dac = data & 0x7F; + } +} + +void Nes_Dmc::start() +{ + reload_sample(); + fill_buffer(); + recalc_irq(); +} + +void Nes_Dmc::fill_buffer() +{ + if ( buf_empty && length_counter ) + { + require( rom_reader ); // dmc_reader must be set + buf = rom_reader( rom_reader_data, 0x8000u + address ); + address = (address + 1) & 0x7FFF; + buf_empty = false; + if ( --length_counter == 0 ) + { + if ( regs [0] & loop_flag ) { + reload_sample(); + } + else { + apu->osc_enables &= ~0x10; + irq_flag = irq_enabled; + next_irq = Nes_Apu::no_irq; + apu->irq_changed(); + } + } + } +} + +void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + int delta = update_amp( dac ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + int bits_remain = this->bits_remain; + if ( silence && buf_empty ) + { + int count = (end_time - time + period - 1) / period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * period; + } + else + { + Blip_Buffer* const output = this->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if ( !silence ) + { + const int step = (bits & 1) * 4 - 2; + bits >>= 1; + if ( unsigned (dac + step) <= 0x7F ) { + dac += step; + synth.offset_inline( time, step, output ); + } + } + + time += period; + + if ( --bits_remain == 0 ) + { + bits_remain = 8; + if ( buf_empty ) { + silence = true; + } + else { + silence = false; + bits = buf; + buf_empty = true; + fill_buffer(); + } + } + } + while ( time < end_time ); + + this->dac = dac; + this->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + delay = time - end_time; +} + +// Nes_Noise + +#include BLARGG_ENABLE_OPTIMIZER + +static const short noise_period_table [16] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 +}; + +void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) +{ + if ( !output ) + return; + + const int volume = this->volume(); + int amp = (noise & 1) ? volume : 0; + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + const int mode_flag = 0x80; + + int period = noise_period_table [regs [2] & 15]; + if ( !volume ) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register a bit + // to do: precise muted noise cycling? + if ( !(regs [2] & mode_flag) ) { + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + else + { + Blip_Buffer* const output = this->output; + + // using resampled time avoids conversion in synth.offset() + Blip_Buffer::resampled_time_t rperiod = output->resampled_duration( period ); + Blip_Buffer::resampled_time_t rtime = output->resampled_time( time ); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (regs [2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ( (noise + 1) & 2 ) { + // bits 0 and 1 of noise differ + delta = -delta; + synth.offset_resampled( rtime, delta, output ); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + delay = time - end_time; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Oscs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Oscs.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,141 @@ + +// Private oscillators used by Nes_Apu + +// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_OSCS_H +#define NES_OSCS_H + +#include "Blip_Buffer.h" + +class Nes_Apu; + +struct Nes_Osc +{ + unsigned char regs [4]; + bool reg_written [4]; + Blip_Buffer* output; + int length_counter;// length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting + + void clock_length( int halt_mask ); + int period() const { + return (regs [3] & 7) * 0x100 + (regs [2] & 0xff); + } + void reset() { + delay = 0; + last_amp = 0; + } + int update_amp( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + return delta; + } +}; + +struct Nes_Envelope : Nes_Osc +{ + int envelope; + int env_delay; + + void clock_envelope(); + int volume() const; + void reset() { + envelope = 0; + env_delay = 0; + Nes_Osc::reset(); + } +}; + +// Nes_Square +struct Nes_Square : Nes_Envelope +{ + enum { negate_flag = 0x08 }; + enum { shift_mask = 0x07 }; + enum { phase_range = 8 }; + int phase; + int sweep_delay; + + typedef Blip_Synth Synth; + const Synth* synth; // shared between squares + + void clock_sweep( int adjust ); + void run( nes_time_t, nes_time_t ); + void reset() { + sweep_delay = 0; + Nes_Envelope::reset(); + } +}; + +// Nes_Triangle +struct Nes_Triangle : Nes_Osc +{ + enum { phase_range = 16 }; + int phase; + int linear_counter; + Blip_Synth synth; + + void run( nes_time_t, nes_time_t ); + void clock_linear_counter(); + void reset() { + linear_counter = 0; + phase = phase_range; + Nes_Osc::reset(); + } +}; + +// Nes_Noise +struct Nes_Noise : Nes_Envelope +{ + int noise; + Blip_Synth synth; + + void run( nes_time_t, nes_time_t ); + void reset() { + noise = 1 << 14; + Nes_Envelope::reset(); + } +}; + +// Nes_Dmc +struct Nes_Dmc : Nes_Osc +{ + int address; // address of next byte to read + int period; + //int length_counter; // bytes remaining to play (already defined in Nes_Osc) + int buf; + int bits_remain; + int bits; + bool buf_empty; + bool silence; + + enum { loop_flag = 0x40 }; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*rom_reader)( void*, nes_addr_t ); // needs to be initialized to rom read function + void* rom_reader_data; + + Nes_Apu* apu; + + Blip_Synth synth; + + void start(); + void write_register( int, int ); + void run( nes_time_t, nes_time_t ); + void recalc_irq(); + void fill_buffer(); + void reload_sample(); + void reset(); + int count_reads( nes_time_t, nes_time_t* ) const; +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Vrc6.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Vrc6.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,210 @@ + +// Nes_Snd_Emu 0.1.6. http://www.slack.net/~ant/libs/ + +#include "Nes_Vrc6.h" + +#include "Tagged_Data.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Vrc6::Nes_Vrc6() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +void Nes_Vrc6::reset() +{ + last_time = 0; + for ( int i = 0; i < osc_count; i++ ) { + Vrc6_Osc& osc = oscs [i]; + for ( int j = 0; j < reg_count; j++ ) + osc.regs [j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } +} + +Nes_Vrc6::~Nes_Vrc6() { +} + +void Nes_Vrc6::volume( double v ) +{ + v *= 0.0967 * 2; + saw_synth.volume( v ); + square_synth.volume( v * 0.5 ); +} + +void Nes_Vrc6::treble_eq( const blip_eq_t& eq ) +{ + saw_synth.treble_eq( eq ); + square_synth.treble_eq( eq ); +} + +void Nes_Vrc6::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc6::run_until( nes_time_t time ) +{ + run_square( oscs [0], time ); + run_square( oscs [1], time ); + run_saw( time ); + last_time = time; +} + +void Nes_Vrc6::write_osc( nes_time_t time, int osc_index, int reg, int data ) +{ + require( (unsigned) osc_index < osc_count ); + require( (unsigned) reg < reg_count ); + + run_until( time ); + oscs [osc_index].regs [reg] = data; + + // to do: remove? this messed up volume envelope in Akumajou Densetsu track 22 + //if ( osc_index == 2 && reg == 2 ) + // oscs [2].amp = 0; +} + +void Nes_Vrc6::end_frame( nes_time_t time ) +{ + run_until( time ); + last_time -= time; + assert( last_time >= 0 ); +} + +void Nes_Vrc6::reflect_state( Tagged_Data& data ) +{ + for ( int i = 0; i < osc_count; i++ ) + { + Tagged_Data odata( data, 'cCH0' + i ); + Vrc6_Osc& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + reflect_int16( odata, 'REG0' + r, &osc.regs [r] ); + reflect_int16( odata, 'DELY', &osc.delay ); + reflect_int16( odata, 'PHAS', &osc.phase ); + if ( i == 2 ) + reflect_int16( odata, 'AMPL', &osc.amp ); + } +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Nes_Vrc6::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +{ + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int volume = osc.regs [0] & 15; + if ( !(osc.regs [2] & 0x80) ) + volume = 0; + + int gate = osc.regs [0] & 0x80; + int duty = ((osc.regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + nes_time_t time = last_time; + if ( delta ) { + osc.last_amp += delta; + square_synth.offset( time, delta, output ); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc.phase; + + do { + phase++; + if ( phase == 16 ) { + phase = 0; + osc.last_amp = volume; + square_synth.offset( time, volume, output ); + } + if ( phase == duty ) { + osc.last_amp = 0; + square_synth.offset( time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase; + } + osc.delay = time - end_time; + } +} + +void Nes_Vrc6::run_saw( nes_time_t end_time ) +{ + Vrc6_Osc& osc = oscs [2]; + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int amp = osc.amp; + int amp_step = osc.regs [0] & 0x3F; + nes_time_t time = last_time; + int last_amp = osc.last_amp; + if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + else + { + time += osc.delay; + if ( time < end_time ) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do { + if ( --phase == 0 ) { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) { + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nes_Vrc6.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nes_Vrc6.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,75 @@ + +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_VRC6_H +#define NES_VRC6_H + +#include "Nes_Apu.h" + +class Tagged_Data; + +class Nes_Vrc6 { +public: + Nes_Vrc6(); + ~Nes_Vrc6(); + + // See Nes_Apu.h for reference. + + void volume( double ); + void treble_eq( const blip_eq_t& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void reset(); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum { reg_count = 3 }; + enum { base_addr = 0x9000 }; + enum { addr_step = 0x1000 }; + void write_osc( nes_time_t, int osc, int reg, int data ); + + void end_frame( nes_time_t ); + void reflect_state( Tagged_Data& ); + +// End of public interface + +private: + // noncopyable + Nes_Vrc6( const Nes_Vrc6& ); + Nes_Vrc6& operator = ( const Nes_Vrc6& ); + + struct Vrc6_Osc { + BOOST::uint8_t regs [3]; + Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const { + return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; + } + }; + + Vrc6_Osc oscs [osc_count]; + nes_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until( nes_time_t ); + void run_square( Vrc6_Osc& osc, nes_time_t ); + void run_saw( nes_time_t ); +}; + + inline void Nes_Vrc6::osc_output( int i, Blip_Buffer* buf ) { + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; + } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nsf_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nsf_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,473 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Nsf_Emu.h" + +#include +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +const unsigned low_mem_size = 0x800; +const unsigned page_size = 0x1000; +const long ram_size = 0x10000; +const nes_addr_t rom_begin = 0x8000; +const nes_addr_t bank_select_addr = 0x5ff8; +const nes_addr_t exram_addr = bank_select_addr - (bank_select_addr % Nes_Cpu::page_size); +const int master_clock_divisor = 12; + +const int vrc6_flag = 0x01; +const int namco_flag = 0x10; + +// ROM + +int Nsf_Emu::read_code( Nsf_Emu* emu, nes_addr_t addr ) +{ + return *emu->cpu.get_code( addr ); +} + +void Nsf_Emu::write_exram( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + unsigned bank = addr - bank_select_addr; + if ( bank < bank_count ) + { + if ( data < emu->total_banks ) { + emu->cpu.map_code( (bank + 8) * page_size, page_size, + &emu->rom [data * page_size] ); + } + else { + dprintf( "Bank %d out of range (%d banks total)\n", + data, (int) emu->total_banks ); + } + } +} + +// APU + +int Nsf_Emu::read_snd( Nsf_Emu* emu, nes_addr_t addr ) +{ + if ( addr == Nes_Apu::status_addr ) + return emu->apu.read_status( emu->cpu.time() ); + return addr >> 8; // high byte of address stays on bus +} + +void Nsf_Emu::write_snd( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + if ( unsigned (addr - Nes_Apu::start_addr) <= Nes_Apu::end_addr - Nes_Apu::start_addr ) + emu->apu.write_register( emu->cpu.time(), addr, data ); +} + +int Nsf_Emu::pcm_read( void* emu, nes_addr_t addr ) +{ + return ((Nsf_Emu*) emu)->cpu.read( addr ); +} + +// Low Mem + +int Nsf_Emu::read_low_mem( Nsf_Emu* emu, nes_addr_t addr ) +{ + return emu->cpu.low_mem [addr & (low_mem_size - 1)]; +} + +void Nsf_Emu::write_low_mem( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + emu->cpu.low_mem [addr & (low_mem_size - 1)] = data; +} + +// SRAM + +int Nsf_Emu::read_sram( Nsf_Emu* emu, nes_addr_t addr ) +{ + return emu->sram [addr & (sram_size - 1)]; +} + +void Nsf_Emu::write_sram( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + emu->sram [addr & (sram_size - 1)] = data; +} + +#if !NSF_EMU_APU_ONLY + +// Namco +int Nsf_Emu::read_namco( Nsf_Emu* emu, nes_addr_t addr ) +{ + if ( addr == Nes_Namco::data_reg_addr ) + return emu->namco.read_data(); + return addr >> 8; +} + +void Nsf_Emu::write_namco( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + if ( addr == Nes_Namco::data_reg_addr ) + emu->namco.write_data( emu->cpu.time(), data ); +} + +void Nsf_Emu::write_namco_addr( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + if ( addr == Nes_Namco::addr_reg_addr ) + emu->namco.write_addr( data ); +} + +// VRC6 +void Nsf_Emu::write_vrc6( Nsf_Emu* emu, nes_addr_t addr, int data ) +{ + unsigned reg = addr & (Nes_Vrc6::addr_step - 1); + unsigned osc = unsigned (addr - Nes_Vrc6::base_addr) / Nes_Vrc6::addr_step; + if ( osc < Nes_Vrc6::osc_count && reg < Nes_Vrc6::reg_count ) + emu->vrc6.write_osc( emu->cpu.time(), osc, reg, data ); +} + +#endif + +// Unmapped +int Nsf_Emu::read_unmapped( Nsf_Emu*, nes_addr_t addr ) +{ + dprintf( "Read unmapped $%.4X\n", (unsigned) addr ); + return addr >> 8; // high byte of address stays on bus +} + +void Nsf_Emu::write_unmapped( Nsf_Emu*, nes_addr_t addr, int ) +{ + if (// some games write to $8000 and $8001 repeatedly + addr != 0x8000 && addr != 0x8001 && + + // probably namco sound mistakenly turned on in mck + addr != 0x4800 && addr != 0xF800 && + + // memory mapper? + addr != 0xFFF8 ) + { + dprintf( "Write unmapped $%.4X\n", (unsigned) addr ); + } +} + +static BOOST::uint8_t unmapped_code [Nes_Cpu::page_size]; + +Nsf_Emu::Nsf_Emu( double gain_ ) +{ + rom = NULL; + play_addr = 0; + clocks_per_msec = 0; + gain = gain_; + cpu.callback_data = this; + set_equalizer( equalizer_t( -8.87, 8800, 110 ) ); + apu.dmc_reader( pcm_read, this ); + // set unmapped code to illegal instruction + memset( unmapped_code, 0x32, sizeof unmapped_code ); +} + +Nsf_Emu::~Nsf_Emu() +{ + unload(); +} + +void Nsf_Emu::unload() +{ + delete [] rom; + rom = NULL; +} + +const char** Nsf_Emu::voice_names() const +{ + static const char* base_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC" + }; + static const char* namco_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC", + "Namco 5&7", "Namco 4&6", "Namco 1-3" + }; + static const char* vrc6_names [] = { + "Square 1", "Square 2", "Triangle", "Noise", "DMC", + "VRC6 Square 1", "VRC6 Square 2", "VRC6 Saw" + }; + if ( exp_flags & namco_flag ) + return namco_names; + if ( exp_flags & vrc6_flag ) + return vrc6_names; + return base_names; +} + +blargg_err_t Nsf_Emu::init_sound() +{ + if ( exp_flags & ~(namco_flag | vrc6_flag) ) + return "NSF requires unsupported expansion audio hardware"; + + // map memory + cpu.reset( unmapped_code ); + cpu.map_memory( 0, ram_size, read_unmapped, write_unmapped ); // unmapped + cpu.map_memory( 0, low_mem_size, read_low_mem, write_low_mem ); // low mem + cpu.map_code( 0, low_mem_size, cpu.low_mem ); + cpu.map_memory( 0x4000, Nes_Cpu::page_size, read_snd, write_snd ); // apu + cpu.map_memory( exram_addr, Nes_Cpu::page_size, read_unmapped, write_exram ); // exram + cpu.map_memory( 0x6000, sram_size, read_sram, write_sram ); // sram + cpu.map_code ( 0x6000, sram_size, sram ); + cpu.map_memory( rom_begin, ram_size - rom_begin, read_code, write_unmapped ); // rom + + voice_count_ = Nes_Apu::osc_count; + + double adjusted_gain = gain; + +#if NSF_EMU_APU_ONLY + if ( exp_flags ) + return "NSF requires expansion audio hardware"; +#else + // namco + if ( exp_flags & namco_flag ) { + adjusted_gain *= 0.75; + voice_count_ += 3; + cpu.map_memory( Nes_Namco::data_reg_addr, Nes_Cpu::page_size, + read_namco, write_namco ); + cpu.map_memory( Nes_Namco::addr_reg_addr, Nes_Cpu::page_size, + read_code, write_namco_addr ); + } + + // vrc6 + if ( exp_flags & vrc6_flag ) { + adjusted_gain *= 0.75; + voice_count_ += 3; + for ( int i = 0; i < Nes_Vrc6::osc_count; i++ ) + cpu.map_memory( Nes_Vrc6::base_addr + i * Nes_Vrc6::addr_step, + Nes_Cpu::page_size, read_code, write_vrc6 ); + } + + namco.volume( adjusted_gain ); + vrc6.volume( adjusted_gain ); +#endif + + apu.volume( adjusted_gain ); + + return blargg_success; +} + +void Nsf_Emu::update_eq( blip_eq_t const& eq ) +{ +#if !NSF_EMU_APU_ONLY + vrc6.treble_eq( eq ); + namco.treble_eq( eq ); +#endif + apu.treble_eq( eq ); +} + +blargg_err_t Nsf_Emu::load( const header_t& h, Emu_Reader& in ) +{ + unload(); + + // check compatibility + if ( 0 != memcmp( h.tag, "NESM\x1A", 5 ) ) + return "Not an NSF file"; + if ( h.vers != 1 ) + return "Unsupported NSF format"; + + // sound and memory + exp_flags = h.chip_flags; + blargg_err_t err = init_sound(); + if ( err ) + return err; + + // set up data + nes_addr_t load_addr = get_le16( h.load_addr ); + init_addr = get_le16( h.init_addr ); + play_addr = get_le16( h.play_addr ); + if ( !load_addr ) load_addr = rom_begin; + if ( !init_addr ) init_addr = rom_begin; + if ( !play_addr ) play_addr = rom_begin; + if ( load_addr < rom_begin || init_addr < rom_begin ) + return "Invalid address in NSF"; + + // set up rom + total_banks = (in.remain() + load_addr % page_size + page_size - 1) / page_size; + long rom_size = total_banks * page_size; + rom = new byte [rom_size]; + if ( !rom ) + return "Out of memory"; + memset( rom, 0, rom_size ); + err = in.read( &rom [load_addr % page_size], in.remain() ); + if ( err ) { + unload(); + return err; + } + + // bank switching + int first_bank = (load_addr - rom_begin) / page_size; + for ( int i = 0; i < bank_count; i++ ) + { + unsigned bank = i - first_bank; + initial_banks [i] = (bank < total_banks) ? bank : 0; + + if ( h.banks [i] ) { + // bank-switched + memcpy( initial_banks, h.banks, sizeof initial_banks ); + break; + } + } + + // playback rate + unsigned playback_rate = get_le16( h.ntsc_speed ); + unsigned standard_rate = 0x411A; + double clock_rate = 1789772.72727; + play_period = 262 * 341L * 4 + 2; + pal_only = false; + + // use pal speed if there is no ntsc speed + if ( (h.speed_flags & 3) == 1 ) { + pal_only = true; + play_period = 33247 * master_clock_divisor; + clock_rate = 1662607.125; + standard_rate = 0x4E20; + playback_rate = get_le16( h.pal_speed ); + } + + clocks_per_msec = clock_rate * (1.0 / 1000.0); + + // use custom playback rate if not the standard rate + if ( playback_rate && playback_rate != standard_rate ) + play_period = long (clock_rate * playback_rate * master_clock_divisor / + 1000000.0); + + // extra flags + int extra_flags = h.speed_flags; + #if !NSF_EMU_EXTRA_FLAGS + extra_flags = 0; + #endif + needs_long_frames = (extra_flags & 0x10) != 0; + initial_pcm_dac = (extra_flags & 0x20) ? 0x3F : 0; + + track_count_ = h.track_count; + + return setup_buffer( clock_rate + 0.5 ); +} + +void Nsf_Emu::set_voice( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) +{ +#if !NSF_EMU_APU_ONLY + if ( i >= Nes_Apu::osc_count ) { + vrc6.osc_output( i - Nes_Apu::osc_count, buf ); + if ( i < 7 ) { + i &= 1; + namco.osc_output( i + 4, buf ); + namco.osc_output( i + 6, buf ); + } + else { + for ( int n = 0; n < namco.osc_count / 2; n++ ) + namco.osc_output( n, buf ); + } + return; + } +#endif + apu.osc_output( i, buf ); +} + +blargg_err_t Nsf_Emu::start_track( int track ) +{ + require( rom ); // file must be loaded + + starting_track(); + + // clear memory + memset( cpu.low_mem, 0, sizeof cpu.low_mem ); + memset( sram, 0, sizeof sram ); + + // initial rom banks + for ( int i = 0; i < bank_count; ++i ) + cpu.write( bank_select_addr + i, initial_banks [i] ); + + // reset sound + apu.reset( pal_only, initial_pcm_dac ); + apu.write_register( 0, 0x4015, 0x0F ); + apu.write_register( 0, 0x4017, needs_long_frames ? 0x80 : 0 ); + +#if !NSF_EMU_APU_ONLY + if ( exp_flags ) { + namco.reset(); + vrc6.reset(); + } +#endif + + // reset cpu + cpu.r.pc = exram_addr; + cpu.r.a = track; + cpu.r.x = pal_only; + cpu.r.y = 0; + cpu.r.sp = 0xFF; + cpu.r.status = 0x04; // i flag + + // first call + cpu_jsr( init_addr, -1 ); + next_play = 0; + play_extra = 0; + + return blargg_success; +} + +void Nsf_Emu::cpu_jsr( nes_addr_t pc, int adj ) +{ + unsigned ret_addr = cpu.r.pc + adj; + cpu.r.pc = pc; + cpu.low_mem [cpu.r.sp-- + 0x100] = ret_addr >> 8; + cpu.low_mem [cpu.r.sp-- + 0x100] = ret_addr; +} + +blip_time_t Nsf_Emu::run( int msec, bool* ) +{ + // run cpu + blip_time_t duration = clocks_per_msec * msec; + cpu.time( 0 ); + while ( cpu.time() < duration ) + { + // check for idle cpu + if ( cpu.r.pc == exram_addr ) + { + if ( next_play > duration ) { + cpu.time( duration ); + break; + } + + if ( next_play > cpu.time() ) + cpu.time( next_play ); + + nes_time_t period = (play_period + play_extra) / master_clock_divisor; + play_extra = play_period - period * master_clock_divisor; + next_play += period; + cpu_jsr( play_addr, -1 ); + } + + Nes_Cpu::result_t result = cpu.run( duration ); + if ( result == Nes_Cpu::result_badop && cpu.r.pc != exram_addr ) + { + dprintf( "Bad opcode $%.2x at $%.4x\n", + (int) cpu.read( cpu.r.pc ), (int) cpu.r.pc ); + + return 0; // error + } + } + + // end time frame + duration = cpu.time(); + next_play -= duration; + if ( next_play < 0 ) // could go negative if routine is taking too long to return + next_play = 0; + apu.end_frame( duration ); + +#if !NSF_EMU_APU_ONLY + if ( exp_flags & namco_flag ) + namco.end_frame( duration ); + if ( exp_flags & vrc6_flag ) + vrc6.end_frame( duration ); +#endif + + return duration; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Nsf_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Nsf_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,127 @@ + +// Nintendo Entertainment System (NES) NSF-format game music file emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NSF_EMU_H +#define NSF_EMU_H + +#include "Classic_Emu.h" +#include "Nes_Apu.h" +#include "Nes_Cpu.h" + +// If NSF_EMU_APU_ONLY is non-zero, external sound chip support is disabled +#if !NSF_EMU_APU_ONLY + #include "Nes_Vrc6.h" + #include "Nes_Namco.h" +#endif + +class Nsf_Emu : public Classic_Emu { +public: + // Set internal gain, where 1.0 results in almost no clamping. Default gain + // roughly matches volume of other emulators. + Nsf_Emu( double gain = 1.4 ); + ~Nsf_Emu(); + + struct header_t { + 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]; + 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]; + + enum { song = 0 }; // no song titles + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 0x80 ); + + // Load NSF, given its header and reader for remaining data + blargg_err_t load( const header_t&, Emu_Reader& ); + + blargg_err_t start_track( int ); + Nes_Apu* apu_() { return &apu; } + const char** voice_names() const; + + +// End of public interface +protected: + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + blip_time_t run( int, bool* ); +private: + // initial state + enum { bank_count = 8 }; + byte initial_banks [bank_count]; + int initial_pcm_dac; + double gain; + bool needs_long_frames; + bool pal_only; + unsigned init_addr; + unsigned play_addr; + int exp_flags; + + // timing + double clocks_per_msec; + nes_time_t next_play; + long play_period; + int play_extra; + nes_time_t clock() const; + nes_time_t next_irq( nes_time_t end_time ); + static void irq_changed( void* ); + + // rom + int total_banks; + byte* rom; + static int read_code( Nsf_Emu*, nes_addr_t ); + void unload(); + + // cpu + Nes_Cpu cpu; + void cpu_jsr( unsigned pc, int adj ); + static int read_low_mem( Nsf_Emu*, nes_addr_t ); + static void write_low_mem( Nsf_Emu*, nes_addr_t, int ); + static int read_unmapped( Nsf_Emu*, nes_addr_t ); + static void write_unmapped( Nsf_Emu*, nes_addr_t, int ); + static void write_exram( Nsf_Emu*, nes_addr_t, int ); + + blargg_err_t init_sound(); + + // apu + Nes_Apu apu; + static int read_snd( Nsf_Emu*, nes_addr_t ); + static void write_snd( Nsf_Emu*, nes_addr_t, int ); + static int pcm_read( void*, nes_addr_t ); + +#if !NSF_EMU_APU_ONLY + // namco + Nes_Namco namco; + static int read_namco( Nsf_Emu*, nes_addr_t ); + static void write_namco( Nsf_Emu*, nes_addr_t, int ); + static void write_namco_addr( Nsf_Emu*, nes_addr_t, int ); + + // vrc6 + Nes_Vrc6 vrc6; + static void write_vrc6( Nsf_Emu*, nes_addr_t, int ); +#endif + + // sram + enum { sram_size = 0x2000 }; + byte sram [sram_size]; + static int read_sram( Nsf_Emu*, nes_addr_t ); + static void write_sram( Nsf_Emu*, nes_addr_t, int ); + + friend class Nsf_Remote_Emu; // hack +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Panning_Buffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Panning_Buffer.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,180 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Panning_Buffer.h" + +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +typedef long fixed_t; + +#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) +#define FMUL( x, y ) (((x) * (y)) >> 15) + +Panning_Buffer::Panning_Buffer() +{ + bufs = NULL; + buf_count = 0; + bass_freq_ = -1; + clock_rate_ = -1; +} + +Panning_Buffer::~Panning_Buffer() +{ + delete [] bufs; +} + +blargg_err_t Panning_Buffer::sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + BLARGG_RETURN_ERR( bufs [i].sample_rate( rate, msec ) ); + sample_rate_ = rate; + length_ = buf_count ? bufs [0].length() : msec; + return blargg_success; +} + +void Panning_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( freq ); +} + +void Panning_Buffer::clock_rate( long rate ) +{ + clock_rate_ = rate; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( clock_rate_ ); +} + +void Panning_Buffer::clear() +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +blargg_err_t Panning_Buffer::set_channel_count( int count ) +{ + count += 2; + if ( count != buf_count ) + { + delete [] bufs; + bufs = NULL; + + bufs = new buf_t [count]; + if ( !bufs ) + return "Out of memory"; + + buf_count = count; + + if ( sample_rate_ ) + BLARGG_RETURN_ERR( sample_rate( sample_rate_, length_ ) ); + + if ( clock_rate_ >= 0 ) + clock_rate( clock_rate_ ); + + if ( bass_freq_ >= 0 ) + bass_freq( bass_freq_ ); + + set_pan( left_chan, 1.0, 0.0 ); + set_pan( right_chan, 0.0, 1.0 ); + for ( int i = 0; i < buf_count - 2; i++ ) + set_pan( i, 1.0, 1.0 ); + } + return blargg_success; +} + +Panning_Buffer::channel_t Panning_Buffer::channel( int i ) +{ + i += 2; + require( i < buf_count ); + channel_t ch; + ch.center = &bufs [i]; + ch.left = &bufs [buf_count]; + ch.right = &bufs [buf_count]; + return ch; +} + + +void Panning_Buffer::set_pan( int i, double left, double right ) +{ + i += 2; + require( i < buf_count ); + bufs [i].left_gain = TO_FIXED( left ); + bufs [i].right_gain = TO_FIXED( right ); +} + + +void Panning_Buffer::end_frame( blip_time_t time, bool ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].end_frame( time ); +} + +long Panning_Buffer::read_samples( blip_sample_t* out, long count ) +{ + require( count % 2 == 0 ); // count must be even + + long avail = bufs [0].samples_avail() * 2; + if ( count > avail ) + count = avail; + + if ( count ) + { + memset( out, 0, count * sizeof *out ); + + int pair_count = count >> 1; + + int i; + for ( i = 0; i < buf_count; i++ ) + add_panned( bufs [i], out, pair_count ); + + for ( i = 0; i < buf_count; i++ ) + bufs [i].remove_samples( pair_count ); + } + return count; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Panning_Buffer::add_panned( buf_t& buf, blip_sample_t* out, long count ) +{ + Blip_Reader in; + + fixed_t left_gain = buf.left_gain; + fixed_t right_gain = buf.right_gain; + int bass = in.begin( buf ); + + while ( count-- ) + { + long s = in.read(); + long l = out [0] + FMUL( s, left_gain ); + long r = out [1] + FMUL( s, right_gain ); + in.next(); + + if ( (BOOST::int16_t) l != l ) + l = 0x7FFF - (l >> 24); + + out [0] = l; + out [1] = r; + out += 2; + + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + in.end( buf ); +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Panning_Buffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Panning_Buffer.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,50 @@ + +// Multi-channel buffer with pan control for each buffer + +// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. + +#ifndef PANNING_BUFFER_H +#define PANNING_BUFFER_H + +#include "Multi_Buffer.h" + +// Panning_Buffer uses several buffers and outputs stereo sample pairs. +class Panning_Buffer : public Multi_Buffer { +public: + Panning_Buffer(); + ~Panning_Buffer(); + + // Set pan of a channel, using left and right gain values (1.0 = normal). + // Use left_chan and right_chan for the common left and right buffers used + // by all channels. + enum { left_chan = -2 }; + enum { right_chan = -1 }; + void set_pan( int channel, double left, double right ); + + // See Multi_Buffer.h + blargg_err_t sample_rate( long rate, int msec ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + blargg_err_t set_channel_count( int ); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long read_samples( blip_sample_t*, long ); + +private: + typedef long fixed_t; + + struct buf_t : Blip_Buffer { + fixed_t left_gain; + fixed_t right_gain; + }; + buf_t* bufs; + int buf_count; + long clock_rate_; + int bass_freq_; + + void add_panned( buf_t&, blip_sample_t*, long ); +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Sms_Apu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Sms_Apu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,323 @@ + +// Sms_Snd_Emu 0.1.3. http://www.slack.net/~ant/libs/ + +#include "Sms_Apu.h" + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Sms_Osc + +Sms_Osc::Sms_Osc() +{ + output = NULL; + outputs [0] = NULL; // always stays NULL + outputs [1] = NULL; + outputs [2] = NULL; + outputs [3] = NULL; +} + +void Sms_Osc::reset() +{ + delay = 0; + last_amp = 0; + volume = 0; + output_select = 3; + output = outputs [3]; +} + +// Sms_Square + +Sms_Square::Sms_Square() { +} + +void Sms_Square::reset() +{ + period = 0; + phase = 0; + Sms_Osc::reset(); +} + +void Sms_Square::run( sms_time_t time, sms_time_t end_time ) +{ + if ( !volume || period <= 128 ) + { + // ignore 16kHz and higher + if ( last_amp ) { + synth->offset( time, -last_amp, output ); + last_amp = 0; + } + time += delay; + if ( !period ) { + time = end_time; + } + else if ( time < end_time ) { + // keep calculating phase + int count = (end_time - time + period - 1) / period; + phase = (phase + count) & 1; + time += count * period; + } + } + else + { + int amp = phase ? volume : -volume; + if ( amp != last_amp ) { + synth->offset( time, amp - last_amp, output ); + last_amp = amp; + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + amp *= 2; + do { + amp = -amp; // amp always alternates + synth->offset_inline( time, amp, output ); + time += period; + phase ^= 1; + } + while ( time < end_time ); + this->last_amp = phase ? volume : -volume; + } + } + delay = time - end_time; +} + +// Sms_Noise + +static const int noise_periods [3] = { 0x100, 0x200, 0x400 }; + +inline Sms_Noise::Sms_Noise() { +} + +inline void Sms_Noise::reset() +{ + period = &noise_periods [0]; + shifter = 0x8000; + tap = 12; + Sms_Osc::reset(); +} + +void Sms_Noise::run( sms_time_t time, sms_time_t end_time ) +{ + int cur_amp = 0; + int period = *this->period * 2; + if ( !volume ) { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + delay = 0; + } + else + { + int amp = (shifter & 1) ? -volume : volume; + if ( !period ) + period = 16; + if ( amp != last_amp ) { + synth.offset( time, amp - last_amp, output ); + last_amp = amp; + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + unsigned shifter = this->shifter; + amp *= 2; + + do { + int changed = (shifter + 1) & 2; + shifter = (((shifter << 15) ^ (shifter << tap)) & 0x8000) | (shifter >> 1); + if ( changed ) { // prev and next bits differ + amp = -amp; + synth.offset_inline( time, amp, output ); + } + time += period; + } + while ( time < end_time ); + + this->shifter = shifter; + this->last_amp = amp >> 1; + } + delay = time - end_time; + } +} + +// Sms_Apu + +Sms_Apu::Sms_Apu() +{ + for ( int i = 0; i < 3; i++ ) { + squares [i].synth = &square_synth; + oscs [i] = &squares [i]; + } + oscs [3] = &noise; + + volume( 1.0 ); + reset(); +} + +Sms_Apu::~Sms_Apu() { +} + +void Sms_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); +} + +void Sms_Apu::volume( double vol ) +{ + vol *= 0.85 / osc_count; + square_synth.volume( vol ); + noise.synth.volume( vol ); +} + +void Sms_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void Sms_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, + Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + + Sms_Osc& osc = *oscs [index]; + if ( center && !left && !right ) + { + // mono + left = center; + right = center; + } + else + { + // must be silenced or stereo + require( (!left && !right) || (left && right) ); + } + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; + osc.output = osc.outputs [osc.output_select]; +} + +void Sms_Apu::reset() +{ + stereo_found = false; + last_time = 0; + latch = 0; + + squares [0].reset(); + squares [1].reset(); + squares [2].reset(); + noise.reset(); +} + +void Sms_Apu::run_until( sms_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + + if ( end_time > last_time ) + { + // run oscillators + for ( int i = 0; i < osc_count; ++i ) { + Sms_Osc& osc = *oscs [i]; + if ( osc.output ) { + if ( osc.output != osc.outputs [3] ) + stereo_found = true; // playing on side output + osc.run( last_time, end_time ); + } + } + + last_time = end_time; + } +} + +bool Sms_Apu::end_frame( sms_time_t end_time ) +{ + run_until( end_time ); + last_time = 0; + + bool result = stereo_found; + stereo_found = false; + return result; +} + +void Sms_Apu::write_ggstereo( sms_time_t time, int data ) +{ + require( (unsigned) data <= 0xff ); + + run_until( time ); + + // left/right assignments + for ( int i = 0; i < osc_count; i++ ) + { + Sms_Osc& osc = *oscs [i]; + int flags = data >> i; + Blip_Buffer* old_output = osc.output; + osc.output_select = ((flags >> 3) & 2) | (flags & 1); + osc.output = osc.outputs [osc.output_select]; + if ( osc.output != old_output && osc.last_amp ) { + if ( old_output ) + square_synth.offset( time, -osc.last_amp, old_output ); + osc.last_amp = 0; + } + } +} + +static const char volumes [16] = { + // volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) + 64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0 +}; + +void Sms_Apu::write_data( sms_time_t time, int data ) +{ + require( (unsigned) data <= 0xff ); + + run_until( time ); + + if ( data & 0x80 ) + latch = data; + + int index = (latch >> 5) & 3; + if ( latch & 0x10 ) + { + // volume + oscs [index]->volume = volumes [data & 15]; + } + else if ( index < 3 ) + { + // square period + Sms_Square& sq = squares [index]; + if ( data & 0x80 ) + sq.period = (sq.period & ~0xff) | ((data << 4) & 0xff); + else + sq.period = (sq.period & 0xff) | ((data << 8) & 0x3f00); + } + else + { + // noise period/mode + int select = data & 3; + if ( select < 3 ) + noise.period = &noise_periods [select]; + else + noise.period = &squares [2].period; + + noise.tap = (data & 0x04) ? 12 : 16; // 16 disables tap + noise.shifter = 0x8000; + } +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Sms_Apu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Sms_Apu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,78 @@ + +// Sega Master System SN76489 PSG sound chip emulator + +// Sms_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef SMS_APU_H +#define SMS_APU_H + +typedef long sms_time_t; // clock cycle count + +#include "Sms_Oscs.h" + +class Sms_Apu { +public: + Sms_Apu(); + ~Sms_Apu(); + + // Overall volume of all oscillators, where 1.0 is full volume. + void volume( double ); + + // Treble equalization (see notes.txt). + void treble_eq( const blip_eq_t& ); + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silence all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign oscillator output to buffer(s). Valid indicies are 0 to + // osc_count - 1, which refer to Square 1, Square 2, Square 3, and + // Noise, respectively. If buffer is NULL, silence oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reset oscillators + void reset(); + + // Write GameGear left/right assignment byte + void write_ggstereo( sms_time_t, int ); + + // Write to data port + void write_data( sms_time_t, int ); + + // Run all oscillators up to specified time, end current frame, then + // start a new frame at time 0. Return true if any oscillators added + // sound to one of the left/right buffers, false if they only added + // to the center buffer. + bool end_frame( sms_time_t ); + + + // End of public interface +private: + // noncopyable + Sms_Apu( const Sms_Apu& ); + Sms_Apu& operator = ( const Sms_Apu& ); + + Sms_Osc* oscs [osc_count]; + Sms_Square squares [3]; + Sms_Noise noise; + Sms_Square::Synth square_synth; // shared between squares + sms_time_t last_time; + int latch; + bool stereo_found; + + void run_until( sms_time_t ); +}; + +inline void Sms_Apu::output( Blip_Buffer* mono ) { + output( mono, NULL, NULL ); +} + +inline void Sms_Apu::osc_output( int index, Blip_Buffer* mono ) { + osc_output( index, mono, NULL, NULL ); +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Sms_Oscs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Sms_Oscs.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,54 @@ + +// Private oscillators used by Sms_Apu + +// Sms_Snd_Emu 0.1.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef SMS_OSCS_H +#define SMS_OSCS_H + +#include "Blip_Buffer.h" + +struct Sms_Osc +{ + Blip_Buffer* outputs [4]; // NULL, right, left, center + Blip_Buffer* output; + int output_select; + + int delay; + int last_amp; + int volume; + + Sms_Osc(); + void reset(); + virtual void run( sms_time_t start, sms_time_t end ) = 0; +}; + +struct Sms_Square : Sms_Osc +{ + int period; + int phase; + + typedef Blip_Synth Synth; + const Synth* synth; + + Sms_Square(); + void reset(); + void run( sms_time_t, sms_time_t ); +}; + +struct Sms_Noise : Sms_Osc +{ + const int* period; + unsigned shifter; + unsigned tap; + + typedef Blip_Synth Synth; + Synth synth; + + Sms_Noise(); + void reset(); + void run( sms_time_t, sms_time_t ); +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Snes_Spc.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Snes_Spc.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,465 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Snes_Spc.h" + +#include +#include + +/* Copyright (C) 2004-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Snes_Spc::Snes_Spc() : cpu( ram, this ), dsp( ram ) +{ + timer [0].shift = 7; // 8 kHz + timer [1].shift = 7; // 8 kHz + timer [2].shift = 4; // 64 kHz + + // Put STOP instruction past end of memory to catch PC overflow. + memset( ram + ram_size, 0xff, (sizeof ram) - ram_size ); +} + +// Load + +const char* Snes_Spc::load_spc( const void* data, long size, int clear_echo_ ) +{ + struct spc_file_t { + char signature [27]; + char unused [10]; + uint8_t pc [2]; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + char unused2 [212]; + uint8_t ram [0x10000]; + uint8_t dsp [128]; + }; + BOOST_STATIC_ASSERT( sizeof (spc_file_t) == spc_file_size ); + + const spc_file_t* spc = (spc_file_t*) data; + + if ( size < spc_file_size ) + return "Not an SPC file"; + + if ( strncmp( spc->signature, "SNES-SPC700 Sound File Data", 27 ) != 0 ) + return "Not an SPC file"; + + registers_t regs; + regs.pc = spc->pc [1] * 0x100 + spc->pc [0]; + regs.a = spc->a; + regs.x = spc->x; + regs.y = spc->y; + regs.status = spc->status; + regs.sp = spc->sp; + + const char* error = load_state( regs, spc->ram, spc->dsp ); + + echo_accessed = false; + + if ( clear_echo_ ) + clear_echo(); + + return error; +} + +void Snes_Spc::clear_echo() +{ + if ( !(dsp.read( 0x6c ) & 0x20) ) + { + unsigned addr = 0x100 * dsp.read( 0x6d ); + unsigned size = 0x800 * dsp.read( 0x7d ); + unsigned limit = ram_size - addr; + memset( ram + addr, 0xff, (size < limit) ? size : limit ); + } +} + +// Handle other file formats (emulator save states) in user code, not here. + +const char* Snes_Spc::load_state( const registers_t& cpu_state, const void* new_ram, + const void* dsp_state ) +{ + // cpu + cpu.r = cpu_state; + + // Allow DSP to generate one sample before code starts + // (Tengai Makyo Zero, Tenjin's Table Toss first notes are lost since it + // clears KON 31 cycles from starting execution. It works on the SNES + // since the SPC player adds a few extra cycles delay after restoring + // KON from the DSP registers at the end of an SPC file). + extra_cycles = 32; + + // ram + memcpy( ram, new_ram, ram_size ); + memcpy( extra_ram, ram + rom_addr, sizeof extra_ram ); + + // boot rom (have to force enable_rom() to update it) + rom_enabled = !(ram [0xf1] & 0x80); + enable_rom( !rom_enabled ); + + // dsp + dsp.reset(); + int i; + for ( i = 0; i < Spc_Dsp::register_count; i++ ) + dsp.write( i, ((uint8_t*) dsp_state) [i] ); + + // timers + for ( i = 0; i < timer_count; i++ ) + { + Timer& t = timer [i]; + + t.enabled = (ram [0xf1] >> i) & 1; + t.count = 0; + t.next_tick = 0; + t.counter = ram [0xfd + i] & 15; + + int p = ram [0xfa + i]; + t.period = p ? p : 0x100; + } + + // Handle registers which already give 0 when read by setting RAM and not changing it. + // Put STOP instruction in registers which can be read, to catch attempted CPU execution. + ram [0xf0] = 0; + ram [0xf1] = 0; + ram [0xf3] = 0xff; + ram [0xfa] = 0; + ram [0xfb] = 0; + ram [0xfc] = 0; + ram [0xfd] = 0xff; + ram [0xfe] = 0xff; + ram [0xff] = 0xff; + + return NULL; // success +} + +// Hardware + +// Current time starts negative and ends at 0 +inline spc_time_t Snes_Spc::time() const +{ + return -cpu.remain(); +} + +// Keep track of next time to run and avoid a function call if it hasn't been reached. + +// Timers + +void Snes_Spc::Timer::run_until_( spc_time_t time ) +{ + assert( enabled ); // when disabled, next_tick should always be in the future + + int elapsed = ((time - next_tick) >> shift) + 1; + next_tick += elapsed << shift; + elapsed += count; + if ( elapsed >= period ) { // avoid costly divide + int n = elapsed / period; + elapsed -= n * period; + counter = (counter + n) & 15; + } + count = elapsed; +} + +// DSP + +const int clocks_per_sample = 32; // 1.024 MHz CPU clock / 32000 samples per second + +void Snes_Spc::run_dsp_( spc_time_t time ) +{ + int count = ((time - next_dsp) >> 5) + 1; // divide by clocks_per_sample + sample_t* buf = sample_buf; + if ( buf ) { + sample_buf = buf + count * 2; // stereo + assert( sample_buf <= buf_end ); + } + next_dsp += count * clocks_per_sample; + dsp.run( count, buf ); +} + +inline void Snes_Spc::run_dsp( spc_time_t time ) +{ + if ( time >= next_dsp ) + run_dsp_( time ); +} + +// Debug-only check for read/write within echo buffer, since this might result in +// inaccurate emulation due to the DSP not being caught up to the present. +inline void Snes_Spc::check_for_echo_access( spc_addr_t addr ) +{ + if ( !echo_accessed && !(dsp.read( 0x6c ) & 0x20) ) + { + // ** If echo accesses are found that require running the DSP, cache + // the start and end address on DSP writes to speed up checking. + + unsigned start = 0x100 * dsp.read( 0x6d ); + unsigned end = start + 0x800 * dsp.read( 0x7d ); + if ( start <= addr && addr < end ) { + echo_accessed = true; + dprintf( "Read/write at $%04X within echo buffer\n", (unsigned) addr ); + } + } +} + +// Read + +int Snes_Spc::read( spc_addr_t addr ) +{ + // zero page ram is used most often + if ( addr < 0xf0 ) + return ram [addr]; + + // dsp + if ( addr == 0xf3 ) { + run_dsp( time() ); + if ( ram [0xf2] >= Spc_Dsp::register_count ) + dprintf( "DSP read from $%02X\n", (int) ram [0xf2] ); + return dsp.read( ram [0xf2] & 0x7f ); + } + + // counters + unsigned i = addr - 0xfd; // negative converts to large positive unsigned + if ( i < timer_count ) { + Timer& t = timer [i]; + t.run_until( time() ); + int result = t.counter; + t.counter = 0; + return result; + } + + if ( addr == 0xf0 || addr == 0xf1 || addr == 0xf8 || + addr == 0xf9 || addr == 0xfa ) + dprintf( "Read from register $%02X\n", (int) addr ); + + // Registers which always read as 0 are handled by setting ram [reg] to 0 + // at startup then never changing that value. + + check(( check_for_echo_access( addr ), true )); + + // ram + return ram [addr]; +} + + +// Write + +const unsigned char Snes_Spc::boot_rom [rom_size] = { // verified + 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 +}; + +void Snes_Spc::enable_rom( int enable ) +{ + if ( rom_enabled != enable ) { + rom_enabled = enable; + memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size ); + } +} + +void Snes_Spc::write( spc_addr_t addr, int data ) +{ + // first page is very common + if ( addr < 0xf0 ) { + ram [addr] = data; + } + else switch ( addr ) + { + // RAM + default: + check(( check_for_echo_access( addr ), true )); + if ( addr < rom_addr ) { + ram [addr] = data; + } + else { + extra_ram [addr - rom_addr] = data; + if ( !rom_enabled ) + ram [addr] = data; + } + break; + + // DSP + //case 0xf2: // mapped to RAM + case 0xf3: { + run_dsp( time() ); + int reg = ram [0xf2]; + if ( next_dsp > 0 ) { + // skip mode + + // key press + if ( reg == 0x4C ) + keys_pressed |= data & ~dsp.read( 0x5C ); + + // key release + if ( reg == 0x5C ) { + keys_released |= data; + keys_pressed &= ~data; + } + } + if ( reg < Spc_Dsp::register_count ) { + dsp.write( reg, data ); + } + else { + dprintf( "DSP write to $%02X\n", (int) reg ); + } + break; + } + + case 0xf0: // Test register + dprintf( "Wrote $%02X to $F0\n", (int) data ); + break; + + // Config + case 0xf1: + { + // timers + for ( int i = 0; i < timer_count; i++ ) + { + Timer& t = timer [i]; + if ( !(data & (1 << i)) ) { + t.enabled = 0; + t.next_tick = 0; + } + else if ( !t.enabled ) { + // just enabled + t.enabled = 1; + t.counter = 0; + t.count = 0; + t.next_tick = time(); + } + } + + // port clears + if ( data & 0x10 ) { + ram [0xf4] = 0; + ram [0xf5] = 0; + } + if ( data & 0x20 ) { + ram [0xf6] = 0; + ram [0xf7] = 0; + } + + enable_rom( data & 0x80 ); + + break; + } + + // Ports + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + // to do: handle output ports + break; + + //case 0xf8: // verified on SNES that these are read/write (RAM) + //case 0xf9: + + // Timers + case 0xfa: + case 0xfb: + case 0xfc: { + Timer& t = timer [addr - 0xfa]; + if ( (t.period & 0xff) != data ) { + t.run_until( time() ); + t.period = data ? data : 0x100; + } + break; + } + + // Counters (cleared on write) + case 0xfd: + case 0xfe: + case 0xff: + dprintf( "Wrote to counter $%02X\n", (int) addr ); + timer [addr - 0xfd].counter = 0; + break; + } +} + +// Play + +blargg_err_t Snes_Spc::skip( long count ) +{ + if ( count > 4 * 32000L ) + { + // don't run DSP for long durations (2-3 times faster) + + const long sync_count = 32000L * 2; + + // keep track of any keys pressed/released (and not subsequently released) + keys_pressed = 0; + keys_released = 0; + // sentinel tells play to ignore DSP + BLARGG_RETURN_ERR( play( count - sync_count, skip_sentinel ) ); + + // press/release keys now + dsp.write( 0x5C, keys_released & ~keys_pressed ); + dsp.write( 0x4C, keys_pressed ); + + clear_echo(); + + // play the last few seconds normally to help synchronize DSP + count = sync_count; + } + + return play( count ); +} + +blargg_err_t Snes_Spc::play( long count, sample_t* out ) +{ + require( count % 2 == 0 ); // output is always in pairs of samples + + // CPU time() runs from -duration to 0 + spc_time_t duration = (count / 2) * clocks_per_sample; + + // DSP output is made on-the-fly when the CPU reads/writes DSP registers + sample_buf = out; + buf_end = out + (out && out != skip_sentinel ? count : 0); + next_dsp = (out == skip_sentinel) ? clocks_per_sample : -duration + clocks_per_sample; + + // Localize timer next_tick times and run them to the present to prevent a running + // but ignored timer's next_tick from getting too far behind and overflowing. + for ( int i = 0; i < timer_count; i++ ) { + Timer& t = timer [i]; + if ( t.enabled ) { + t.next_tick -= duration; + t.run_until( -duration ); + } + } + + // Run CPU for duration, reduced by any extra cycles from previous run + int elapsed = cpu.run( duration - extra_cycles ); + if ( elapsed > 0 ) + { + dprintf( "Unhandled instruction $%02X, pc = $%04X\n", + (int) cpu.read( cpu.r.pc ), (unsigned) cpu.r.pc ); + return "Emulation error"; + } + extra_cycles = -elapsed; + + // Catch DSP up to present. + run_dsp( 0 ); + if ( out ) { + assert( next_dsp == clocks_per_sample ); + assert( out == skip_sentinel || sample_buf - out == count ); + } + buf_end = NULL; + + return blargg_success; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Snes_Spc.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Snes_Spc.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,111 @@ + +// Super Nintendo (SNES) SPC-700 APU Emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. + +#ifndef SNES_SPC_H +#define SNES_SPC_H + +#include "blargg_common.h" +#include "Spc_Cpu.h" +#include "Spc_Dsp.h" + +class Snes_Spc { +public: + Snes_Spc(); + + // Load copy of SPC data into emulator. Clear echo buffer if 'clear_echo' is true. + enum { spc_file_size = 0x10180 }; + blargg_err_t load_spc( const void* spc, long spc_size, int clear_echo = 1 ); + + // Load copy of state into emulator. + typedef Spc_Cpu::registers_t registers_t; + blargg_err_t load_state( const registers_t& cpu_state, const void* ram_64k, + const void* dsp_regs_128 ); + + // Clear echo buffer + void clear_echo(); + + // Mute voice n if bit n (1 << n) of mask is set + enum { voice_count = Spc_Dsp::voice_count }; + void mute_voices( int mask ); + + // Generate 'count' samples and optionally write to 'buf'. Count must be even. + // Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first. + typedef short sample_t; + blargg_err_t play( long count, sample_t* buf = NULL ); + + // Skip forward by the specified number of samples (64000 samples = 1 second) + blargg_err_t skip( long count ); + + // Set gain, where 1.0 is normal. When greater than 1.0, output is clamped the + // 16-bit sample range. + void set_gain( double ); + + +// End of public interface +private: + typedef BOOST::uint8_t uint8_t; + + // timers + struct Timer { + spc_time_t next_tick; + int period; + int count; + int shift; + int counter; + int enabled; + + void run_until_( spc_time_t ); + void run_until( spc_time_t time ) { + if ( time >= next_tick ) + run_until_( time ); + } + }; + enum { timer_count = 3 }; + Timer timer [timer_count]; + + // hardware + Spc_Cpu cpu; + int extra_cycles; + spc_time_t time() const; + int read( spc_addr_t ); + void write( spc_addr_t, int ); + friend class Spc_Cpu; + + // boot rom + enum { rom_size = 64 }; + enum { rom_addr = 0xffc0 }; + int rom_enabled; + uint8_t extra_ram [rom_size]; + static const uint8_t boot_rom [rom_size]; + void enable_rom( int ); + + // dsp + sample_t* sample_buf; + sample_t* buf_end; // to do: remove this once possible bug resolved + spc_time_t next_dsp; + Spc_Dsp dsp; + int keys_pressed; + int keys_released; + sample_t skip_sentinel [1]; // special value for play() passed by skip() + void run_dsp( spc_time_t ); + void run_dsp_( spc_time_t ); + bool echo_accessed; + void check_for_echo_access( spc_addr_t addr ); + + // 64KB RAM + padding filled with STOP instruction to catch PC overflow. + enum { ram_size = 0x10000 }; + uint8_t ram [ram_size + 0x100]; +}; + +inline void Snes_Spc::mute_voices( int mask ) { + dsp.mute_voices( mask ); +} + +inline void Snes_Spc::set_gain( double v ) { + dsp.set_gain( v ); +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Spc_Cpu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Cpu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,1082 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Spc_Cpu.h" + +#include + +#include "blargg_endian.h" +#include "Snes_Spc.h" + +/* Copyright (C) 2004-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Several instructions are commented out (or not even implemented). These aren't +// used by the SPC files tested. + +// Optimize performance for the most common instructions, and size for the rest: +// +// 15% 0xF0 BEQ rel +// 8% 0xE4 MOV A,dp +// 4% 0xF5 MOV A,abs+X +// 4% 0xD0 BNE rel +// 4% 0x6F RET +// 4% 0x3F CALL addr +// 4% 0xF4 MOV A,dp+X +// 3% 0xC4 MOV dp,A +// 2% 0xEB MOV Y,dp +// 2% 0x3D INC X +// 2% 0xF6 MOV A,abs+Y +// (1% and below not shown) + +Spc_Cpu::Spc_Cpu( uint8_t* ram_, Snes_Spc* e ) : + ram( ram_ ), + emu( *e ) +{ + remain_ = 0; + BOOST_STATIC_ASSERT( sizeof (int) >= 4 ); +} + +#define READ( addr ) (emu.read( addr )) +#define WRITE( addr, value ) (emu.write( addr, value )) + +#define READ_DP( addr ) READ( (addr) + dp ) +#define WRITE_DP( addr, value ) WRITE( (addr) + dp, value ) + +#define READ_PROG( addr ) (ram [addr]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +int Spc_Cpu::read( spc_addr_t addr ) +{ + return READ( addr ); +} + +void Spc_Cpu::write( spc_addr_t addr, int data ) +{ + WRITE( addr, data ); +} + +// Cycle table derived from text copy of SPC-700 manual (using regular expressions) +static const unsigned char cycle_table [0x100] = { + 2,8,4,5,3,4,3,6,2,6,5,4,5,4,6,8, + 2,8,4,5,4,5,5,6,5,5,6,5,2,2,4,6, + 2,8,4,5,3,4,3,6,2,6,5,4,5,4,5,4, + 2,8,4,5,4,5,5,6,5,5,6,5,2,2,3,8, + 2,8,4,5,3,4,3,6,2,6,4,4,5,4,6,6, + 2,8,4,5,4,5,5,6,5,5,4,5,2,2,4,3, + 2,8,4,5,3,4,3,6,2,6,4,4,5,4,5,5, + 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,6, + 2,8,4,5,3,4,3,6,2,6,5,4,5,2,4,5, + 2,8,4,5,4,5,5,6,5,5,5,5,2,2,12,5, + 3,8,4,5,3,4,3,6,2,6,4,4,5,2,4,4, + 2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,4, + 3,8,4,5,4,5,4,7,2,5,6,4,5,2,4,9, + 2,8,4,5,5,6,6,7,4,5,4,5,2,2,6,3, + 2,8,4,5,3,4,3,6,2,4,5,3,4,3,4,3, + 2,8,4,5,4,5,5,6,3,4,5,4,2,2,4,3 +}; + +// The C,mem instructions are hardly used, so a non-inline function is used for +// the common access code. +unsigned Spc_Cpu::mem_bit( spc_addr_t pc ) +{ + unsigned addr = READ_PROG16( pc ); + unsigned t = READ( addr & 0x1fff ) >> (addr >> 13); + return (t << 8) & 0x100; +} + +#include BLARGG_ENABLE_OPTIMIZER + +spc_time_t Spc_Cpu::run( spc_time_t cycle_count ) +{ + remain_ = cycle_count; + +#if BLARGG_CPU_POWERPC + uint8_t* const ram = this->ram; // cache +#endif + + // Stack pointer is kept one greater than usual SPC stack pointer to allow + // common pre-decrement and post-increment memory instructions that some + // processors have. Address wrap-around isn't supported. + #define PUSH( v ) (*--sp = (v)) + #define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) + #define POP() (*sp++) + #define SET_SP( v ) (sp = ram + 0x101 + (v)) + #define GET_SP() (sp - 0x101 - ram) + + uint8_t* sp; + SET_SP( r.sp ); + + // registers + unsigned pc = r.pc; + int a = r.a; + int x = r.x; + int y = r.y; + + // status flags + + const int st_n = 0x80; + const int st_v = 0x40; + const int st_p = 0x20; + const int st_b = 0x10; + const int st_h = 0x08; + const int st_i = 0x04; + const int st_z = 0x02; + const int st_c = 0x01; + + // Special encoding for negative and zero being set simultaneously (by POP PSW). + // To do: be sure this works properly (I copied it from my NES 6502 emulator). + #define IS_NEG (int ((nz + 0x800) | (nz << (CHAR_BIT * sizeof (int) - 8))) < 0) + + #define CALC_STATUS( out ) do { \ + out = status & ~(st_n | st_z | st_c); \ + out |= (c >> 8) & st_c; \ + out |= (dp >> 3) & st_p; \ + if ( IS_NEG ) out |= st_n; \ + if ( !(uint8_t) nz ) out |= st_z; \ + } while ( 0 ) + + #define SET_STATUS( in ) do { \ + status = in & ~(st_n | st_z | st_c | st_p); \ + c = in << 8; \ + nz = in << 4; \ + nz &= 0x820; \ + nz ^= ~0xDF; \ + dp = (in << 3) & 0x100; \ + } while ( 0 ) + + uint8_t status; + int c; // store C as 'c' & 0x100. + int nz; // store Z as 'nz' & 0xFF == 0 (see above for encoding of N) + unsigned dp; // direct page base + { + int temp = r.status; + SET_STATUS( temp ); + } + + goto loop; + + unsigned data; // first operand of instruction and temporary across function calls + + // Common endings for instructions +cbranch_taken_loop: // compare and branch + data = READ_PROG( pc ); +branch_taken_loop: // taken branch (displacement already in 'data') + pc += (BOOST::int8_t) data; // sign-extend + remain_ -= 2; +inc_pc_loop: // end of instruction with an operand + pc++; +loop: + + // Be sure all registers are in range. PC and SP wrap-around isn't handled so + // those checks might fail, but a, x, and y should always be in range. + check( (unsigned) pc < 0x10000 ); + check( (unsigned) GET_SP() < 0x100 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + // Read opcode and first operand. Optimize if processor's byte order is known + // and non-portable constructs are allowed. +#if BLARGG_NONPORTABLE && BLARGG_BIG_ENDIAN + data = *(BOOST::uint16_t*) &READ_PROG( pc ); + pc++; + unsigned opcode = data >> 8; + data = (uint8_t) data; + +#elif BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN + data = *(BOOST::uint16_t*) &READ_PROG( pc ); + pc++; + unsigned opcode = (uint8_t) data; + data >>= 8; + +#else + unsigned opcode = READ_PROG( pc ); + pc++; + data = READ_PROG( pc ); + +#endif + + if ( remain_ <= 0 ) + goto stop; + + remain_ -= cycle_table [opcode]; + + // Use 'data' for temporaries whose lifetime crosses read/write calls, otherwise + // use a local temporary. + switch ( opcode ) + { + + #define BRANCH( cond ) \ + if ( cond ) \ + goto branch_taken_loop; \ + goto inc_pc_loop; + +// Most-Common + + case 0xF0: // BEQ (most common) + BRANCH( !(uint8_t) nz ) + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ) + + case 0x3F: // CALL + PUSH16( pc + 2 ); + pc = READ_PROG16( pc ); + goto loop; + + case 0x6F: // RET + pc = POP(); + pc += POP() * 0x100; + goto loop; + +#define CASE( n ) case n: + +// Define common address modes based on opcode for immediate mode. Execution +// ends with data set to the address of the operand. +#define ADDR_MODES( op ) \ + CASE( op - 0x02 ) /* (X) */ \ + data = x + dp; \ + pc--; \ + goto end_##op; \ + CASE( op + 0x0F ) /* (dp)+Y */ \ + data = READ_PROG16( data + dp ) + y;\ + goto end_##op; \ + CASE( op - 0x01 ) /* (dp+X) */ \ + data = READ_PROG16( uint8_t (data + x) + dp );\ + goto end_##op; \ + CASE( op + 0x0E ) /* abs+Y */ \ + data += y; \ + goto abs_##op; \ + CASE( op + 0x0D ) /* abs+X */ \ + data += x; \ + CASE( op - 0x03 ) /* abs */ \ + abs_##op: \ + pc++; \ + data += 0x100 * READ_PROG( pc );\ + goto end_##op; \ + CASE( op + 0x0C ) /* dp+X */ \ + data = uint8_t (data + x); \ + CASE( op - 0x04 ) /* dp */ \ + data += dp; \ + end_##op: + +// 1. 8-bit Data Transmission Commands. Group I + + ADDR_MODES( 0xE8 ) // MOV A,addr + // case 0xE4: // MOV a,dp (most common) + mov_a_addr: + a = nz = READ( data ); + goto inc_pc_loop; + case 0xBF: // MOV A,(X)+ + data = x + dp; + x = uint8_t (x + 1); + pc--; + goto mov_a_addr; + + case 0xE8: // MOV A,imm + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: // MOV X,dp+Y + data = uint8_t (data + y); + case 0xF8: // MOV X,dp + data += dp; + goto mov_x_addr; + case 0xE9: // MOV X,abs + data = READ_PROG16( pc ); + pc++; + mov_x_addr: + data = READ( data ); + case 0xCD: // MOV X,imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: // MOV Y,dp+X + data = uint8_t (data + x); + case 0xEB: // MOV Y,dp + data += dp; + goto mov_y_addr; + case 0xEC: // MOV Y,abs + data = READ_PROG16( pc ); + pc++; + mov_y_addr: + data = READ( data ); + case 0x8D: // MOV Y,imm + y = data; + nz = data; + goto inc_pc_loop; + +// 2. 8-BIT DATA TRANSMISSION COMMANDS. GROUP 2. + + ADDR_MODES( 0xC8 ) // MOV addr,A + WRITE( data, a ); + goto inc_pc_loop; + + { + int temp; + case 0xCC: // MOV abs,Y + temp = y; + goto mov_abs_temp; + case 0xC9: // MOV abs,X + temp = x; + mov_abs_temp: + WRITE( READ_PROG16( pc ), temp ); + pc += 2; + goto loop; + } + + case 0xD9: // MOV dp+Y,X + data = uint8_t (data + y); + case 0xD8: // MOV dp,X + WRITE( data + dp, x ); + goto inc_pc_loop; + + case 0xDB: // MOV dp+X,Y + data = uint8_t (data + x); + case 0xCB: // MOV dp,Y + WRITE( data + dp, y ); + goto inc_pc_loop; + + case 0xFA: // MOV dp,dp + data = READ( data + dp ); + case 0x8F: // MOV dp,#imm + pc++; + WRITE_DP( READ_PROG( pc ), data ); + goto inc_pc_loop; + +// 3. 8-BIT DATA TRANSMISSIN COMMANDS, GROUP 3. + + case 0x7D: // MOV A,X + a = x; + nz = x; + goto loop; + + case 0xDD: // MOV A,Y + a = y; + nz = y; + goto loop; + + case 0x5D: // MOV X,A + x = a; + nz = a; + goto loop; + + case 0xFD: // MOV Y,A + y = a; + nz = a; + goto loop; + + case 0x9D: // MOV X,SP + x = nz = GET_SP(); + goto loop; + + case 0xBD: // MOV SP,X + SET_SP( x ); + goto loop; + + //case 0xC6: // MOV (X),A (handled by MOV addr,A in group 2) + + case 0xAF: // MOV (X)+,A + WRITE_DP( x, a ); + x++; + goto loop; + +// 5. 8-BIT LOGIC OPERATION COMMANDS. + +#define LOGICAL_OP( op, func ) \ + ADDR_MODES( op ) /* addr */ \ + data = READ( data ); \ + case op: /* imm */ \ + nz = a func##= data; \ + goto inc_pc_loop; \ + { unsigned addr; \ + case op + 0x11: /* X,Y */ \ + data = READ_DP( y ); \ + addr = x + dp; \ + pc--; \ + goto addr_##op; \ + case op + 0x01: /* dp,dp */ \ + data = READ_DP( data ); \ + case op + 0x10: /*dp,imm*/\ + pc++; \ + addr = READ_PROG( pc ) + dp;\ + addr_##op: \ + nz = data func READ( addr );\ + WRITE( addr, nz ); \ + goto inc_pc_loop; \ + } + + LOGICAL_OP( 0x28, & ); // AND + + LOGICAL_OP( 0x08, | ); // OR + + LOGICAL_OP( 0x48, ^ ); // EOR + +// 4. 8-BIT ARITHMETIC OPERATION COMMANDS. + + ADDR_MODES( 0x68 ) // CMP addr + data = READ( data ); + case 0x68: // CMP imm + nz = a - data; + c = ~nz; + goto inc_pc_loop; + + case 0x79: // CMP (X),(Y) + data = READ_DP( x ); + nz = data - READ_DP( y ); + c = ~nz; + goto loop; + + case 0x69: // CMP (dp),(dp) + data = READ_DP( data ); + case 0x78: // CMP dp,imm + pc++; + nz = READ_DP( READ_PROG( pc ) ) - data; + c = ~nz; + goto inc_pc_loop; + + case 0x3E: // CMP X,dp + data += dp; + goto cmp_x_addr; + case 0x1E: // CMP X,abs + data = READ_PROG16( pc ); + pc++; + cmp_x_addr: + data = READ( data ); + case 0xC8: // CMP X,imm + nz = x - data; + c = ~nz; + goto inc_pc_loop; + + case 0x7E: // CMP Y,dp + data += dp; + goto cmp_y_addr; + case 0x5E: // CMP Y,abs + data = READ_PROG16( pc ); + pc++; + cmp_y_addr: + data = READ( data ); + case 0xAD: // CMP Y,imm + nz = y - data; + c = ~nz; + goto inc_pc_loop; + + { + int addr; + case 0xB9: // SBC (x),(y) + case 0x99: // ADC (x),(y) + pc--; // compensate for inc later + data = READ_DP( x ); + addr = y + dp; + goto adc_addr; + case 0xA9: // SBC dp,dp + case 0x89: // ADC dp,dp + data = READ_DP( data ); + case 0xB8: // SBC dp,imm + case 0x98: // ADC dp,imm + pc++; + addr = READ_PROG( pc ) + dp; + adc_addr: + nz = READ( addr ); + goto adc_data; + +// catch ADC and SBC together, then decode later based on operand +#undef CASE +#define CASE( n ) case n: case (n) + 0x20: + ADDR_MODES( 0x88 ) // ADC/SBC addr + data = READ( data ); + case 0xA8: // SBC imm + case 0x88: // ADC imm + addr = -1; // A + nz = a; + adc_data: { + if ( opcode & 0x20 ) + data ^= 0xff; // SBC + int carry = (c >> 8) & 1; + int ov = (nz ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + int hc = (nz & 15) + carry; + c = nz += data + carry; + hc = (nz & 15) - hc; + status = (status & ~(st_v | st_h)) | ((ov >> 2) & st_v) | ((hc >> 1) & st_h); + if ( addr < 0 ) { + a = (uint8_t) nz; + goto inc_pc_loop; + } + WRITE( addr, (uint8_t) nz ); + goto inc_pc_loop; + } + + } + +// 6. ADDITION & SUBTRACTION COMMANDS. + +#define INC_DEC_REG( reg, n ) \ + nz = reg + n; \ + reg = (uint8_t) nz; \ + goto loop; + + case 0xBC: INC_DEC_REG( a, 1 ) // INC A + case 0x3D: INC_DEC_REG( x, 1 ) // INC X + case 0xFC: INC_DEC_REG( y, 1 ) // INC Y + + case 0x9C: INC_DEC_REG( a, -1 ) // DEC A + case 0x1D: INC_DEC_REG( x, -1 ) // DEC X + case 0xDC: INC_DEC_REG( y, -1 ) // DEC Y + + case 0x9B: // DEC dp+X + case 0xBB: // INC dp+X + data = uint8_t (data + x); + case 0x8B: // DEC dp + case 0xAB: // INC dp + data += dp; + goto inc_abs; + case 0x8C: // DEC abs + case 0xAC: // INC abs + data = READ_PROG16( pc ); + pc++; + inc_abs: + nz = ((opcode >> 4) & 2) - 1; + nz += READ( data ); + WRITE( data, (uint8_t) nz ); + goto inc_pc_loop; + +// 7. SHIFT, ROTATION COMMANDS + + case 0x5C: // LSR A + c = 0; + case 0x7C:{// ROR A + nz = ((c >> 1) & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto loop; + } + + case 0x1C: // ASL A + c = 0; + case 0x3C:{// ROL A + int temp = (c >> 8) & 1; + c = a << 1; + nz = c | temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x0B: // ASL dp + c = 0; + data += dp; + goto rol_mem; + case 0x1B: // ASL dp+X + c = 0; + case 0x3B: // ROL dp+X + data = uint8_t (data + x); + case 0x2B: // ROL dp + data += dp; + goto rol_mem; + case 0x0C: // ASL abs + c = 0; + case 0x2C: // ROL abs + data = READ_PROG16( pc ); + pc++; + rol_mem: + nz = (c >> 8) & 1; + nz |= (c = READ( data ) << 1); + WRITE( data, (uint8_t) nz ); + goto inc_pc_loop; + + case 0x4B: // LSR dp + c = 0; + data += dp; + goto ror_mem; + case 0x5B: // LSR dp+X + c = 0; + case 0x7B: // ROR dp+X + data = uint8_t (data + x); + case 0x6B: // ROR dp + data += dp; + goto ror_mem; + case 0x4C: // LSR abs + c = 0; + case 0x6C: // ROR abs + data = READ_PROG16( pc ); + pc++; + ror_mem: { + int temp = READ( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + WRITE( data, nz ); + goto inc_pc_loop; + } + + case 0x9F: // XCN + nz = a = (a >> 4) | uint8_t (a << 4); + goto loop; + +// 8. 16-BIT TRANSMISION COMMANDS + + case 0xBA: // MOVW YA,dp + a = READ_DP( data ); + nz = (a & 0x7f) | (a >> 1); + y = READ_DP( uint8_t (data + 1) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: // MOVW dp,YA + WRITE_DP( data, a ); + WRITE_DP( uint8_t (data + 1), y ); + goto inc_pc_loop; + +// 9. 16-BIT OPERATION COMMANDS. + + case 0x3A: // INCW dp + case 0x1A:{// DECW dp + data += dp; + + // low byte + int temp = READ( data ); + temp += ((opcode >> 4) & 2) - 1; // +1 for INCW, -1 for DECW + nz = ((temp >> 1) | temp) & 0x7f; + WRITE( data, (uint8_t) temp ); + + // high byte + data = uint8_t (data + 1) + dp; + temp >>= 8; + temp = uint8_t (temp + READ( data )); + nz |= temp; + WRITE( data, temp ); + + goto inc_pc_loop; + } + + case 0x9A: // SUBW YA,dp + case 0x7A: // ADDW YA,dp + { + // read 16-bit addend + int temp = READ_DP( data ); + int sign = READ_DP( uint8_t (data + 1) ); + temp += 0x100 * sign; + status &= ~(st_v | st_h); + + // to do: fix half-carry for SUBW (it's probably wrong) + + // for SUBW, negate and truncate to 16 bits + if ( opcode & 0x80 ) { + temp = (temp ^ 0xFFFF) + 1; + sign = temp >> 8; + } + + // add low byte (A) + temp += a; + a = (uint8_t) temp; + nz = (temp | (temp >> 1)) & 0x7f; + + // add high byte (Y) + temp >>= 8; + c = y + temp; + nz |= c; + + // half-carry (temporary avoids CodeWarrior optimizer bug) + unsigned hc = (c & 15) - (y & 15); + status |= (hc >> 4) & st_h; + + // overflow if sign of YA changed when previous sign and addend sign were same + status |= (((c ^ y) & ~(y ^ sign)) >> 1) & st_v; + + y = (uint8_t) c; + + goto inc_pc_loop; + } + + case 0x5A: { // CMPW YA,dp + int temp = a - READ_DP( data ); + nz = ((temp >> 1) | temp) & 0x7f; + temp = y + (temp >> 8); + temp -= READ_DP( uint8_t (data + 1) ); + nz |= temp; + c = ~temp; + goto inc_pc_loop; + } + +// 10. MULTIPLICATION & DIVISON COMMANDS. + + case 0xCF: { // MUL YA + unsigned temp = y * a; + a = (uint8_t) temp; + nz = ((temp >> 1) | temp) & 0x7f; + y = temp >> 8; + nz |= y; + goto loop; + } + + case 0x9E: // DIV YA,X + { + // behavior based on SPC CPU tests + + status &= ~(st_h | st_v); + + if ( y >= x ) + status |= st_v; + + if ( (y & 15) >= (x & 15) ) + status |= st_h; + + unsigned temp = y * 0x100 + a; + if ( y < x * 2 ) { + a = temp / x; + y = temp - a * x; + } + else { + temp -= x * 0x200; + a = temp / (256 - x); + y = temp - a * (256 - x) + x; + a = 255 - a; + } + + nz = (uint8_t) a; + a = (uint8_t) a; + + goto loop; + } + +// 11. DECIMAL COMPENSATION COMMANDS. + + // seem unused + // case 0xDF: // DAA + // case 0xBE: // DAS + +// 12. BRANCHING COMMANDS. + + case 0x2F: // BRA rel + goto branch_taken_loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x03: // BBS dp.bit,rel + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + pc++; + if ( (READ_DP( data ) >> (opcode >> 5)) & 1 ) + goto cbranch_taken_loop; + goto inc_pc_loop; + + case 0x13: // BBC dp.bit,rel + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + pc++; + if ( !((READ_DP( data ) >> (opcode >> 5)) & 1) ) + goto cbranch_taken_loop; + goto inc_pc_loop; + + case 0xDE: // CBNE dp+X,rel + data = uint8_t (data + x); + // fall through + case 0x2E: // CBNE dp,rel + pc++; + if ( READ_DP( data ) != a ) + goto cbranch_taken_loop; + goto inc_pc_loop; + + case 0xFE: // DBNZ Y,rel + y = uint8_t (y - 1); + if ( y ) + goto branch_taken_loop; + goto inc_pc_loop; + + case 0x6E: { // DBNZ dp,rel + pc++; + unsigned temp = READ_DP( data ) - 1; + WRITE_DP( (uint8_t) data, (uint8_t) temp ); + if ( temp ) + goto cbranch_taken_loop; + goto inc_pc_loop; + } + + case 0x1F: // JMP (abs+X) + pc = READ_PROG16( pc ) + x; + // fall through + case 0x5F: // JMP abs + pc = READ_PROG16( pc ); + goto loop; + +// 13. SUB-ROUTINE CALL RETURN COMMANDS. + + /* + // seems unused + case 0x0F: // BRK + PUSH16( pc + 1 ); + pc = READ_PROG16( 0xffde ); // vector address verified + int temp; + CALC_STATUS( temp ); + PUSH( temp ); + status = (status | st_b) & ~st_i; + goto loop; + */ + + case 0x4F: // PCALL offset + pc++; + PUSH16( pc ); + pc = 0xff00 + data; + goto loop; + + case 0x01: // TCALL n + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xA1: + case 0xB1: + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: + PUSH16( pc ); + pc = READ_PROG16( 0xffde - (opcode >> 3) ); + goto loop; + +// 14. STACK OPERATION COMMANDS. + + { + int temp; + case 0x7F: // RET1 + temp = POP(); + pc = POP(); + pc |= POP() << 8; + goto set_status; + case 0x8E: // POP PSW + temp = POP(); + set_status: + SET_STATUS( temp ); + goto loop; + } + + case 0x0D: { // PUSH PSW + int temp; + CALC_STATUS( temp ); + PUSH( temp ); + goto loop; + } + + case 0x2D: // PUSH A + PUSH( a ); + goto loop; + + case 0x4D: // PUSH X + PUSH( x ); + goto loop; + + case 0x6D: // PUSH Y + PUSH( y ); + goto loop; + + case 0xAE: // POP A + a = POP(); + goto loop; + + case 0xCE: // POP X + x = POP(); + goto loop; + + case 0xEE: // POP Y + y = POP(); + goto loop; + +// 15. BIT OPERATION COMMANDS. + + case 0x02: // SET1 + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xA2: + case 0xC2: + case 0xE2: + case 0x12: // CLR1 + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xB2: + case 0xD2: + case 0xF2: { + data += dp; + int bit = 1 << (opcode >> 5); + int mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + WRITE( data, (READ( data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: // TSET1 abs + case 0x4E:{// TCLR1 abs + data = READ_PROG16( pc ); + pc += 2; + unsigned temp = READ( data ); + nz = temp & a; + temp &= ~a; + if ( !(opcode & 0x40) ) + temp |= a; + WRITE( data, temp ); + goto loop; + } + + case 0x4A: // AND1 C,mem.bit + c &= mem_bit( pc ); + pc += 2; + goto loop; + /* + // seem unused + case 0x6A: // AND1 C,/mem.bit + c &= ~mem_bit( pc ); + pc += 2; + goto loop; + + case 0x0A: // OR1 C,mem.bit + c |= mem_bit( pc ); + pc += 2; + goto loop; + + case 0x2A: // OR1 C,/mem.bit + c |= ~mem_bit( pc ); + pc += 2; + goto loop; + */ + + case 0x8A: // EOR1 C,mem.bit + c ^= mem_bit( pc ); + pc += 2; + goto loop; + + case 0xEA: { // NOT1 C,mem.bit + data = READ_PROG16( pc ); + pc += 2; + unsigned temp = READ( data & 0x1fff ); + temp ^= 1 << (data >> 13); + WRITE( data & 0x1fff, temp ); + goto loop; + } + + case 0xCA: { // MOV1 mem.bit,C + data = READ_PROG16( pc ); + pc += 2; + unsigned temp = READ( data & 0x1fff ); + unsigned bit = data >> 13; + temp = (temp & ~(1 << bit)) | ((c >> (8 - bit)) & 1); + WRITE( data & 0x1fff, temp ); + goto loop; + } + + case 0xAA: // MOV1 C,mem.bit + c = mem_bit( pc ); + pc += 2; + goto loop; + +// 16. PROGRAM STATUS FLAG OPERATION COMMANDS. + + case 0x60: // CLRC + c = 0; + goto loop; + + case 0x80: // SETC + c = ~0; + goto loop; + + case 0xED: // NOTC + c ^= 0x100; + goto loop; + + case 0xE0: // CLRV + status &= ~(st_v | st_h); + goto loop; + + case 0x20: // CLRP + dp = 0; + goto loop; + + case 0x40: // SETP + dp = 0x100; + goto loop; + + /* // seem unused + case 0xA0: // EI + status |= st_i; + goto loop; + + case 0xC0: // DI + status &= ~st_i; + goto loop; + */ + +// 17. OTHER COMMANDS. + + case 0x00: // NOP + goto loop; + + //case 0xEF: // SLEEP + //case 0xFF: // STOP + + } // switch + + // unhandled instructions fall out of switch so emulator can catch them + +stop: + pc--; + + { + int temp; + CALC_STATUS( temp ); + r.status = temp; + } + + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + + return remain_; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Spc_Cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Cpu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,61 @@ + +// Super Nintendo (SNES) SPC-700 CPU emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. + +#ifndef SPC_CPU_H +#define SPC_CPU_H + +#include "blargg_common.h" + +typedef unsigned spc_addr_t; +typedef long spc_time_t; + +class Snes_Spc; + +class Spc_Cpu { + typedef BOOST::uint8_t uint8_t; + uint8_t* const ram; + spc_time_t remain_; + Snes_Spc& emu; +public: + // Keeps pointer to ram and spc + Spc_Cpu( uint8_t ram [0x10000], Snes_Spc* spc ); + + // SPC-700 registers. *Not* kept updated during a call to run(). + struct registers_t { + unsigned short pc; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t status; + uint8_t sp; + } r; + + // Run CPU for at least 'count' cycles. Return the number of cycles remaining + // when emulation stopped (negative if extra cycles were emulated). Emulation + // stops when there are no more remaining cycles or an unhandled instruction + // is encountered (STOP, SLEEP, and any others not yet implemented). In the + // latter case, the return value is greater than zero. + spc_time_t run( spc_time_t count ); + + // Number of clock cycles remaining for current run() call + spc_time_t remain() const; + + // Access memory as the emulated CPU does + int read ( spc_addr_t ); + void write( spc_addr_t, int ); + +private: + // noncopyable + Spc_Cpu( const Spc_Cpu& ); + Spc_Cpu& operator = ( const Spc_Cpu& ); + unsigned mem_bit( spc_addr_t ); +}; + +inline spc_time_t Spc_Cpu::remain() const { + return remain_; +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Spc_Dsp.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Dsp.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,644 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +// Based on Brad Martin's OpenSPC DSP emulator. + +#include "Spc_Dsp.h" + +#include + +#include "blargg_endian.h" + +/* Copyright (C) 2002 Brad Martin */ +/* Copyright (C) 2004-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Spc_Dsp::Spc_Dsp( uint8_t* ram_ ) : ram( ram_ ) +{ + voices_muted = 0; + set_gain( 1.0 ); + + BOOST_STATIC_ASSERT( sizeof (g) == register_count && sizeof (voice) == register_count ); +} + +void Spc_Dsp::reset() +{ + keys = 0; + echo_ptr = 0; + noise_count = 0; + noise = 1; + fir_offset = 0; + + g.flags = 0xE0; // reset, mute, echo off + g.key_ons = 0; + + for ( int i = 0; i < voice_count; i++ ) { + voice_state [i].on_cnt = 0; + voice_state [i].envstate = state_release; + } + + memset( fir_buf, 0, sizeof fir_buf ); + memset( voice_vol, 0, sizeof voice_vol ); +} + +void Spc_Dsp::write( int i, int data ) +{ + require( (unsigned) i < register_count ); + + reg [i] = data; + int high = i >> 4; + switch ( i & 0x0f ) + { + // voice volume + case 0: + case 1: { + int left = (int8_t) reg [i & ~1]; + int right = (int8_t) reg [i | 1]; + voice_vol [high] [0] = left; + voice_vol [high] [1] = right; + // kill surround only if signs of volumes differ + if ( left * right < 0 ) + { + if ( left < 0 ) + voice_vol [high] [0] = -left; + else + voice_vol [high] [1] = -right; + } + break; + } + + // fir coefficients + case 0x0f: + fir_coeff [high] = (int8_t) data; // sign-extend + break; + } +} + +// This table is for envelope timing. It represents the number of counts +// that should be subtracted from the counter each sample period (32kHz). +// The counter starts at 30720 (0x7800). Each count divides exactly into +// 0x7800 without remainder. +const int env_rate_init = 0x7800; +static const short env_rates [0x20] = { + 0x0000, 0x000F, 0x0014, 0x0018, 0x001E, 0x0028, 0x0030, 0x003C, + 0x0050, 0x0060, 0x0078, 0x00A0, 0x00C0, 0x00F0, 0x0140, 0x0180, + 0x01E0, 0x0280, 0x0300, 0x03C0, 0x0500, 0x0600, 0x0780, 0x0A00, + 0x0C00, 0x0F00, 0x1400, 0x1800, 0x1E00, 0x2800, 0x3C00, 0x7800 +}; + +const int env_range = 0x800; + +int Spc_Dsp::clock_envelope( int v ) +{ /* Return value is current + * ENVX */ + raw_voice_t& raw_voice = this->voice [v]; + voice_t& voice = voice_state [v]; + + int envx = voice.envx; + if ( voice.envstate == state_release ) + { + /* + * Docs: "When in the state of "key off". the "click" sound is + * prevented by the addition of the fixed value 1/256" WTF??? + * Alright, I'm going to choose to interpret that this way: + * When a note is keyed off, start the RELEASE state, which + * subtracts 1/256th each sample period (32kHz). Note there's + * no need for a count because it always happens every update. + */ + envx -= env_range / 256; + if ( envx <= 0 ) { + envx = 0; + keys &= ~(1 << v); + return -1; + } + voice.envx = envx; + raw_voice.envx = envx >> 8; + return envx; + } + + int cnt = voice.envcnt; + int adsr1 = raw_voice.adsr [0]; + if ( adsr1 & 0x80 ) + { + switch ( voice.envstate ) + { + case state_attack: { + // increase envelope by 1/64 each step + int t = adsr1 & 15; + if ( t == 15 ) { + envx += env_range / 2; + } + else { + cnt -= env_rates [t * 2 + 1]; + if ( cnt > 0 ) + break; + envx += env_range / 64; + cnt = env_rate_init; + } + if ( envx >= env_range ) { + envx = env_range - 1; + voice.envstate = state_decay; + } + voice.envx = envx; + break; + } + + case state_decay: { + // Docs: "DR... [is multiplied] by the fixed value + // 1-1/256." Well, at least that makes some sense. + // Multiplying ENVX by 255/256 every time DECAY is + // updated. + cnt -= env_rates [((adsr1 >> 3) & 0xE) + 0x10]; + if ( cnt <= 0 ) { + cnt = env_rate_init; + envx -= ((envx - 1) >> 8) + 1; + voice.envx = envx; + } + int sustain_level = raw_voice.adsr [1] >> 5; + + if ( envx <= (sustain_level + 1) * 0x100 ) + voice.envstate = state_sustain; + break; + } + + case state_sustain: + // Docs: "SR [is multiplied] by the fixed value 1-1/256." + // Multiplying ENVX by 255/256 every time SUSTAIN is + // updated. + cnt -= env_rates [raw_voice.adsr [1] & 0x1f]; + if ( cnt <= 0 ) { + cnt = env_rate_init; + envx -= ((envx - 1) >> 8) + 1; + voice.envx = envx; + } + break; + } + } + else + { /* GAIN mode is set */ + /* + * Note: if the game switches between ADSR and GAIN modes + * partway through, should the count be reset, or should it + * continue from where it was? Does the DSP actually watch for + * that bit to change, or does it just go along with whatever + * it sees when it performs the update? I'm going to assume + * the latter and not update the count, unless I see a game + * that obviously wants the other behavior. The effect would + * be pretty subtle, in any case. + */ + int t = raw_voice.gain; + if (t < 0x80) + { + envx = voice.envx = t << 4; + } + else switch (t >> 5) + { + case 4: /* Docs: "Decrease (linear): Subtraction + * of the fixed value 1/64." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + envx -= env_range / 64; + if ( envx < 0 ) { + envx = 0; + if ( voice.envstate == state_attack ) + voice.envstate = state_decay; + } + voice.envx = envx; + break; + case 5: /* Docs: "Drecrease (exponential): + * Multiplication by the fixed value + * 1-1/256." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + envx -= ((envx - 1) >> 8) + 1; + if ( envx < 0 ) { + envx = 0; + if ( voice.envstate == state_attack ) + voice.envstate = state_decay; + } + voice.envx = envx; + break; + case 6: /* Docs: "Increase (linear): Addition of + * the fixed value 1/64." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + envx += env_range / 64; + if ( envx >= env_range ) + envx = env_range - 1; + voice.envx = envx; + break; + case 7: /* Docs: "Increase (bent line): Addition + * of the constant 1/64 up to .75 of the + * constaint 1/256 from .75 to 1." */ + cnt -= env_rates [t & 0x1F]; + if (cnt > 0) + break; + cnt = env_rate_init; + if ( envx < env_range * 3 / 4 ) + envx += env_range / 64; + else + envx += env_range / 256; + if ( envx >= env_range ) + envx = env_range - 1; + voice.envx = envx; + break; + } + } + voice.envcnt = cnt; + raw_voice.envx = envx >> 4; + return envx; +} + +// Clamp n into range -32768 <= n <= 32767 +inline int clamp_16( int n ) +{ + if ( (BOOST::int16_t) n != n ) + n = BOOST::int16_t (0x7FFF - (n >> 31)); + return n; +} + +void Spc_Dsp::run( long count, short* out_buf ) +{ + // to do: make clock_envelope() inline to avoid out-of-line calls? + + // Should we just fill the buffer with silence? Flags won't be cleared + // during this run so it seems it should keep resetting every sample. + if ( g.flags & 0x80 ) + reset(); + + struct src_dir { + char start [2]; + char loop [2]; + }; + + const src_dir* const sd = (src_dir*) &ram [g.wave_page * 0x100]; + + int const left_volume = g.left_volume * emu_gain; + int right_volume = g.right_volume * emu_gain; + if ( left_volume * right_volume < 0 ) + right_volume = -right_volume; // kill global surround + + while ( --count >= 0 ) + { + // Here we check for keys on/off. Docs say that successive writes + // to KON/KOF must be separated by at least 2 Ts periods or risk + // being neglected. Therefore DSP only looks at these during an + // update, and not at the time of the write. Only need to do this + // once however, since the regs haven't changed over the whole + // period we need to catch up with. + + g.wave_ended &= ~g.key_ons; // Keying on a voice resets that bit in ENDX. + + if ( g.noise_enables ) { + noise_count -= env_rates [g.flags & 0x1F]; + if ( noise_count <= 0 ) { + noise_count = env_rate_init; + + if ( noise & 0x4000 ) + noise_amp += (noise & 1) ? noise : -noise; + else + noise_amp >>= 1; + + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + + // What is the expected behavior when pitch modulation is enabled on + // voice 0? Jurassic Park 2 does this. Assume 0 for now. + long prev_outx = 0; + + int echol = 0; + int echor = 0; + int left = 0; + int right = 0; + for ( int vidx = 0; vidx < voice_count; vidx++ ) + { + const int vbit = 1 << vidx; + raw_voice_t& raw_voice = voice [vidx]; + voice_t& voice = voice_state [vidx]; + + if ( voice.on_cnt && !--voice.on_cnt ) + { + // key on + keys |= vbit; + voice.addr = GET_LE16( sd [raw_voice.waveform].start ); + voice.block_remain = 1; + voice.envx = 0; + voice.block_header = 0; + voice.fraction = 0x3fff; // decode three samples immediately + voice.interp0 = 0; // BRR decoder filter uses previous two samples + voice.interp1 = 0; + + // NOTE: Real SNES does *not* appear to initialize the + // envelope counter to anything in particular. The first + // cycle always seems to come at a random time sooner than + // expected; as yet, I have been unable to find any + // pattern. I doubt it will matter though, so we'll go + // ahead and do the full time for now. + voice.envcnt = env_rate_init; + voice.envstate = state_attack; + } + + if ( g.key_ons & vbit & ~g.key_offs ) { + // voice doesn't come on if key off is set + g.key_ons &= ~vbit; + voice.on_cnt = 8; + } + + if ( keys & g.key_offs & vbit ) { + // key off + voice.envstate = state_release; + voice.on_cnt = 0; + } + + int envx; + if ( !(keys & vbit) || (envx = clock_envelope( vidx )) < 0 ) { + raw_voice.envx = 0; + raw_voice.outx = 0; + prev_outx = 0; + continue; + } + + // Decode samples when fraction >= 1.0 (0x1000) + for ( int n = voice.fraction >> 12; --n >= 0; ) + { + if ( !--voice.block_remain ) + { + if ( voice.block_header & 1 ) + { + g.wave_ended |= vbit; + + if ( voice.block_header & 2 ) { + // verified (played endless looping sample and ENDX was set) + voice.addr = GET_LE16( sd [raw_voice.waveform].loop ); + } + else { + // first block was end block; don't play anything (verified) + goto sample_ended; // to do: find alternative to goto + } + } + + voice.block_header = ram [voice.addr++]; + voice.block_remain = 16; // nybbles + } + + // if next block has end flag set, *this* block ends *early* (verified) + if ( voice.block_remain == 9 && (ram [voice.addr + 5] & 3) == 1 && + (voice.block_header & 3) != 3 ) + { + sample_ended: + g.wave_ended |= vbit; + keys &= ~vbit; + raw_voice.envx = 0; + voice.envx = 0; + // add silence samples to interpolation buffer + do { + voice.interp3 = voice.interp2; + voice.interp2 = voice.interp1; + voice.interp1 = voice.interp0; + voice.interp0 = 0; + } + while ( --n >= 0 ); + break; + } + + int delta = ram [voice.addr]; + if ( voice.block_remain & 1 ) { + delta <<= 4; // use lower nybble + voice.addr++; + } + + // Use sign-extended upper nybble + delta = int8_t (delta) >> 4; + + // For invalid ranges (D,E,F): if the nybble is negative, + // the result is F000. If positive, 0000. Nothing else + // like previous range, etc seems to have any effect. If + // range is valid, do the shift normally. Note these are + // both shifted right once to do the filters properly, but + // the output will be shifted back again at the end. + int shift = voice.block_header >> 4; + delta = (delta << shift) >> 1; + if ( shift > 0x0C ) + delta = (delta >> 14) & ~0x7FF; + + // One, two and three point IIR filters + int smp1 = voice.interp0; + int smp2 = voice.interp1; + switch ( (voice.block_header >> 2) & 3 ) + { + case 0: + break; + + case 1: + delta += smp1 >> 1; + delta += (-smp1) >> 5; + break; + + case 2: + delta += smp1; + delta += (-(smp1 + (smp1 >> 1))) >> 5; + delta -= smp2 >> 1; + delta += smp2 >> 5; + break; + + case 3: + delta += smp1; + delta += (-(smp1 + (smp1 << 2) + (smp1 << 3))) >> 7; + delta -= smp2 >> 1; + delta += (smp2 + (smp2 >> 1)) >> 4; + break; + } + + voice.interp3 = voice.interp2; + voice.interp2 = smp2; + voice.interp1 = smp1; + voice.interp0 = BOOST::int16_t (clamp_16( delta ) * 2); // sign-extend + } + + // rate (with possible modulation) + int rate = GET_LE16( raw_voice.rate ) & 0x3fff; + if ( g.pitch_mods & vbit ) + rate = (rate * (prev_outx + 32768)) >> 15; + + // fraction + int fraction = voice.fraction & 0xfff; + voice.fraction = fraction + rate; + fraction >>= 4; + + // Gaussian interpolation using most recent 4 samples + const short* table = gauss [fraction]; + const short* table2 = gauss [255 - fraction]; + int s = ((table [0] * voice.interp3) >> 12) + + ((table [1] * voice.interp2) >> 12) + + ((table2 [1] * voice.interp1) >> 12) + + ((table2 [0] * voice.interp0) >> 12); + int output = noise_amp; // noise is almost never used + if ( !(g.noise_enables & vbit) ) + output = clamp_16( s * 2 ); + + int muted = vbit & voices_muted; + + // scale output and set outx values + output = ((output * envx) >> 11) & ~1; + prev_outx = output; + raw_voice.outx = output >> 8; + + // apply muting if voice is externally disabled (not a SNES feature) + if ( muted ) + output = 0; + + // output + int l = (voice_vol [vidx] [0] * output) >> 7; + int r = (voice_vol [vidx] [1] * output) >> 7; + if ( g.echo_ons & vbit ) { + echol += l; + echor += r; + } + left += l; + right += r; + } // end of channel loop + + // main volume control + left = (left * left_volume) >> (7 + emu_gain_bits); + right = (right * right_volume) >> (7 + emu_gain_bits); + + // Echo FIR filter + + // read feedback from echo buffer + int echo_ptr = this->echo_ptr; + uint8_t* echo_buf = &ram [(g.echo_page * 0x100 + echo_ptr) & 0xFFFF]; + echo_ptr += 4; + if ( echo_ptr >= (g.echo_delay & 15) * 0x800 ) + echo_ptr = 0; + int fb_left = (BOOST::int16_t) GET_LE16( echo_buf ); // sign-extend + int fb_right = (BOOST::int16_t) GET_LE16( echo_buf + 2 ); // sign-extend + this->echo_ptr = echo_ptr; + + // put samples in history ring buffer + const int fir_offset = this->fir_offset; + short (*fir_pos) [2] = &fir_buf [fir_offset]; + this->fir_offset = (fir_offset + 7) & 7; // move backwards one step + fir_pos [0] [0] = fb_left; + fir_pos [0] [1] = fb_right; + fir_pos [8] [0] = fb_left; // duplicate at +8 eliminates wrap checking below + fir_pos [8] [1] = fb_right; + + // FIR + fb_left = fb_left * fir_coeff [7] + + fir_pos [1] [0] * fir_coeff [6] + + fir_pos [2] [0] * fir_coeff [5] + + fir_pos [3] [0] * fir_coeff [4] + + fir_pos [4] [0] * fir_coeff [3] + + fir_pos [5] [0] * fir_coeff [2] + + fir_pos [6] [0] * fir_coeff [1] + + fir_pos [7] [0] * fir_coeff [0]; + + fb_right = fb_right * fir_coeff [7] + + fir_pos [1] [1] * fir_coeff [6] + + fir_pos [2] [1] * fir_coeff [5] + + fir_pos [3] [1] * fir_coeff [4] + + fir_pos [4] [1] * fir_coeff [3] + + fir_pos [5] [1] * fir_coeff [2] + + fir_pos [6] [1] * fir_coeff [1] + + fir_pos [7] [1] * fir_coeff [0]; + + // overlap calculations with tests + left += (fb_left * g.left_echo_volume) >> 14; + + // echo buffer feedback + if ( !(g.flags & 0x20) ) { + echol += (fb_left * g.echo_feedback) >> 14; + echor += (fb_right * g.echo_feedback) >> 14; + SET_LE16( echo_buf, clamp_16( echol ) ); + SET_LE16( echo_buf + 2, clamp_16( echor ) ); + } + + right += (fb_right * g.right_echo_volume) >> 14; + + if ( out_buf ) + { + // write final samples + + left = clamp_16( left ); + right = clamp_16( right ); + + int mute = g.flags & 0x40; + + out_buf [0] = left; + out_buf [1] = right; + out_buf += 2; + + // muting + if ( mute ) { + out_buf [-2] = 0; + out_buf [-1] = 0; + } + } + } +} + +// Base normal_gauss table is very close to the following: +#if 0 +double e = 2.718281828; +for ( int i = 0; i < 512; i++ ) { + double x = i / 511.0 * 2.31 - 0.05; + double y = pow( e, -x * x ) * 1305.64; + normal_gauss [i] = y - 16.54; +} +#endif + +// Interleved gauss table (to improve cache coherency). +// gauss [i] [j] = normal_gauss [(1 - j) * 256 + i] +const short Spc_Dsp::gauss [256] [2] = { + 370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303, + 339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299, + 311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292, + 283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282, + 257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269, + 233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253, + 210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234, + 188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213, + 168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190, + 150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164, + 132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136, + 117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106, + 102,1102, 100,1098, 99,1094, 97,1090, 95,1086, 94,1082, 92,1078, 90,1074, + 89,1070, 87,1066, 86,1061, 84,1057, 83,1053, 81,1049, 80,1045, 78,1040, + 77,1036, 76,1032, 74,1027, 73,1023, 71,1019, 70,1014, 69,1010, 67,1005, + 66,1001, 65, 997, 64, 992, 62, 988, 61, 983, 60, 978, 59, 974, 58, 969, + 56, 965, 55, 960, 54, 955, 53, 951, 52, 946, 51, 941, 50, 937, 49, 932, + 48, 927, 47, 923, 46, 918, 45, 913, 44, 908, 43, 904, 42, 899, 41, 894, + 40, 889, 39, 884, 38, 880, 37, 875, 36, 870, 36, 865, 35, 860, 34, 855, + 33, 851, 32, 846, 32, 841, 31, 836, 30, 831, 29, 826, 29, 821, 28, 816, + 27, 811, 27, 806, 26, 802, 25, 797, 24, 792, 24, 787, 23, 782, 23, 777, + 22, 772, 21, 767, 21, 762, 20, 757, 20, 752, 19, 747, 19, 742, 18, 737, + 17, 732, 17, 728, 16, 723, 16, 718, 15, 713, 15, 708, 15, 703, 14, 698, + 14, 693, 13, 688, 13, 683, 12, 678, 12, 674, 11, 669, 11, 664, 11, 659, + 10, 654, 10, 649, 10, 644, 9, 640, 9, 635, 9, 630, 8, 625, 8, 620, + 8, 615, 7, 611, 7, 606, 7, 601, 6, 596, 6, 592, 6, 587, 6, 582, + 5, 577, 5, 573, 5, 568, 5, 563, 4, 559, 4, 554, 4, 550, 4, 545, + 4, 540, 3, 536, 3, 531, 3, 527, 3, 522, 3, 517, 2, 513, 2, 508, + 2, 504, 2, 499, 2, 495, 2, 491, 2, 486, 1, 482, 1, 477, 1, 473, + 1, 469, 1, 464, 1, 460, 1, 456, 1, 451, 1, 447, 1, 443, 1, 439, + 0, 434, 0, 430, 0, 426, 0, 422, 0, 418, 0, 414, 0, 410, 0, 405, + 0, 401, 0, 397, 0, 393, 0, 389, 0, 385, 0, 381, 0, 378, 0, 374, +}; + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Spc_Dsp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Dsp.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,159 @@ + +// Super Nintendo (SNES) SPC DSP emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. +// Copyright (C) 2002 Brad Martin + +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +// Surround effects using opposite volumes for left and right are currently removed +// by making left and right volume positive if their signs differ. + +class Spc_Dsp { + typedef BOOST::int8_t int8_t; + typedef BOOST::uint8_t uint8_t; +public: + + // Keeps pointer to ram + Spc_Dsp( uint8_t ram [0x10000] ); + + // Mute voice n if bit n (1 << n) of mask is clear. + enum { voice_count = 8 }; + void mute_voices( int mask ); + + // Clear state and silence everything. + void reset(); + + // Set gain, where 1.0 is normal. When greater than 1.0, output is clamped to + // the 16-bit sample range. + void set_gain( double ); + + // Read/write register 'n', where n ranges from 0 to register_count - 1. + enum { register_count = 128 }; + int read ( int n ); + void write( int n, int ); + + // Run DSP for 'count' samples. Write resulting samples to 'buf' if not NULL. + void run( long count, short* buf = NULL ); + + +// End of public interface +private: + + struct raw_voice_t { + int8_t left_vol; + int8_t right_vol; + uint8_t rate [2]; + uint8_t waveform; + uint8_t adsr [2]; // envelope rates for attack, decay, and sustain + uint8_t gain; // envelope gain (if not using ADSR) + int8_t envx; // current envelope level + int8_t outx; // current sample + int8_t unused [6]; + }; + + union { + raw_voice_t voice [voice_count]; + + uint8_t reg [register_count]; + + struct { + int8_t unused1 [12]; + int8_t left_volume; // 0C Main Volume Left (-.7) + int8_t echo_feedback; // 0D Echo Feedback (-.7) + int8_t unused2 [14]; + int8_t right_volume; // 1C Main Volume Right (-.7) + int8_t unused3 [15]; + int8_t left_echo_volume; // 2C Echo Volume Left (-.7) + uint8_t pitch_mods; // 2D Pitch Modulation on/off for each voice + int8_t unused4 [14]; + int8_t right_echo_volume; // 3C Echo Volume Right (-.7) + uint8_t noise_enables; // 3D Noise output on/off for each voice + int8_t unused5 [14]; + uint8_t key_ons; // 4C Key On for each voice + uint8_t echo_ons; // 4D Echo on/off for each voice + int8_t unused6 [14]; + uint8_t key_offs; // 5C key off for each voice (instantiates release mode) + uint8_t wave_page; // 5D source directory (wave table offsets) + int8_t unused7 [14]; + uint8_t flags; // 6C flags and noise freq + uint8_t echo_page; // 6D + int8_t unused8 [14]; + uint8_t wave_ended; // 7C + uint8_t echo_delay; // 7D ms >> 4 + char unused9 [2]; + } g; + }; + + uint8_t* const ram; + + // Cache of echo FIR values for faster access + short fir_coeff [voice_count]; + + // fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code + short fir_buf [16] [2]; + int fir_offset; // (0 to 7) + + short voice_vol [voice_count] [2]; + + enum { emu_gain_bits = 8 }; + int emu_gain; + + int keyed_on; // 8-bits for 8 voices + int keys; + + int echo_ptr; + int noise_amp; + int noise; + int noise_count; + + int voices_muted; + int disable_surround_; // set to sign bit (0x80) when disabled + + static const short gauss [] [2]; + + enum state_t { + state_attack, + state_decay, + state_sustain, + state_release + }; + + struct voice_t { + short fraction;// 12-bit fractional position + short interp0; // most recent four decoded samples + short interp1; + short interp2; + short interp3; + short block_remain; // number of nybbles remaining in current block + unsigned short addr; + short block_header; // header byte from current block + short envcnt; + short envx; + short on_cnt; + state_t envstate; + }; + + voice_t voice_state [voice_count]; + + int clock_envelope( int ); +}; + +inline int Spc_Dsp::read( int i ) { + assert( (unsigned) i < register_count ); + return reg [i]; +} + +inline void Spc_Dsp::mute_voices( int mask ) { + voices_muted = mask; +} + +inline void Spc_Dsp::set_gain( double v ) { + emu_gain = v * (1 << emu_gain_bits); +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Spc_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,113 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Spc_Emu.h" + +#include + +/* Copyright (C) 2004-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Spc_Emu::Spc_Emu() +{ + resample_ratio = 1.0; + use_resampler = false; + track_count_ = 0; +} + +Spc_Emu::~Spc_Emu() +{ +} + +const char** Spc_Emu::voice_names() const +{ + static const char* names [] = { + "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" + }; + return names; +} + +blargg_err_t Spc_Emu::init( long sample_rate, double gain ) +{ + apu.set_gain( gain ); + use_resampler = false; + resample_ratio = (double) native_sample_rate / sample_rate; + if ( sample_rate != native_sample_rate ) + { + BLARGG_RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) ); + resampler.time_ratio( resample_ratio, 0.9965 ); + use_resampler = true; + } + + return blargg_success; +} + +blargg_err_t Spc_Emu::load( const header_t& h, Emu_Reader& in ) +{ + if ( in.remain() < sizeof file.data ) + return "Not an SPC file"; + + if ( strncmp( h.tag, "SNES-SPC700 Sound File Data", 27 ) != 0 ) + return "Not an SPC file"; + + track_count_ = 1; + voice_count_ = Snes_Spc::voice_count; + + memcpy( &file.header, &h, sizeof file.header ); + return in.read( file.data, sizeof file.data ); +} + +blargg_err_t Spc_Emu::start_track( int ) +{ + resampler.clear(); + return apu.load_spc( &file, sizeof file ); +} + +blargg_err_t Spc_Emu::skip( long count ) +{ + count = long (count * resample_ratio) & ~1; + + count -= resampler.skip_input( count ); + if ( count > 0 ) + BLARGG_RETURN_ERR( apu.skip( count ) ); + + // eliminate pop due to resampler + const int resampler_latency = 64; + sample_t buf [resampler_latency]; + return play( resampler_latency, buf ); +} + +blargg_err_t Spc_Emu::play( long count, sample_t* out ) +{ + require( track_count_ ); // file must be loaded + + if ( !use_resampler ) + return apu.play( count, out ); + + long remain = count; + while ( remain > 0 ) + { + remain -= resampler.read( &out [count - remain], remain ); + if ( remain > 0 ) + { + long n = resampler.max_write(); + BLARGG_RETURN_ERR( apu.play( n, resampler.buffer() ) ); + resampler.write( n ); + } + } + + assert( remain == 0 ); + + return blargg_success; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Spc_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Spc_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,79 @@ + +// Super Nintendo (SNES) SPC music file emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2004 Shay Green. GNU LGPL license. + +#ifndef SPC_EMU_H +#define SPC_EMU_H + +#include "Fir_Resampler.h" +#include "Music_Emu.h" +#include "Snes_Spc.h" + +class Spc_Emu : public Music_Emu { +public: + Spc_Emu(); + ~Spc_Emu(); + + // The Super Nintendo hardware samples at 32kHz + enum { native_sample_rate = 32000 }; + + // Initialize emulator with given sample rate and gain. A sample rate different than + // the native 32kHz results in internal resampling to the desired rate. A gain of 1.0 + // results in almost no clamping. Default gain roughly matches volume of other emulators. + blargg_err_t init( long sample_rate, double gain = 1.4 ); + + struct header_t { + char tag [35]; + byte format; + byte version; + byte pc [2]; + byte a, x, y, psw, sp; + byte unused [2]; + char song [32]; + char game [32]; + char dumper [16]; + char comment [32]; + byte date [11]; + char len_secs [3]; + byte fade_msec [5]; + char author [32]; + byte mute_mask; + byte emulator; + byte unused2 [45]; + + enum { copyright = 0 }; // no copyright field + }; + + // Load SPC, given its header and reader for remaining data + blargg_err_t load( const header_t&, Emu_Reader& ); + + void mute_voices( int ); + blargg_err_t start_track( int ); + blargg_err_t play( long count, sample_t* ); + blargg_err_t skip( long ); + const char** voice_names() const; + + +// End of public interface +private: + Snes_Spc apu; + Fir_Resampler resampler; + double resample_ratio; + bool use_resampler; + + struct spc_file_t { + header_t header; + char data [0x10080]; + }; + BOOST_STATIC_ASSERT( sizeof (spc_file_t) == 0x10180 ); + + spc_file_t file; +}; + +inline void Spc_Emu::mute_voices( int m ) { + apu.mute_voices( m ); +} + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Tagged_Data.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Tagged_Data.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,31 @@ + +// Stubs to disable tagged data reflection functions + +// Game_Music_Emu 0.2.4. Copyright (C) 2005 Shay Green. GNU LGPL license. + +#ifndef TAGGED_DATA_H +#define TAGGED_DATA_H + +typedef long data_tag_t; + +class Tagged_Data { +public: + Tagged_Data( Tagged_Data& parent, data_tag_t ) { } + int reading() const { return false; } + int not_found() const { return true; } + int reflect_int8( data_tag_t, int n ) { return n; } + int reflect_int16( data_tag_t, int n ) { return n; } + long reflect_int32( data_tag_t, long n ) { return n; } +}; + +template +inline void reflect_int8( Tagged_Data&, data_tag_t, T* ) { } + +template +inline void reflect_int16( Tagged_Data&, data_tag_t, T* ) { } + +template +inline void reflect_int32( Tagged_Data&, data_tag_t, T* ) { } + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Vgm_Emu.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vgm_Emu.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,286 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "Vgm_Emu.h" + +#include +#include + +/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +const long vgm_sample_rate = 44100; + +Vgm_Emu::Vgm_Emu( double gain ) +{ + data = NULL; + pos = NULL; + apu.volume( gain ); + + // to do: decide on equalization parameters + set_equalizer( equalizer_t( -32, 8000, 66 ) ); +} + +Vgm_Emu::~Vgm_Emu() +{ + unload(); +} + +void Vgm_Emu::unload() +{ + delete [] data; + data = NULL; + pos = NULL; + track_ended_ = false; +} + +const char** Vgm_Emu::voice_names() const +{ + static const char* names [] = { "Square 1", "Square 2", "Square 3", "Noise" }; + return names; +} + +void Vgm_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + apu.osc_output( i, c, l, r ); +} + +void Vgm_Emu::update_eq( blip_eq_t const& eq ) +{ + apu.treble_eq( eq ); +} + +const int time_bits = 12; + +inline sms_time_t Vgm_Emu::clocks_from_samples( int samples ) const +{ + const long round_up = 1L << (time_bits - 1); + return (samples * time_factor + round_up) >> time_bits; +} + +static long get_le32( const BOOST::uint8_t b [4] ) +{ + return b [3] * 0x1000000L + b [2] * 0x10000L + b [1] * 0x100L + b [0]; +} + +blargg_err_t Vgm_Emu::load( const header_t& h, Emu_Reader& in ) +{ + unload(); + + // compatibility + if ( 0 != memcmp( h.tag, "Vgm ", 4 ) ) + return "Not a VGM file"; + if ( get_le32( h.vers ) > 0x0101 ) + return "Unsupported VGM format"; + + // clock rate + long clock_rate = get_le32( h.psg_rate ); + if ( !clock_rate ) + return "Only PSG sound chip is supported"; + time_factor = (long) floor( clock_rate * ((1L << time_bits) / + (double) vgm_sample_rate) + 0.5 ); + + // data + long data_size = in.remain(); + data = new byte [data_size + 3]; // allow pointer to go past end + if ( !data ) + return "Out of memory"; + end = data + data_size; + data [data_size] = 0; + data [data_size + 1] = 0; + data [data_size + 2] = 0; + blargg_err_t err = in.read( data, data_size ); + if ( err ) { + unload(); + return err; + } + long loop_offset = get_le32( h.loop_offset ); + loop_begin = end; + loop_duration = 0; + if ( loop_offset ) + { + loop_duration = get_le32( h.loop_duration ); + if ( loop_duration ) + loop_begin = &data [loop_offset + 0x1c - sizeof (header_t)]; + } + + voice_count_ = Sms_Apu::osc_count; + track_count_ = 1; + + return setup_buffer( clock_rate ); +} + +int Vgm_Emu::track_length( const byte** end_out, int* remain_out ) const +{ + require( data ); // file must have been loaded + + long time = 0; + + if ( end_out || !loop_duration ) + { + const byte* p = data; + while ( p < end ) + { + int cmd = *p++; + switch ( cmd ) + { + case 0x4f: + case 0x50: + p += 1; + break; + + case 0x61: + if ( p + 1 < end ) { + time += p [1] * 0x100L + p [0]; + p += 2; + } + break; + + case 0x62: + time += 735; // ntsc frame + break; + + case 0x63: + time += 882; // pal frame + break; + + default: + if ( (p [-1] & 0xf0) == 0x50 ) { + p += 2; + break; + } + dprintf( "Bad command in VGM stream: %02X\n", (int) cmd ); + break; + + case 0x66: + if ( end_out ) + *end_out = p; + if ( remain_out ) + *remain_out = end - p; + p = end; + break; + } + } + } + + // i.e. ceil( exact_length + 0.5 ) + return loop_duration ? 0 : + (time + (vgm_sample_rate >> 1) + vgm_sample_rate - 1) / vgm_sample_rate; +} + +blargg_err_t Vgm_Emu::start_track( int ) +{ + require( data ); // file must have been loaded + + pos = data; + loop_remain = 0; + delay = 0; + track_ended_ = false; + apu.reset(); + starting_track(); + return blargg_success; +} + +blip_time_t Vgm_Emu::run( int msec, bool* added_stereo ) +{ + require( pos ); // track must have been started + + const int duration = vgm_sample_rate / 100 * msec / 10; + int time = delay; + while ( time < duration && pos < end ) + { + if ( !loop_remain && pos >= loop_begin ) + loop_remain = loop_duration; + + int cmd = *pos++; + int delay = 0; + switch ( cmd ) + { + case 0x66: + pos = end; // end + if ( loop_duration ) { + pos = loop_begin; + loop_remain = loop_duration; + } + break; + + case 0x62: + delay = 735; // ntsc frame + break; + + case 0x63: + delay = 882; // pal frame + break; + + case 0x4f: + if ( pos == end ) { + check( false ); // missing data + break; + } + apu.write_ggstereo( clocks_from_samples( time ), *pos++ ); + break; + + case 0x50: + if ( pos == end ) { + check( false ); // missing data + break; + } + apu.write_data( clocks_from_samples( time ), *pos++ ); + break; + + case 0x61: + if ( end - pos < 1 ) { + check( false ); // missing data + break; + } + delay = pos [1] * 0x100L + pos [0]; + pos += 2; + break; + + default: + if ( (cmd & 0xf0) == 0x50 ) + { + if ( end - pos < 1 ) { + check( false ); // missing data + break; + } + pos += 2; + break; + } + dprintf( "Bad command in VGM stream: %02X\n", (int) cmd ); + break; + + } + time += delay; + if ( loop_remain && (loop_remain -= delay) <= 0 ) + { + pos = loop_begin; + loop_remain = 0; + } + } + + blip_time_t end_time = clocks_from_samples( duration ); + if ( pos < end ) + { + delay = time - duration; + if ( apu.end_frame( end_time ) && added_stereo ) + *added_stereo = true; + } + else { + delay = 0; + track_ended_ = true; + } + + return end_time; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/Vgm_Emu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/Vgm_Emu.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,75 @@ + +// Sega Master System VGM-format game music file emulator (PSG chip only) + +// Game_Music_Emu 0.2.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef VGM_EMU_H +#define VGM_EMU_H + +#include "Classic_Emu.h" +#include "Sms_Apu.h" + +class Vgm_Emu : public Classic_Emu { +public: + // Set internal gain, where 1.0 results in almost no clamping. Default gain + // roughly matches volume of other emulators. + Vgm_Emu( double gain = 1.0 ); + ~Vgm_Emu(); + + struct header_t { + char tag [4]; + byte data_size [4]; + byte vers [4]; + byte psg_rate [4]; + byte fm_rate [4]; + byte g3d_offset [4]; + byte sample_count [4]; + byte loop_offset [4]; + byte loop_duration [4]; + byte frame_rate [4]; + char unused [0x18]; + + enum { track_count = 1 }; // one track per file + + // no text fields + enum { game = 0 }; + enum { song = 0 }; + enum { author = 0 }; + enum { copyright = 0 }; + }; + BOOST_STATIC_ASSERT( sizeof (header_t) == 64 ); + + // Load VGM, given its header and reader for remaining data + blargg_err_t load( const header_t&, Emu_Reader& ); + + // Determine length of track, in seconds (0 if track is endless). + // Optionally returns pointer and size of data past end of sequence data + // (i.e. any tagging information). + int track_length( const byte** end_out = NULL, int* remain_out = NULL ) const; + + blargg_err_t start_track( int ); + const char** voice_names() const; + + +// End of public interface +protected: + void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + void update_eq( blip_eq_t const& ); + blip_time_t run( int, bool* ); +private: + byte* data; + const byte* pos; + const byte* end; + const byte* loop_begin; + long loop_duration; + long loop_remain; + long time_factor; + int delay; + Sms_Apu apu; + + sms_time_t clocks_from_samples( int ) const; + void unload(); +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/abstract_file.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/abstract_file.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,247 @@ + +#include "abstract_file.h" + +#include +#include +#include + +/* Copyright (C) 2005 by Shay Green. Permission is hereby granted, free of +charge, to any person obtaining a copy of this software module and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. THE +SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#ifndef RAISE_ERROR + #define RAISE_ERROR( str ) return str +#endif + +typedef Data_Reader::error_t error_t; + +error_t Data_Reader::read( void* p, long s ) +{ + long result = read_avail( p, s ); + if ( result != s ) + { + if ( result >= 0 && result < s ) + RAISE_ERROR( "Unexpected end-of-file" ); + + RAISE_ERROR( "Read error" ); + } + + return NULL; +} + +long File_Reader::remain() const +{ + return size() - tell(); +} + +error_t Data_Reader::skip( long count ) +{ + char buf [512]; + while ( count ) + { + int n = sizeof buf; + if ( n > count ) + n = count; + count -= n; + RAISE_ERROR( read( buf, n ) ); + } + return NULL; +} + +error_t File_Reader::skip( long n ) +{ + assert( n >= 0 ); + if ( n ) + RAISE_ERROR( seek( tell() + n ) ); + + return NULL; +} + + +// Subset_Reader + +Subset_Reader::Subset_Reader( Data_Reader* in_, long size ) : + in( in_ ), + remain_( in_->remain() ) +{ + if ( remain_ > size ) + remain_ = size; +} + +long Subset_Reader::remain() const { + return remain_; +} + +long Subset_Reader::read_avail( void* p, long s ) +{ + if ( s > remain_ ) + s = remain_; + remain_ -= s; + return in->read_avail( p, s ); +} + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( (const char*) p ), + pos( 0 ), + size_( s ) +{ +} + +long Mem_File_Reader::size() const { + return size_; +} + +long Mem_File_Reader::read_avail( void* p, long s ) +{ + long r = remain(); + if ( s > r ) + s = r; + memcpy( p, begin + pos, s ); + pos += s; + return s; +} + +long Mem_File_Reader::tell() const { + return pos; +} + +error_t Mem_File_Reader::seek( long n ) +{ + if ( n > size_ ) + RAISE_ERROR( "Tried to go past end of file" ); + pos = n; + return NULL; +} + +// Std_File_Reader + +Std_File_Reader::Std_File_Reader() : file( NULL ) { +} + +Std_File_Reader::~Std_File_Reader() { + close(); +} + +error_t Std_File_Reader::open( const char* path ) +{ + file = fopen( path, "rb" ); + if ( !file ) + RAISE_ERROR( "Couldn't open file" ); + return NULL; +} + +long Std_File_Reader::size() const +{ + long pos = tell(); + fseek( file, 0, SEEK_END ); + long result = tell(); + fseek( file, pos, SEEK_SET ); + return result; +} + +long Std_File_Reader::read_avail( void* p, long s ) { + return fread( p, 1, s, file ); +} + +long Std_File_Reader::tell() const { + return ftell( file ); +} + +error_t Std_File_Reader::seek( long n ) +{ + if ( fseek( file, n, SEEK_SET ) != 0 ) + RAISE_ERROR( "Error seeking in file" ); + return NULL; +} + +void Std_File_Reader::close() +{ + if ( file ) { + fclose( file ); + file = NULL; + } +} + +// Std_File_Writer + +Std_File_Writer::Std_File_Writer() : file( NULL ) { +} + +Std_File_Writer::~Std_File_Writer() { + close(); +} + +error_t Std_File_Writer::open( const char* path ) +{ + file = fopen( path, "wb" ); + if ( !file ) + RAISE_ERROR( "Couldn't open file for writing" ); + + // to do: increase file buffer size + //setvbuf( file, NULL, _IOFBF, 32 * 1024L ); + + return NULL; +} + +error_t Std_File_Writer::write( const void* p, long s ) +{ + long result = fwrite( p, 1, s, file ); + if ( result != s ) + RAISE_ERROR( "Couldn't write to file" ); + return NULL; +} + +void Std_File_Writer::close() +{ + if ( file ) { + fclose( file ); + file = NULL; + } +} + +// Mem_Writer + +Mem_Writer::Mem_Writer( void* p, long s, int b ) : + out( p ), + remain_( s ), + ignore_excess( b ) +{ +} + +error_t Mem_Writer::write( const void* p, long s ) +{ + if ( s > remain_ ) + { + if ( !ignore_excess ) + RAISE_ERROR( "Tried to write more data than expected" ); + s = remain_; + } + remain_ -= s; + memcpy( out, p, s ); + out = (char*) out + s; + return NULL; +} + +long Mem_Writer::remain() const { + return remain_; +} + +// Null_Writer + +error_t Null_Writer::write( const void*, long ) { + return NULL; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/abstract_file.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/abstract_file.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,151 @@ + +// Abstract file access interfaces + +// Copyright (C) 2005 Shay Green. MIT license. + +#ifndef ABSTRACT_FILE_H +#define ABSTRACT_FILE_H + +#include + +// to do: built-in buffering? + +class Data_Reader { + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); +public: + Data_Reader() { } + virtual ~Data_Reader() { } + + // NULL on success, otherwise error string + typedef const char* error_t; + + // Read at most 'n' bytes. Return number of bytes read, negative + // value if error. + virtual long read_avail( void*, long n ) = 0; + + // Read exactly 'n' bytes (error if fewer are available). NULL on success, + // otherwise error string. + virtual error_t read( void*, long ); + + // Number of bytes remaining + virtual long remain() const = 0; + + // Skip forwards by 'n' bytes. + virtual error_t skip( long n ); + + // to do: bytes remaining = LONG_MAX when unknown? +}; + +class File_Reader : public Data_Reader { +public: + // Size of file + virtual long size() const = 0; + + // Current position in file + virtual long tell() const = 0; + + // Change position in file + virtual error_t seek( long ) = 0; + + virtual long remain() const; + + error_t skip( long n ); +}; + +class Subset_Reader : public Data_Reader { + Data_Reader* in; + long remain_; +public: + Subset_Reader( Data_Reader*, long size ); + long remain() const; + long read_avail( void*, long ); +}; + +class Mem_File_Reader : public File_Reader { + const char* const begin; + long pos; + const long size_; +public: + Mem_File_Reader( const void*, long size ); + + long size() const; + long read_avail( void*, long ); + + long tell() const; + error_t seek( long ); +}; + +class Std_File_Reader : public File_Reader { + FILE* file; + //FILE* owned_file; +public: + Std_File_Reader(); + ~Std_File_Reader(); + + error_t open( const char* ); + + // Forward read requests to file. Caller must close file later. + //void forward( FILE* ); + + long size() const; + long read_avail( void*, long ); + + long tell() const; + error_t seek( long ); + + void close(); +}; + +class Data_Writer { + // noncopyable + Data_Writer( const Data_Writer& ); + Data_Writer& operator = ( const Data_Writer& ); +public: + Data_Writer() { } + virtual ~Data_Writer() { } + + typedef const char* error_t; + + // Write 'n' bytes. NULL on success, otherwise error string. + virtual error_t write( const void*, long n ) = 0; +}; + +class Null_Writer : public Data_Writer { +public: + error_t write( const void*, long ); +}; + +class Std_File_Writer : public Data_Writer { + FILE* file; +public: + Std_File_Writer(); + ~Std_File_Writer(); + + error_t open( const char* ); + + // Forward writes to file. Caller must close file later. + //void forward( FILE* ); + + error_t write( const void*, long ); + + void close(); +}; + +// to do: mem file writer + +// Write to block of memory +class Mem_Writer : public Data_Writer { + void* out; + long remain_; + int ignore_excess; +public: + // to do: automatic allocation and expansion of memory? + Mem_Writer( void*, long size, int ignore_excess = 1 ); + error_t write( const void*, long ); + long remain() const; +}; + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/blargg_common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/blargg_common.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,161 @@ + +// Common headers used by Shay Green's libraries + +// Copyright (C) 2004-2005 Shay Green. + +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// Allow prefix configuration file *which can re-include blargg_common.h* +// (probably indirectly). +#ifdef HAVE_CONFIG_H + #undef BLARGG_COMMON_H + #include "config.h" + #define BLARGG_COMMON_H +#endif + +// Source files use #include BLARGG_ENABLE_OPTIMIZER before performance-critical code +#ifndef BLARGG_ENABLE_OPTIMIZER + #define BLARGG_ENABLE_OPTIMIZER "blargg_common.h" +#endif + +// Source files have #include BLARGG_SOURCE_BEGIN at the beginning +#ifndef BLARGG_SOURCE_BEGIN + #define BLARGG_SOURCE_BEGIN "blargg_source.h" +#endif + +// Determine compiler's language support + +#if defined (__MWERKS__) + // Metrowerks CodeWarrior + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + +#elif defined (_MSC_VER) + // Microsoft Visual C++ + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + +#elif defined (__GNUC__) + // GNU C++ + #define BLARGG_COMPILER_HAS_BOOL 1 + +#elif defined (__MINGW32__) + // Mingw? + #define BLARGG_COMPILER_HAS_BOOL 1 + +#elif __cplusplus < 199711 + // Pre-ISO C++ compiler + #define BLARGG_COMPILER_HAS_BOOL 0 + +#endif + +// Set up boost +#include "boost/config.hpp" +#ifndef BOOST_MINIMAL + #define BOOST boost + #ifndef BLARGG_COMPILER_HAS_NAMESPACE + #define BLARGG_COMPILER_HAS_NAMESPACE 1 + #endif + #ifndef BLARGG_COMPILER_HAS_BOOL + #define BLARGG_COMPILER_HAS_BOOL 1 + #endif +#endif + +// Bool support +#ifndef BLARGG_COMPILER_HAS_BOOL + #define BLARGG_COMPILER_HAS_BOOL 1 +#elif !BLARGG_COMPILER_HAS_BOOL + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// Set up namespace support + +#ifndef BLARGG_COMPILER_HAS_NAMESPACE + #define BLARGG_COMPILER_HAS_NAMESPACE 0 +#endif + +#ifndef BLARGG_USE_NAMESPACE + #define BLARGG_USE_NAMESPACE BLARGG_COMPILER_HAS_NAMESPACE +#endif + +#ifndef BOOST + #if BLARGG_USE_NAMESPACE + #define BOOST boost + #else + #define BOOST + #endif +#endif + +#undef BLARGG_BEGIN_NAMESPACE +#undef BLARGG_END_NAMESPACE +#if BLARGG_USE_NAMESPACE + #define BLARGG_BEGIN_NAMESPACE( name ) namespace name { + #define BLARGG_END_NAMESPACE } +#else + #define BLARGG_BEGIN_NAMESPACE( name ) + #define BLARGG_END_NAMESPACE +#endif + +#if BLARGG_USE_NAMESPACE + #define STD std +#else + #define STD +#endif + +// BOOST::uint8_t, BOOST::int16_t, etc. +#include "boost/cstdint.hpp" + +// BOOST_STATIC_ASSERT( expr ) +#include "boost/static_assert.hpp" + +// Common standard headers +#if BLARGG_COMPILER_HAS_NAMESPACE + #include + #include +#else + #include + #include +#endif + +// blargg_err_t (NULL on success, otherwise error string) +typedef const char* blargg_err_t; +const blargg_err_t blargg_success = 0; + +// BLARGG_BIG_ENDIAN and BLARGG_LITTLE_ENDIAN +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) + #if defined (__powerc) || defined (macintosh) + #define BLARGG_BIG_ENDIAN 1 + + #elif defined (_MSC_VER) && defined (_M_IX86) + #define BLARGG_LITTLE_ENDIAN 1 + + #endif +#endif + +// BLARGG_NONPORTABLE (allow use of nonportable optimizations/features) +#ifndef BLARGG_NONPORTABLE + #define BLARGG_NONPORTABLE 0 +#endif +#ifdef BLARGG_MOST_PORTABLE + #error "BLARGG_MOST_PORTABLE has been removed; see BLARGG_NONPORTABLE." +#endif + +// BLARGG_CPU_* +#if !defined (BLARGG_CPU_POWERPC) && !defined (BLARGG_CPU_X86) + #if defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + + #elif defined (_MSC_VER) && defined (_M_IX86) + #define BLARGG_CPU_X86 1 + + #endif +#endif + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/blargg_endian.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/blargg_endian.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,47 @@ + +// CPU Byte Order Utilities + +// Game_Music_Emu 0.2.4. Copyright (C) 2005 Shay Green. BSD license. + +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +inline unsigned get_le16( const void* p ) { + return *((unsigned char*) p + 1) * 0x100u + *(unsigned char*) p; +} + +inline void set_le16( void* p, unsigned n ) { + *(unsigned char*) p = n; + *((unsigned char*) p + 1) = n >> 8; +} + +#ifndef GET_LE16 + + #if 0 + // Read 16-bit little-endian unsigned integer from memory + unsigned GET_LE16( const void* ); + + // Write 16-bit little-endian integer to memory + void SET_LE16( void*, unsigned ); + #endif + + // Optimized implementation if byte order is known + #if BLARGG_NONPORTABLE && BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(unsigned short*) (addr)) + #define SET_LE16( addr, data ) (void (*(unsigned short*) (addr) = (data))) + + #elif BLARGG_NONPORTABLE && BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #define GET_LE16( addr ) ((unsigned) __lhbrx( (addr), 0 )) + #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) + + #else + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) + + #endif + +#endif + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/blargg_source.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/blargg_source.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,40 @@ + +// By default, #included at beginning of library source files + +// Copyright (C) 2005 Shay Green. + +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert(( "unmet requirement", expr )) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments. +// void dprintf( const char* format, ... ); +#undef dprintf +#define dprintf (1) ? ((void) 0) : (void) + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr returns error string, return it from current function, otherwise continue. +#define BLARGG_RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/boost/config.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/boost/config.hpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,13 @@ + +// Boost substitute. For full boost library see http://boost.org + +#ifndef BOOST_CONFIG_HPP +#define BOOST_CONFIG_HPP + +#define BOOST_MINIMAL 1 + +#define BLARGG_BEGIN_NAMESPACE( name ) +#define BLARGG_END_NAMESPACE + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/boost/cstdint.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/boost/cstdint.hpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,42 @@ + +// Boost substitute. For full boost library see http://boost.org + +#ifndef BOOST_CSTDINT_HPP +#define BOOST_CSTDINT_HPP + +#if BLARGG_USE_NAMESPACE + #include +#else + #include +#endif + +BLARGG_BEGIN_NAMESPACE( boost ) + +#if UCHAR_MAX != 0xFF || SCHAR_MAX != 0x7F +# error "No suitable 8-bit type available" +#endif + +typedef unsigned char uint8_t; +typedef signed char int8_t; + +#if USHRT_MAX != 0xFFFF +# error "No suitable 16-bit type available" +#endif + +typedef short int16_t; +typedef unsigned short uint16_t; + +#if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; +#elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; +#else +# error "No suitable 32-bit type available" +#endif + +BLARGG_END_NAMESPACE + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/boost/static_assert.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/boost/static_assert.hpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,22 @@ + +// Boost substitute. For full boost library see http://boost.org + +#ifndef BOOST_STATIC_ASSERT_HPP +#define BOOST_STATIC_ASSERT_HPP + +#if defined (_MSC_VER) && _MSC_VER <= 1200 + // MSVC6 can't handle the ##line concatenation + #define BOOST_STATIC_ASSERT( expr ) struct { int n [1 / ((expr) ? 1 : 0)]; } + +#else + #define BOOST_STATIC_ASSERT3( expr, line ) \ + typedef int boost_static_assert_##line [1 / ((expr) ? 1 : 0)] + + #define BOOST_STATIC_ASSERT2( expr, line ) BOOST_STATIC_ASSERT3( expr, line ) + + #define BOOST_STATIC_ASSERT( expr ) BOOST_STATIC_ASSERT2( expr, __LINE__ ) + +#endif + +#endif + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/changes.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/changes.txt Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,63 @@ +Game_Music_Emu Change Log + + +Game_Music_Emu 0.2.4 +-------------------- + +- Created a discussion forum for problems and feedback: +http://groups-beta.google.com/group/blargg-sound-libs + +- Added to-do list and design notes + +- Added Music_Emu::skip( long sample_count ) to skip ahead in current track + +- Added Gym_Emu::track_length() and Vgm_Emu::track_length() for determining the +length of non-looped GYM and VGM files + +- Fixed Fir_Resampler, used for SPC and GYM playback (was incorrectly using +abs() instead of fabs()...argh) + +- Fixed SPC emulation bugs: eliminated clicks in Plok! soundtrack and now stops +sample slightly earlier than the end, as the SNES does. Fixed a totally broken +CPU addressing mode. + +- Fixed Konami VRC6 saw wave (was very broken before). Now VRC6 music sounds +decent + +- Fixed a minor GBS emulation bug + +- Fixed GYM loop point bug when track was restarted before loop point had been +reached + +- Made default GBS frequency equalization less muffled + +- Added pseudo-surround effect removal for SPC files + +- Added Music_Emu::voice_names() which returns names for each voice. + +- Added BLARGG_SOURCE_BEGIN which allows custom compiler options to be easily +set for library sources + +- Changed assignment of expansion sound chips in Nsf_Emu to be spread more +evenly when using Effects_Buffer + + +Game_Music_Emu 0.2.0 +-------------------- + +- Redid framework and rewrote/cleaned up emulators + +- Changed licensing to GNU Lesser General Public License (LGPL) + +- Added Sega Genesis GYM and Super Nintendo SPC emulators + +- Added Namco-106 and Konami VRC6 sound chip support to NSF emulator + +- Eliminated use of static mutable data in emulators, allowing multi-instance +safety + + +Game_Music_Emu 0.1.0 +-------------------- + +- First release diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/demo.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/demo.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,63 @@ + +// Play "test.nsf" using Nsf_Emu and record to "out.wav". + +#include "Nsf_Emu.h" +#include "Wave_Writer.hpp" + +#include +#include + +static void exit_if_error( const char* str ) +{ + if ( str ) { + fprintf( stderr, "Error: %s\n", str ); + exit( EXIT_FAILURE ); + } +} + +int main() +{ + const long sample_rate = 44100; + + // Prepare emulator + Nsf_Emu* emu = new Nsf_Emu; + if ( !emu ) + exit_if_error( "Out of memory" ); + exit_if_error( emu->init( sample_rate ) ); + + // Load file + Emu_Std_Reader reader; + exit_if_error( reader.open( "test.nsf" ) ); + Nsf_Emu::header_t header; + exit_if_error( reader.read( &header, sizeof header ) ); + exit_if_error( emu->load( header, reader ) ); + + // Print game and song info + printf( "Game : %-32s\n", header.game ? (char*) header.game : "" ); + printf( "Song : %-32s\n", header.song ? (char*) header.song : "" ); + printf( "Author : %-32s\n", header.author ? (char*) header.author : "" ); + printf( "Copyright: %-32s\n", header.copyright ? (char*) header.copyright : "" ); + printf( "Tracks : %d\n", emu->track_count() ); + printf( "\n" ); + + // Record first track for several seconds + exit_if_error( emu->start_track( 0 ) ); + Wave_Writer wave( sample_rate, "out.wav" ); + wave.stereo( true ); + while ( wave.sample_count() < 2 * sample_rate * 10 ) + { + const long buf_size = 1024; // must be even + Music_Emu::sample_t buf [buf_size]; + + // fill buffer + exit_if_error( emu->play( buf_size, buf ) ); + + // write to sound file + wave.write( buf, buf_size ); + } + + delete emu; + + return 0; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/demo_effects.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/demo_effects.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,71 @@ + +// Use Effects_Buffer to add stereo effects while playing "test.gbs" using Gbs_Emu, +// and record to "out.wav". + +#include "Gbs_Emu.h" +#include "Wave_Writer.hpp" +#include "Effects_Buffer.h" + +#include +#include + +static void exit_if_error( const char* str ) +{ + if ( str ) { + fprintf( stderr, "Error: %s\n", str ); + exit( EXIT_FAILURE ); + } +} + +int main() +{ + const long sample_rate = 44100; + + // Create effects buffer with 1/30 second length + Effects_Buffer buf; + exit_if_error( buf.sample_rate( sample_rate, 1000 / 30 ) ); + + // Create emulator and output to effects buffer + Gbs_Emu* emu = new Gbs_Emu; + if ( !emu ) + exit_if_error( "Out of memory" ); + exit_if_error( emu->init( &buf ) ); + + // Load file + Emu_Std_Reader reader; + exit_if_error( reader.open( "test.gbs" ) ); + Gbs_Emu::header_t header; + exit_if_error( reader.read( &header, sizeof header ) ); + exit_if_error( emu->load( header, reader ) ); + + // Configure effects buffer + Effects_Buffer::config_t cfg; + cfg.pan_1 = -0.12; // put first two oscillators slightly off-center + cfg.pan_2 = 0.12; + cfg.reverb_delay = 88; // delays are in milliseconds + cfg.reverb_level = 0.20; // significant reverb + cfg.echo_delay = 61; // echo applies to noise and percussion oscillators + cfg.echo_level = 0.15; + cfg.delay_variance = 18; // left/right delays must differ for stereo effect + cfg.effects_enabled = true; + buf.config( cfg ); + + // Record first track for several seconds + exit_if_error( emu->start_track( 0 ) ); + Wave_Writer wave( sample_rate, "out.wav" ); + wave.stereo( true ); + while ( wave.sample_count() < 2 * sample_rate * 10 ) + { + const long buf_size = 1024; // must be even + Music_Emu::sample_t buf [buf_size]; + + exit_if_error( emu->play( buf_size, buf ) ); + + wave.write( buf, buf_size ); + } + + delete emu; + + return 0; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/demo_panning.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/demo_panning.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,67 @@ + +// Use Panning_Buffer to add left/right panning while playing "test.vgm" using Vgm_Emu, +// and record to "out.wav". + +#include "Vgm_Emu.h" +#include "Wave_Writer.hpp" +#include "Panning_Buffer.h" + +#include +#include + +static void exit_if_error( const char* str ) +{ + if ( str ) { + fprintf( stderr, "Error: %s\n", str ); + exit( EXIT_FAILURE ); + } +} + +int main() +{ + const long sample_rate = 44100; + + // Create panning buffer with 1/30 second length + Panning_Buffer buf; + exit_if_error( buf.sample_rate( sample_rate, 1000 / 30 ) ); + + // Prepare emulator with output set to panning buffer + Vgm_Emu* emu = new Vgm_Emu; + if ( !emu ) + exit_if_error( "Out of memory" ); + exit_if_error( emu->init( &buf ) ); + + // Load file + Emu_Std_Reader reader; + exit_if_error( reader.open( "test.vgm" ) ); + Vgm_Emu::header_t header; + exit_if_error( reader.read( &header, sizeof header ) ); + exit_if_error( emu->load( header, reader ) ); + + // Configure panning buffer + buf.set_pan( 0, 1.40, 0.60 ); // pulse 1 - left + buf.set_pan( 1, 1.00, 1.00 ); // pulse 2 - center + buf.set_pan( 2, 0.40, 1.60 ); // pulse 3 - right + buf.set_pan( 3, 1.00,-1.00 ); // noise - "surround" (phase-inverted left/right) + + // Record first track for several seconds + exit_if_error( emu->start_track( 0 ) ); + Wave_Writer wave( sample_rate, "out.wav" ); + wave.stereo( true ); + while ( wave.sample_count() < 2 * sample_rate * 10 ) + { + const long buf_size = 1234; // can be any size + Music_Emu::sample_t buf [buf_size]; + + // fill buffer + exit_if_error( emu->play( buf_size, buf ) ); + + // write to sound file + wave.write( buf, buf_size ); + } + + delete emu; + + return 0; +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/design.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/design.txt Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,177 @@ +Game_Music_Emu Design Notes + + +Managing Complexity +------------------- +Complexity has been a factor in most library decisions. Many features +have been passed by due to the complexity they would add. Once past a +certain level, complexity prevents mentally grasping the library in its +entirety, at which point more defects will occur and be hard to find. + +I chose 16-bit signed samples because it seems to be the most common +format. Supporting multiple formats would add too much complexity to be +worth it. Other formats can be obtained via conversion. + +I've kept interfaces fairly lean, leaving many possible features +untapped but easy to add if necessary. For example the classic emulators +could have volume and frequency equalization adjusted separately for +each channel, since they each have an associated Blip_Synth. + +Source files of 400 lines or less seem to be the best size to limit +complexity. In a few cases there is no reasonable way to split longer +files, or there is benefit from having the source together in one file. + + +Library Configuration +--------------------- +Library optimizations can be configured through macros defined in +config.h. By default, the library is configured to be most likely to +compile and work on any platform, rather than be most optimal with +increased chance of problems. It's easier to track down optimiztation +problems if the library can first be shown to work correctly without +them. + + +Flexibility through indirection +------------------------------- +I've tried to allow the most flexibility of modules by using indirection +to allow extension by the user. This keeps each module simple and more +focused on its unique task. + +The classic emulators use Multi_Buffer, which potentially allows a +separate Blip_Buffer for each channel. This keeps emulators free of +typical code to allow output in mono, stereo, panning, etc. + +All emulators use a reader object to access file data, allowing it to be +stored in a regular file, compressed archive, memory, or generated +on-the-fly. Again, the library can be kept free of the particulars of +file access and changes required to support new formats. + + +Choice of pre-ISO (ARM) C++ +--------------------------- +The library started out as an unreleased NSF player written using ISO +C++. The code was clean enough that I decided to release it as a player +library. Before release I evaluated its use of C++ features to determine +the important ones. + +Namespaces and exceptions weren't essential, so I compared a version of +the library with and without them. I decided that I could do without and +get better compatibility with older compilers or newer ones with buggy +namespace and exception implementations. + +Templates are the worst area for most compilers, due to their inherent +complexity, but they are too useful to avoid entirely. I've used them +sparingly and in ways that compilers are more likely to work with. + +Sticking to ARM C++ has helped keep the library simpler to understand. + + +Platform-specific optimization +------------------------------ +Performance profiling doesn't shown any big bottlenecks that warrant +heavy platform-specific optimization. The main bottlenecks are CPU +emulation, Blip_Buffer synthesis and sample reading, Super NES DSP, Sega +Genesis FM, and Fir_Resampler. + +Further optimization of the CPU emulators can probably only be achieved +by writing them in assembly or using dynamic recompilation. Blip_Buffer +might benefit somewhat from vector instructions. Sega Genesis FM +synthesis can probably be made twice as fast by someone who fully +understands its operation (I am almost clueless about its internals). +The Super NES DSP might have some room for optimization. Fir_Resampler +would benefit greatly from vector operations. + +Most of the above optimizations add more complexity than they are worth. +All that seems worthwhile is optimization of Sega Genesis FM emulation +and Fir_Resampler. + + +Preventing Bugs +--------------- +I've done many things to reduce the opportunity for defects. A general +principle is to write code so that defects will be as visible as +possible. I've used several techniques to achieve this. + +I put assertions at key points where defects seem likely or where +corruption due to a defect is likely to be visible. I've also put +assetions where violations of the interface are likely. In emulators +where I am unsure of exact hardware operation in a particular case, I +output a debug-only message noting that this has occurred; many times I +haven't implemented a hardware feature because nothing uses it. I've +made code brittle where there is no clear reason flexibility; code +written to handle every possibility sacrifices quality and reliability +to handle vaguely defined situations. + + +Miscellaneous +------------- +I don't like naming header files with a ".hpp" suffix for some reason. +They aren't as visually distinct from ".cpp" source files. The ".cpp" +suffix is useful to inform the compiler that it's a C++ file rather than +a C file, but I don't know of significant practical benefits the ".hpp" +suffix gives over ".h" (I used to use *no* suffix for header files, but +this does cause problems). + +When implementation has to be put in a header file, it's as far near the +end as possible to prevent distraction from the public interface. + + +CPU Cores +--------- +I've spent lots of time coming up with techniques to optimize the CPU +cores. Some of the most important: execute multiple instructions during +an emulation call, keep state in local variables to allow register +assignment, optimize state representation for most common instructions, +defer status flag calculation until actually needed, read program code +directly without a call to the memory read function, always pre-fetch +the operand byte before decoding instruction, and emulate instructions +using common blocks of code. + +I've successfully used Nes_Cpu in a simple NES emulator, and I'd like to +make all the CPU emulators suitable for use in emulators. It seems a +waste for them to be used only for the small amount of emulation +necessary for game music files. + +I debugged the CPU cores by writing a test shell that ran them in +parallel with other CPU cores and compared all memory accesses and +processor states at each step. This provided good value at little cost. + +The CPU mapping page size is adjustable to allow the best tradeoff +between memory/cache usage and handler granularity. The interface allows +code to be somewhat independent of the page size. + +I optimize program memory accesses to to direct reads rather than calls +to the memory read function. My assumption is that it would be difficult +to get useful code out of hardware I/O addresses, so no software will +intentionally execute out of I/O space. Since the page size can be +changed easily, most program memory mapping schemes can be accommodated. +This greatly reduces memory access function calls. + + +Sub-Libraries +------------- +I've also released the sound cores as individual libraries to reduce +complexity for emulator authors who just want a single sound core. These +authors will be using the sound cores directly, while users of this +music emulator library won't even see them, so documentation and demos +can be specific to each library. + + +Documentation +------------- +I started out with separate documentation in HTML and found that it +wasn't going to be easy to maintain. I switched to putting descriptions +of function behavior in header files before the function declarations. +This has worked well so far. + +I think the concrete executable demo code helps the most when someone is +using the library for the first time, since it shows a complete program +and provides a framework for using the library. By recording to a sound +file, I can keep the code portable, and the result can be listened to or +examined closely, which is important for something real-time like sound. + + +I want to write some tutorials to complement the demo code, desribing +the basic framework and operation of the modules. + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/notes.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/notes.txt Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,220 @@ +Game_Music_Emu Notes + + +Architecture +------------ + +This library has several emulator classes derived from common interface +classes. Music_Emu specifies the main interface, and Classic_Emu adds +features available only for "classic" systems (frequency equalization +and customizable multi-channel sound buffer). + +To play a given game music file, do the following: + +- Determine its file type +- Create and set up an appropriate emulator +- Load file header and file data into emulator +- Start desired track +- When samples are needed, call play() +- When done, delete emulator + +Each emulator type defines a nested header_t structure type with members +for the file header. When loading a file, the emulators expect the file +header to have already been loaded; this allows the caller to use header +fields like the game name and music author. + +See Music_Emu.h and Classic_Emu.h for reference. + + +Error handling +-------------- + +Functions which can fail have a return type of blargg_err_t, which is a +pointer to an error string (const char*). If the function is successful +it returns blargg_success (NULL), otherwise it returns a pointer to an +error string. + +To allow compatibility with older C++ compilers, no exceptions are +thrown by any of the modules. The library is exception-safe, and any +exceptions which occur are not intercepted. + +Due to the different ways compiler runtime libraries handle +out-of-memory errors, if the library encounters one it will be reported +in one of two ways: if the compiler is configured to throw an exception +when operator new can't satisfy a request (which is the case in ISO +C++), it is allowed to propagate normally, otherwise the error string +"Out of memory" is returned. + +Significant violations of the documented interface are flagged with +debug-only assertions. Failure of these usually indicates a caller error +rather than a defect in the library. + + +Configuration +------------- + +The header "blargg_common.h" is used to establish a common environment. +It attempts to automatically determine the features of the environment, +but might need help. + +If HAVE_CONFIG_H is defined, the file "config.h" is included at the +beginning of each library header file, allowing configuration options +for the library to be set. It's fine if other libraries also use this +scheme, as they won't conflict. + +Some libraries depend on the order of bytes in multibyte types. These +will cause a compilation error if the order can't be determined. If this +occurs, define the appropriate symbol. For big-endian (most significant +byte first, i.e. Motorola 68000, PowerPC), #define BLARGG_BIG_ENDIAN to +1. For little-endian (least significant byte first, i.e. Intel x86), +#define BLARGG_LITTLE_ENDIAN to 1. + +Pre-ISO C++ compilers might not support bool. Support is provided where +bool is not available, but the compiler's support of bool might not be +properly determined. If errors occur in "blargg_common.h" in the bool +section, #define BLARGG_COMPILER_HAS_BOOL to 1 if your compiler supports +bool, otherwise 0. + +If your compiler supports namespaces, blargg_common.h uses standard +headers with the "c" prefix to avoid bringing names from std into the +global namespace. If your compiler supports namespaces but this isn't +being detected by blargg_common.h, #define BLARGG_COMPILER_HAS_NAMESPACE +to 1 in your config.h file. + +If you have any problems with "blargg_common.h", contact me. + + +Game Music File Handling +------------------------ + +Game music files include text fields with information about the game and +track. Each emulator's header_t defines the basic fields (game, song, +author, copyright, track_count) supported by that music type, or has an +enum { field = 0 } if unsupported. This allows the same code to be used +for parsing each header if it checks that the field is non-zero. + +Text fields in most game music formats won't have a nul terminator if +the string completely fills the field. The demos show one way to handle +this. + +This library is focused on playback only and the emulators don't parse +extended text fields, since this can be complex for some formats and can +be done by the caller. To load the NSFE format, it must be parsed first +and an NSF header must be made in memory and passed Nsf_Emu. See the +Game Music Box source code for an example of this: + + http://www.slack.net/~ant/game-music-box/dev.html + + +Frequency equalization +---------------------- + +The classic emulators allow frequency equalization to be adjusted; +Classic_Emu::equalizer_t( treble, cutoff, bass ) specifies low-pass and +high-pass filtering. + +Low-pass is an exponential rolloff beginning at 'cutoff' Hz and +attenuating by 'treble' dB at 22kHz. For example, with cutoff = 8000 Hz +and treble = -6 dB, the following results: + + cutoff = 8kHz + 0dB -------*__ + ~~--__ treble = -6dB + ~~-*__ + ~~--__ + ~~--__ + ~~--__ +-18dB - - - - - - - - - - - - - - - - - - - - - - + 0 8kHz 22kHz 44kHz ... + + +High-pass is a steep rolloff which passes -3dB attenuation at +'breakpoint' Hz, where useful frequencies range from 0 to 6000 Hz. For +example, with breakpoint = 1000 Hz, the following results: + + breakpoint = 1000 Hz + 0dB ___________ +-3dB ,_*---~~~~~ + _~ + / + / + | + | +-21dB - - - - - - - - - - - - - + 0 1000 Hz 4000 Hz + +Each emulator defaults to a profile that approximates its particular +console's sound quality; this default can be determined by getting the +current equalization just after creating the emulator. See Classic_Emu.h +for reference. + + +Emulator gain control +--------------------- + +Each emulator allows its gain to be adjusted. The default gains are +selected to give consistent relative volumes between emulators without +resulting in excessive clamping of samples that would otherwise go +beyond the 16-bit range. A gain of 1.0 results in a conservative volume +that rarely requires any clamping to stay within the 16-bit sample +range. Clamping samples to 16 bits is handled by the library. + + +Output sample rate +------------------ + +Each emulator has a way of specifying the output sample rate during +initialization. All the emulators use internal band-limiting, so there +is no reason to use a sample rate above 48kHz unless the sound hardware +demands it; 44-48kHz will yield the best results. You could use the +following code to choose a rate that is both near this range and an +integral division of the native rate: + + long adjust_rate( double native ) + { + for ( double divider = 1; divider <= 4; divider++ ) + { + long adjusted = native / divider + 0.5; + if ( adjusted <= 56000 ) + return adjusted; + } + return 44100; // give up; CD rate probably works well enough + } + + +Interface conventions +---------------------- + +If a function will keep a pointer to an object passed, to make this +clear in source code it takes a pointer rather than a reference. + +Multi-word names have an underscore '_' separator between individual +words. + +Functions are named with lowercase words. Functions which perform an +action with side-effects are named with a verb phrase (i.e. load, move, +run). Functions which set or return the value of a piece of state are +named using a noun phrase (i.e. loaded, moved, running). + +Classes are named with capitalized words. Only the first letter of an +acronym is capitalized. Class names are nouns, sometimes suggestive of +what they do (i.e. File_Scanner). + +Structure, enumeration, and typedefs to these and built-in types are +named using lowercase words with a _t suffix. + +Macros are named with all-uppercase words. + +Internal names which can't be hidden due to technical reasons have an +underscore '_' suffix. + + +Misc +---- + +Special thanks to Chris Moeller (kode54) for help with library testing +and feedback. His openspc++ library in C++ was an essential starting +point and framework for developing the SPC emulator. Brad Martin's +excellent SNES DSP emulator (also part of openspc++) provided an +essential foundation for the DSP core. + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/readme.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/readme.txt Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,122 @@ +Game_Music_Emu 0.2.4: Multi-Format Game Music Emulation Library + + +Game_Music_Emu is a collection of portable video game music emulators for the +following file formats: Nintendo NSF, Game Boy GBS, Sega Master System VGM, +Sega Gensesis GYM, and Super Nintendo SPC. + +Licensed under the GNU Lesser General Public License (LGPL); see LGPL.txt. +Copyright (C) 2003-2005 Shay Green. SNES SPC DSP emulator based on OpenSPC, +Copyright (C) 2002 Brad Martin. Sega Genesis YM2612 emulator from Gens project, +Copyright (C) 2002 Stephane Dallongeville. + +Website: http://www.slack.net/~ant/libs/ +Forum : http://groups-beta.google.com/group/blargg-sound-libs +Contact: hotpop.com@blargg (swap to e-mail) + + +Getting Started +--------------- + +This library is written in somewhat conservative C++ that should compile with +current and older compilers (ANSI/ISO and ARM). + +If the Boost library is installed in your environment, delete the included +"boost" compatibility directory, otherwise add the included "boost" directory +to your compiler's search paths. + +Build a program consisting of the included source files except demo_effects.cpp +and demo_panning.cpp, and any necessary system libraries. Be sure "test.nsf" is +in the same directory. The program should generate a WAVE sound file "out.wav" +of music. + +For a full example of using Game_Music_Emu in a music player, see the Game +Music Box source code: http://www.slack.net/~ant/game-music-box/dev.html + +See notes.txt for more information, and respective header (.h) files for +reference. Visit the discussion forum to get assistance. + + +Files +----- + +notes.txt Collection of notes about the library +changes.txt Changes made since previous releases +todo.txt Planned improvements and fixes +design.txt Library design notes +LGPL.TXT GNU Lesser General Public License + +demo.cpp Record NSF to WAVE sound file using emulator +demo_effects.cpp Use Effects_Buffer while recording GBS file +demo_panning.cpp Use Panning_Buffer while recording VGM file +test.nsf Test file for NSF emulator + +Music_Emu.h Game music emulator interface +Spc_Emu.h Super NES SPC emulator +Gym_Emu.h Sega Genesis GYM emulator + +Classic_Emu.h "Classic" game music emulator interface +Nsf_Emu.h Nintendo NSF emulator +Gbs_Emu.h Game Boy GBS emulator +Vgm_Emu.h Sega Master System VGM emulator + +Multi_Buffer.h Mono and stereo buffers for classic emulators +Effects_Buffer.h Effects buffer for classic emulators +Panning_Buffer.h Panning buffer for classic emulators + +blargg_common.h Common library source +blargg_endian.h +blargg_source.h +Blip_Buffer.cpp +Blip_Buffer.h +Blip_Synth.h +Music_Emu.cpp +Classic_Emu.cpp +Multi_Buffer.cpp +Effects_Buffer.cpp +Panning_Buffer.cpp +Fir_Resampler.cpp +Fir_Resampler.h +abstract_file.cpp +abstract_file.h +Nes_Apu.cpp NSF emulator source +Nes_Apu.h +Nes_Cpu.cpp +Nes_Cpu.h +Nes_Oscs.cpp +Nes_Oscs.h +Nsf_Emu.cpp +Nes_Namco.cpp +Nes_Namco.h +Nes_Vrc6.cpp +Nes_Vrc6.h +Tagged_Data.h +Gbs_Emu.cpp GBS emulator source +Gb_Apu.cpp +Gb_Apu.h +Gb_Cpu.cpp +Gb_Cpu.h +Gb_Oscs.cpp +Gb_Oscs.h +Sms_Apu.cpp VGM emulator source +Sms_Apu.h +Sms_Oscs.h +Vgm_Emu.cpp +Gym_Emu.cpp GYM emulator source +ym2612.cpp +ym2612.h +Spc_Emu.cpp SPC emulator source +Snes_Spc.cpp +Snes_Spc.h +Spc_Cpu.cpp +Spc_Cpu.h +Spc_Dsp.cpp +Spc_Dsp.h + +boost/ Substitute for boost library if it's unavailable + +Wave_Writer.hpp WAVE sound file writer used for demo output +Wave_Writer.cpp + +-- +Shay Green (swap to e-mail) diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/todo.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/todo.txt Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,72 @@ +Game_Music_Emu 0.2.4: Problems and planned improvements + + +Interface +--------- + +- Refer to sound chip oscillators as channels, since Nes_Channel was +renamed a long time ago. + +- Rename setters with set_ prefix. I decided the terseness wasn't worth +it. + +- Consider putting buffer pointer before count in play() etc. Only +reason I put count first is to allow making buffer pointer default to +NULL for skip. But it seems better to have skip() a separate function. +One other reason to put count first is to follow the left-to-right +principle in organizing arguments: play( count, buf ) generates 'count' +samples and writes them into 'buf', and count is a more significant +parameter than the buffer to write to. + +- VGM, GYM: change track_length() to return length and start of loop? + + +Misc +---- + +- NSF: Partial implementation of DAC non-linearity causes possible +drift. Messes up saw wave demo at high frequencies. + +- GYM: See if ignoring PCM when it's disabled eliminates pop in some +tracks. + +- All CPU emulators: what if instruction straddles page boundary? Might +need to completely avoid reading words from code memory. + +- Test reloading a new file into emulator after already loading one + +- Optimize resampling in Sega Genesis GYM emulator + +- Improve GBS emulation + +- Improve GYM PCM channel emulation + +- SPC: Finish KON and KOFF reverse-engineering and incorporate into +Spc_Dsp + +- GBS: Crystalis track 18 plays really fast. Does same in gbsplay 0.7. + +- GBS: Ultima 3 overflows stack. Messes up in gbsplay 0.7. + +- Set track_ended flag when emulation error occurs (log emulation error +only in debug mode). + +- Change wave and noise Gb_Oscs to blip_good_quality? + +- VGM: Support Sega Genesis + +- VGM: Parse extended header + +- VGM: Support new version of VGM file format that might be released +soon + +- GYM: Check Altered Beast tracks with samples. They cut off seemingly +too soon. + +- Include example of how to use NSF non-linear handling (with note about +limitations) + +- Keep SP and PC as 32-bit in CPU registers structure so emulator can +detect overflow/underflow and halt emulation, rather than having them +masked when written back? + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/ym2612.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/ym2612.cpp Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,1328 @@ + +// Game_Music_Emu 0.2.4. http://www.slack.net/~ant/libs/ + +#include "ym2612.h" + +#include +#include +#include + +/* Copyright (C) 2002 Stéphane Dallongeville (gens@consolemul.com) */ +/* Copyright (C) 2004-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// This is mostly the original source in its C style and all. +// +// Somewhat optimized and simplified. Uses a template to generate the many +// variants of Update_Chan. Rewrote header file. In need of full rewrite by +// someone more familiar with FM sound and the YM2612. Has some inaccuracies +// compared to the Sega Genesis sound, particularly being mixed at such a +// high sample accuracy (the Genesis sounds like it has only 8 bit samples). +// - Shay + +const int max_length = 3072; // A little over 4 frames + +const int output_bits = 14; + +typedef struct YM2612_slot_t { + const int *DT; // parametre detune + int MUL; // parametre "multiple de frequence" + int TL; // Total Level = volume lorsque l'enveloppe est au plus haut + int TLL; // Total Level ajusted + int SLL; // Sustin Level (ajusted) = volume où l'enveloppe termine sa premiere phase de regression + int KSR_S; // Key Scale Rate Shift = facteur de prise en compte du KSL dans la variations de l'enveloppe + int KSR; // Key Scale Rate = cette valeur est calculee par rapport à la frequence actuelle, elle va influer + // sur les differents parametres de l'enveloppe comme l'attaque, le decay ... comme dans la realite ! + int SEG; // Type enveloppe SSG + int env_xor; + int env_max; + + const int *AR; // Attack Rate (table pointeur) = Taux d'attaque (AR[KSR]) + const int *DR; // Decay Rate (table pointeur) = Taux pour la regression (DR[KSR]) + const int *SR; // Sustin Rate (table pointeur) = Taux pour le maintien (SR[KSR]) + const int *RR; // Release Rate (table pointeur) = Taux pour le rel'chement (RR[KSR]) + int Fcnt; // Frequency Count = compteur-frequence pour determiner l'amplitude actuelle (SIN[Finc >> 16]) + int Finc; // frequency step = pas d'incrementation du compteur-frequence + // plus le pas est grand, plus la frequence est aïgu (ou haute) + int Ecurp; // Envelope current phase = cette variable permet de savoir dans quelle phase + // de l'enveloppe on se trouve, par exemple phase d'attaque ou phase de maintenue ... + // en fonction de la valeur de cette variable, on va appeler une fonction permettant + // de mettre à jour l'enveloppe courante. + int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir où l'on se trouve dans l'enveloppe + int Einc; // Envelope step courant + int Ecmp; // Envelope counter limite pour la prochaine phase + int EincA; // Envelope step for Attack = pas d'incrementation du compteur durant la phase d'attaque + // cette valeur est egal à AR[KSR] + int EincD; // Envelope step for Decay = pas d'incrementation du compteur durant la phase de regression + // cette valeur est egal à DR[KSR] + int EincS; // Envelope step for Sustain = pas d'incrementation du compteur durant la phase de maintenue + // cette valeur est egal à SR[KSR] + int EincR; // Envelope step for Release = pas d'incrementation du compteur durant la phase de rel'chement + // cette valeur est egal à RR[KSR] + int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot à l'entree + // d'un autre ou carrement à la sortie de la voie + int INd; // input data of the slot = donnees en entree du slot + int ChgEnM; // Change envelop mask. + int AMS; // AMS depth level of this SLOT = degre de modulation de l'amplitude par le LFO + int AMSon; // AMS enable flag = drapeau d'activation de l'AMS +} slot_t; + +typedef struct YM2612_channel_t { + int S0_OUT[4]; // anciennes sorties slot 0 (pour le feed back) + int LEFT; // LEFT enable flag + int RIGHT; // RIGHT enable flag + int ALGO; // Algorythm = determine les connections entre les operateurs + int FB; // shift count of self feed back = degre de "Feed-Back" du SLOT 1 (il est son unique entree) + int FMS; // Frequency Modulation Sensitivity of channel = degre de modulation de la frequence sur la voie par le LFO + int AMS; // Amplitude Modulation Sensitivity of channel = degre de modulation de l'amplitude sur la voie par le LFO + int FNUM[4]; // hauteur frequence de la voie (+ 3 pour le mode special) + int FOCT[4]; // octave de la voie (+ 3 pour le mode special) + int KC[4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S) + slot_t SLOT[4]; // four slot.operators = les 4 slots de la voie + int FFlag; // Frequency step recalculation flag +} channel_t; + +typedef struct YM2612_state_t { + int Clock; // Horloge YM2612 + int Rate; // Sample Rate (11025/22050/44100) + int TimerBase; // TimerBase calculation + int Status; // YM2612 Status (timer overflow) + int OPNAadr; // addresse pour l'ecriture dans l'OPN A (propre à l'emulateur) + int OPNBadr; // addresse pour l'ecriture dans l'OPN B (propre à l'emulateur) + int LFOcnt; // LFO counter = compteur-frequence pour le LFO + int TimerA; // timerA limit = valeur jusqu'à laquelle le timer A doit compter + int TimerAL; + int TimerAcnt; // timerA counter = valeur courante du Timer A + int TimerB; // timerB limit = valeur jusqu'à laquelle le timer B doit compter + int TimerBL; + int TimerBcnt; // timerB counter = valeur courante du Timer B + int Mode; // Mode actuel des voie 3 et 6 (normal / special) + int DAC; // DAC enabled flag + double Frequence; // Frequence de base, se calcul par rapport à l'horlage et au sample rate + channel_t CHANNEL[YM2612_Emu::channel_count]; // Les 6 voies du YM2612 + int REG[2][0x100]; // Sauvegardes des valeurs de tout les registres, c'est facultatif + // cela nous rend le debuggage plus facile +} state_t; + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define ATTACK 0 +#define DECAY 1 +#define SUBSTAIN 2 +#define RELEASE 3 + +// SIN_LBITS <= 16 +// LFO_HBITS <= 16 +// (SIN_LBITS + SIN_HBITS) <= 26 +// (ENV_LBITS + ENV_HBITS) <= 28 +// (LFO_LBITS + LFO_HBITS) <= 28 + +#define SIN_HBITS 12 // Sinus phase counter int part +#define SIN_LBITS (26 - SIN_HBITS) // Sinus phase counter float part (best setting) + +#if (SIN_LBITS > 16) +#define SIN_LBITS 16 // Can't be greater than 16 bits +#endif + +#define ENV_HBITS 12 // Env phase counter int part +#define ENV_LBITS (28 - ENV_HBITS) // Env phase counter float part (best setting) + +#define LFO_HBITS 10 // LFO phase counter int part +#define LFO_LBITS (28 - LFO_HBITS) // LFO phase counter float part (best setting) + +#define SIN_LENGHT (1 << SIN_HBITS) +#define ENV_LENGHT (1 << ENV_HBITS) +#define LFO_LENGHT (1 << LFO_HBITS) + +#define TL_LENGHT (ENV_LENGHT * 3) // Env + TL scaling + LFO + +#define SIN_MASK (SIN_LENGHT - 1) +#define ENV_MASK (ENV_LENGHT - 1) +#define LFO_MASK (LFO_LENGHT - 1) + +#define ENV_STEP (96.0 / ENV_LENGHT) // ENV_MAX = 96 dB + +#define ENV_ATTACK ((ENV_LENGHT * 0) << ENV_LBITS) +#define ENV_DECAY ((ENV_LENGHT * 1) << ENV_LBITS) +#define ENV_END ((ENV_LENGHT * 2) << ENV_LBITS) + +#define MAX_OUT_BITS (SIN_HBITS + SIN_LBITS + 2) // Modulation = -4 <--> +4 +#define MAX_OUT ((1 << MAX_OUT_BITS) - 1) + +#define PG_CUT_OFF ((int) (78.0 / ENV_STEP)) +#define ENV_CUT_OFF ((int) (68.0 / ENV_STEP)) + +#define AR_RATE 399128 +#define DR_RATE 5514396 + +//#define AR_RATE 426136 +//#define DR_RATE (AR_RATE * 12) + +#define LFO_FMS_LBITS 9 // FIXED (LFO_FMS_BASE gives somethink as 1) +#define LFO_FMS_BASE ((int) (0.05946309436 * 0.0338 * (double) (1 << LFO_FMS_LBITS))) + +#define S0 0 // Stupid typo of the YM2612 +#define S1 2 +#define S2 1 +#define S3 3 + +inline void set_seg( slot_t& s, int seg ) { + s.env_xor = 0; + s.env_max = INT_MAX; + s.SEG = seg; + if ( seg & 4 ) { + s.env_xor = ENV_MASK; + s.env_max = ENV_MASK; + } +} + +typedef struct YM2612_tables_t +{ + short SIN_TAB [SIN_LENGHT]; // SINUS TABLE (offset into TL TABLE) + int LFOinc; // LFO step counter = pas d'incrementation du compteur-frequence du LFO + // plus le pas est grand, plus la frequence est grande + unsigned int AR_TAB [128]; // Attack rate table + unsigned int DR_TAB [96]; // Decay rate table + unsigned int DT_TAB [8] [32]; // Detune table + unsigned int SL_TAB [16]; // Substain level table + unsigned int NULL_RATE [32]; // Table for NULL rate + int LFO_INC_TAB [8]; // LFO step table + + int LFO_ENV_FREQ_UP [max_length * 2]; // Temporary calculated LFO FMS, AMS (adjusted for 11.8 dB) (interleved) + unsigned int FINC_TAB [2048]; // Frequency step table + + short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) + + unsigned int DECAY_TO_ATTACK [ENV_LENGHT]; // Conversion from decay to attack phase + short LFO_ENV_TAB [LFO_LENGHT]; // LFO AMS TABLE (adjusted for 11.8 dB) + short LFO_FREQ_TAB [LFO_LENGHT]; // LFO FMS TABLE + int TL_TAB [TL_LENGHT * 2]; // TOTAL LEVEL TABLE (positif and minus) +} tables_t; + +static const unsigned char DT_DEF_TAB [4 * 32] = { +// FD = 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +// FD = 1 + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + +// FD = 2 + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 16, 16, 16, + +// FD = 3 + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 20, 22, 22, 22, 22 +}; + +static const unsigned char FKEY_TAB [16] = { + 0, 0, 0, 0, + 0, 0, 0, 1, + 2, 3, 3, 3, + 3, 3, 3, 3 +}; + +static const unsigned int LFO_AMS_TAB [4] = { + 31, 4, 1, 0 +}; + +static const unsigned char LFO_FMS_TAB [8] = { + LFO_FMS_BASE * 0, LFO_FMS_BASE * 1, + LFO_FMS_BASE * 2, LFO_FMS_BASE * 3, + LFO_FMS_BASE * 4, LFO_FMS_BASE * 6, + LFO_FMS_BASE * 12, LFO_FMS_BASE * 24 +}; + +inline void YM2612_Special_Update() { } + + +struct YM2612_Impl +{ + enum { channel_count = YM2612_Emu::channel_count }; + + state_t YM2612; + int mute_mask; + tables_t g; + + void KEY_ON( channel_t&, int ); + void KEY_OFF( channel_t&, int ); + int SLOT_SET( int, int ); + int CHANNEL_SET( int, int ); + int YM_SET( int, int ); + + blargg_err_t set_rate( long sample_rate, long clock_rate ); + void reset(); + void write( int addr, int data ); + void run_timer( int ); + void run( YM2612_Emu::sample_t*, int count ); +}; + +void YM2612_Impl::KEY_ON( channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp == RELEASE) // la touche est-elle rel'chee ? + { + SL->Fcnt = 0; + + // Fix Ecco 2 splash sound + + SL->Ecnt = (g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK) & SL->ChgEnM; + SL->ChgEnM = ~0; + +// SL->Ecnt = g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK; +// SL->Ecnt = 0; + + SL->Einc = SL->EincA; + SL->Ecmp = ENV_DECAY; + SL->Ecurp = ATTACK; + } +} + + +void YM2612_Impl::KEY_OFF(channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp != RELEASE) // la touche est-elle appuyee ? + { + if (SL->Ecnt < ENV_DECAY) // attack phase ? + { + SL->Ecnt = (g.ENV_TAB [SL->Ecnt >> ENV_LBITS] << ENV_LBITS) + ENV_DECAY; + } + + SL->Einc = SL->EincR; + SL->Ecmp = ENV_END; + SL->Ecurp = RELEASE; + } +} + + +int YM2612_Impl::SLOT_SET( int Adr, int data ) +{ + int nch = Adr & 3; + if ( nch == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [nch + (Adr & 0x100 ? 3 : 0)]; + slot_t& sl = ch.SLOT [(Adr >> 2) & 3]; + + switch ( Adr & 0xF0 ) + { + case 0x30: + if ( (sl.MUL = (data & 0x0F)) != 0 ) sl.MUL <<= 1; + else sl.MUL = 1; + + sl.DT = (int*) g.DT_TAB [(data >> 4) & 7]; + + ch.SLOT [0].Finc = -1; + + break; + + case 0x40: + sl.TL = data & 0x7F; + + // SOR2 do a lot of TL adjustement and this fix R.Shinobi jump sound... + YM2612_Special_Update(); + +#if ((ENV_HBITS - 7) < 0) + sl.TLL = sl.TL >> (7 - ENV_HBITS); +#else + sl.TLL = sl.TL << (ENV_HBITS - 7); +#endif + + break; + + case 0x50: + sl.KSR_S = 3 - (data >> 6); + + ch.SLOT [0].Finc = -1; + + if (data &= 0x1F) sl.AR = (int*) &g.AR_TAB [data << 1]; + else sl.AR = (int*) &g.NULL_RATE [0]; + + sl.EincA = sl.AR [sl.KSR]; + if (sl.Ecurp == ATTACK) sl.Einc = sl.EincA; + break; + + case 0x60: + if ( (sl.AMSon = (data & 0x80)) != 0 ) sl.AMS = ch.AMS; + else sl.AMS = 31; + + if (data &= 0x1F) sl.DR = (int*) &g.DR_TAB [data << 1]; + else sl.DR = (int*) &g.NULL_RATE [0]; + + sl.EincD = sl.DR [sl.KSR]; + if (sl.Ecurp == DECAY) sl.Einc = sl.EincD; + break; + + case 0x70: + if (data &= 0x1F) sl.SR = (int*) &g.DR_TAB [data << 1]; + else sl.SR = (int*) &g.NULL_RATE [0]; + + sl.EincS = sl.SR [sl.KSR]; + if ((sl.Ecurp == SUBSTAIN) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincS; + break; + + case 0x80: + sl.SLL = g.SL_TAB [data >> 4]; + + sl.RR = (int*) &g.DR_TAB [((data & 0xF) << 2) + 2]; + + sl.EincR = sl.RR [sl.KSR]; + if ((sl.Ecurp == RELEASE) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincR; + break; + + case 0x90: + // SSG-EG envelope shapes : + /* + E At Al H + + 1 0 0 0 \\\\ + 1 0 0 1 \___ + 1 0 1 0 \/\/ + 1 0 1 1 \ + 1 1 0 0 //// + 1 1 0 1 / + 1 1 1 0 /\/\ + 1 1 1 1 /___ + + E = SSG-EG enable + At = Start negate + Al = Altern + H = Hold */ + + set_seg( sl, (data & 8) ? (data & 0x0F) : 0 ); + break; + } + + return 0; +} + + +int YM2612_Impl::CHANNEL_SET( int Adr, int data ) +{ + int num = Adr & 3; + if ( num == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [num + (Adr & 0x100 ? 3 : 0)]; + + switch ( Adr & 0xFC ) + { + case 0xA0: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x700) + data; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA4: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x0FF) + ((data & 0x07) << 8); + ch.FOCT [0] = (data & 0x38) >> 3; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA8: + if ( Adr < 0x100 ) { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x700) + data; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xAC: + if ( Adr < 0x100 ) { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x0FF) + ((data & 0x07) << 8); + YM2612.CHANNEL [2].FOCT [num] = (data & 0x38) >> 3; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xB0: + if ( ch.ALGO != (data & 7) ) { + // Fix VectorMan 2 heli sound (level 1) + YM2612_Special_Update(); + + ch.ALGO = data & 7; + + ch.SLOT [0].ChgEnM = 0; + ch.SLOT [1].ChgEnM = 0; + ch.SLOT [2].ChgEnM = 0; + ch.SLOT [3].ChgEnM = 0; + } + + ch.FB = 9 - ((data >> 3) & 7); // Real thing ? + +// if (ch.FB = ((data >> 3) & 7)) ch.FB = 9 - ch.FB; // Thunder force 4 (music stage 8), Gynoug, Aladdin bug sound... +// else ch.FB = 31; + break; + + case 0xB4: { + YM2612_Special_Update(); + + ch.LEFT = 0 - ((data >> 7) & 1); + ch.RIGHT = 0 - ((data >> 6) & 1); + + ch.AMS = LFO_AMS_TAB [(data >> 4) & 3]; + ch.FMS = LFO_FMS_TAB [data & 7]; + + for ( int i = 0; i < 4; i++ ) { + slot_t& sl = ch.SLOT [i]; + sl.AMS = (sl.AMSon ? ch.AMS : 31); + } + break; + } + } + + return 0; +} + + +int YM2612_Impl::YM_SET(int Adr, int data) +{ + switch ( Adr ) + { + case 0x22: + if (data & 8) // LFO enable + { + // Cool Spot music 1, LFO modified severals time which + // distord the sound, have to check that on a real genesis... + + g.LFOinc = g.LFO_INC_TAB [data & 7]; + } + else + { + g.LFOinc = YM2612.LFOcnt = 0; + } + break; + + case 0x24: + YM2612.TimerA = (YM2612.TimerA & 0x003) | (((int) data) << 2); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x25: + YM2612.TimerA = (YM2612.TimerA & 0x3fc) | (data & 3); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x26: + YM2612.TimerB = data; + + if (YM2612.TimerBL != (256 - YM2612.TimerB) << (4 + 12)) + { + YM2612.TimerBcnt = YM2612.TimerBL = (256 - YM2612.TimerB) << (4 + 12); + } + break; + + case 0x27: + // Parametre divers + // b7 = CSM MODE + // b6 = 3 slot mode + // b5 = reset b + // b4 = reset a + // b3 = timer enable b + // b2 = timer enable a + // b1 = load b + // b0 = load a + + if ((data ^ YM2612.Mode) & 0x40) + { + // We changed the channel 2 mode, so recalculate phase step + // This fix the punch sound in Street of Rage 2 + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; // recalculate phase step + } + +// if ((data & 2) && (YM2612.Status & 2)) YM2612.TimerBcnt = YM2612.TimerBL; +// if ((data & 1) && (YM2612.Status & 1)) YM2612.TimerAcnt = YM2612.TimerAL; + +// YM2612.Status &= (~data >> 4); // Reset du Status au cas ou c'est demande + YM2612.Status &= (~data >> 4) & (data >> 2); // Reset Status + + YM2612.Mode = data; + break; + + case 0x28: { + int nch = data & 3; + if ( nch == 3 ) + return 1; + if ( data & 4 ) + nch += 3; + channel_t& ch = YM2612.CHANNEL [nch]; + + YM2612_Special_Update(); + + if (data & 0x10) KEY_ON(ch, S0); // On appuie sur la touche pour le slot 1 + else KEY_OFF(ch, S0); // On rel'che la touche pour le slot 1 + if (data & 0x20) KEY_ON(ch, S1); // On appuie sur la touche pour le slot 3 + else KEY_OFF(ch, S1); // On rel'che la touche pour le slot 3 + if (data & 0x40) KEY_ON(ch, S2); // On appuie sur la touche pour le slot 2 + else KEY_OFF(ch, S2); // On rel'che la touche pour le slot 2 + if (data & 0x80) KEY_ON(ch, S3); // On appuie sur la touche pour le slot 4 + else KEY_OFF(ch, S3); // On rel'che la touche pour le slot 4 + break; + } + + case 0x2B: + if (YM2612.DAC ^ (data & 0x80)) YM2612_Special_Update(); + + YM2612.DAC = data & 0x80; // activation/desactivation du DAC + break; + } + + return 0; +} + +YM2612_Emu::YM2612_Emu() { + impl = NULL; +} + +YM2612_Emu::~YM2612_Emu() { + delete impl; +} + +blargg_err_t YM2612_Emu::set_rate( long rate, long clock ) +{ + if ( !impl ) { + impl = new YM2612_Impl; + if ( !impl ) + return "Out of memory"; + impl->mute_mask = 0; + } + return impl->set_rate( rate, clock ); +} + +blargg_err_t YM2612_Impl::set_rate( long Rate, long Clock ) +{ + require( Rate ); + require( Clock ); + + int i, j; + double x; + + memset(&YM2612, 0, sizeof(YM2612)); + + YM2612.Clock = Clock; + YM2612.Rate = Rate; + + // 144 = 12 * (prescale * 2) = 12 * 6 * 2 + // prescale set to 6 by default + + YM2612.Frequence = ((double) YM2612.Clock / (double) YM2612.Rate) / 144.0; + YM2612.TimerBase = (int) (YM2612.Frequence * 4096.0); + + // Tableau TL : + // [0 - 4095] = +output [4095 - ...] = +output overflow (fill with 0) + // [12288 - 16383] = -output [16384 - ...] = -output overflow (fill with 0) + + for(i = 0; i < TL_LENGHT; i++) + { + if (i >= PG_CUT_OFF) // YM2612 cut off sound after 78 dB (14 bits output ?) + { + g.TL_TAB [TL_LENGHT + i] = g.TL_TAB [i] = 0; + } + else + { + x = MAX_OUT; // Max output + x /= pow( 10.0, (ENV_STEP * i) / 20.0 ); // Decibel -> Voltage + + g.TL_TAB [i] = (int) x; + g.TL_TAB [TL_LENGHT + i] = -g.TL_TAB [i]; + } + } + + // Tableau SIN : + // g.SIN_TAB [x] [y] = sin(x) * y; + // x = phase and y = volume + + g.SIN_TAB [0] = g.SIN_TAB [SIN_LENGHT / 2] = PG_CUT_OFF; + + for(i = 1; i <= SIN_LENGHT / 4; i++) + { + x = sin(2.0 * PI * (double) (i) / (double) (SIN_LENGHT)); // Sinus + x = 20 * log10(1 / x); // convert to dB + + j = (int) (x / ENV_STEP); // Get TL range + + if (j > PG_CUT_OFF) j = (int) PG_CUT_OFF; + + g.SIN_TAB [i] = g.SIN_TAB [(SIN_LENGHT / 2) - i] = j; + g.SIN_TAB [(SIN_LENGHT / 2) + i] = g.SIN_TAB [SIN_LENGHT - i] = TL_LENGHT + j; + } + + // Tableau LFO (LFO wav) : + + for(i = 0; i < LFO_LENGHT; i++) + { + x = sin(2.0 * PI * (double) (i) / (double) (LFO_LENGHT)); // Sinus + x += 1.0; + x /= 2.0; // positive only + x *= 11.8 / ENV_STEP; // ajusted to MAX enveloppe modulation + + g.LFO_ENV_TAB [i] = (int) x; + + x = sin(2.0 * PI * (double) (i) / (double) (LFO_LENGHT)); // Sinus + x *= (double) ((1 << (LFO_HBITS - 1)) - 1); + + g.LFO_FREQ_TAB [i] = (int) x; + + } + + // Tableau Enveloppe : + // g.ENV_TAB [0] -> g.ENV_TAB [ENV_LENGHT - 1] = attack curve + // g.ENV_TAB [ENV_LENGHT] -> g.ENV_TAB [2 * ENV_LENGHT - 1] = decay curve + + for(i = 0; i < ENV_LENGHT; i++) + { + // Attack curve (x^8 - music level 2 Vectorman 2) + x = pow(((double) ((ENV_LENGHT - 1) - i) / (double) (ENV_LENGHT)), 8); + x *= ENV_LENGHT; + + g.ENV_TAB [i] = (int) x; + + // Decay curve (just linear) + x = pow(((double) (i) / (double) (ENV_LENGHT)), 1); + x *= ENV_LENGHT; + + g.ENV_TAB [ENV_LENGHT + i] = (int) x; + } + + g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state + + // Tableau pour la conversion Attack -> Decay and Decay -> Attack + + for(i = 0, j = ENV_LENGHT - 1; i < ENV_LENGHT; i++) + { + while (j && (g.ENV_TAB [j] < (unsigned) i)) j--; + + g.DECAY_TO_ATTACK [i] = j << ENV_LBITS; + } + + // Tableau pour le Substain Level + + for(i = 0; i < 15; i++) + { + x = i * 3; // 3 and not 6 (Mickey Mania first music for test) + x /= ENV_STEP; + + j = (int) x; + j <<= ENV_LBITS; + + g.SL_TAB [i] = j + ENV_DECAY; + } + + j = ENV_LENGHT - 1; // special case : volume off + j <<= ENV_LBITS; + g.SL_TAB [15] = j + ENV_DECAY; + + // Tableau Frequency Step + + for(i = 0; i < 2048; i++) + { + x = (double) (i) * YM2612.Frequence; + +#if ((SIN_LBITS + SIN_HBITS - (21 - 7)) < 0) + x /= (double) (1 << ((21 - 7) - SIN_LBITS - SIN_HBITS)); +#else + x *= (double) (1 << (SIN_LBITS + SIN_HBITS - (21 - 7))); +#endif + + x /= 2.0; // because MUL = value * 2 + + g.FINC_TAB [i] = (unsigned int) x; + } + + // Tableaux Attack & Decay Rate + + for(i = 0; i < 4; i++) + { + g.AR_TAB [i] = 0; + g.DR_TAB [i] = 0; + } + + for(i = 0; i < 60; i++) + { + x = YM2612.Frequence; + + x *= 1.0 + ((i & 3) * 0.25); // bits 0-1 : x1.00, x1.25, x1.50, x1.75 + x *= (double) (1 << ((i >> 2))); // bits 2-5 : shift bits (x2^0 - x2^15) + x *= (double) (ENV_LENGHT << ENV_LBITS); // on ajuste pour le tableau g.ENV_TAB + + g.AR_TAB [i + 4] = (unsigned int) (x / AR_RATE); + g.DR_TAB [i + 4] = (unsigned int) (x / DR_RATE); + } + + for(i = 64; i < 96; i++) + { + g.AR_TAB [i] = g.AR_TAB [63]; + g.DR_TAB [i] = g.DR_TAB [63]; + + g.NULL_RATE [i - 64] = 0; + } + + // Tableau Detune + + for(i = 0; i < 4; i++) + { + for (j = 0; j < 32; j++) + { +#if ((SIN_LBITS + SIN_HBITS - 21) < 0) + x = (double) DT_DEF_TAB [(i << 5) + j] * YM2612.Frequence / (double) (1 << (21 - SIN_LBITS - SIN_HBITS)); +#else + x = (double) DT_DEF_TAB [(i << 5) + j] * YM2612.Frequence * (double) (1 << (SIN_LBITS + SIN_HBITS - 21)); +#endif + + g.DT_TAB [i + 0] [j] = (int) x; + g.DT_TAB [i + 4] [j] = (int) -x; + } + } + + // Tableau LFO + + j = YM2612.Rate; + + g.LFO_INC_TAB [0] = (unsigned int) (3.98 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [1] = (unsigned int) (5.56 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [2] = (unsigned int) (6.02 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [3] = (unsigned int) (6.37 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [4] = (unsigned int) (6.88 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [5] = (unsigned int) (9.63 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [6] = (unsigned int) (48.1 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + g.LFO_INC_TAB [7] = (unsigned int) (72.2 * (double) (1 << (LFO_HBITS + LFO_LBITS)) / j); + + reset(); + + return blargg_success; +} + +void YM2612_Emu::reset() { + impl->reset(); +} + +void YM2612_Impl::reset() +{ + YM2612.LFOcnt = 0; + YM2612.TimerA = 0; + YM2612.TimerAL = 0; + YM2612.TimerAcnt = 0; + YM2612.TimerB = 0; + YM2612.TimerBL = 0; + YM2612.TimerBcnt = 0; + YM2612.DAC = 0; + + YM2612.Status = 0; + + YM2612.OPNAadr = 0; + YM2612.OPNBadr = 0; + + int i; + for ( i = 0; i < channel_count; i++ ) + { + channel_t& ch = YM2612.CHANNEL [i]; + + ch.LEFT = ~0; + ch.RIGHT = ~0; + ch.ALGO = 0;; + ch.FB = 31; + ch.FMS = 0; + ch.AMS = 0; + + for ( int j = 0 ;j < 4 ; j++ ) + { + ch.S0_OUT [j] = 0; + ch.FNUM [j] = 0; + ch.FOCT [j] = 0; + ch.KC [j] = 0; + + ch.SLOT [j].Fcnt = 0; + ch.SLOT [j].Finc = 0; + ch.SLOT [j].Ecnt = ENV_END; // Put it at the end of Decay phase... + ch.SLOT [j].Einc = 0; + ch.SLOT [j].Ecmp = 0; + ch.SLOT [j].Ecurp = RELEASE; + + ch.SLOT [j].ChgEnM = 0; + } + } + + for ( i = 0; i < 0x100; i++ ) { + YM2612.REG [0] [i] = -1; + YM2612.REG [1] [i] = -1; + } + + for ( i = 0xB6; i >= 0xB4; i-- ) { + write( 0, i ); + write( 2, i ); + write( 1, 0xC0 ); + write( 3, 0xC0 ); + } + + for ( i = 0xB2; i >= 0x22; i-- ) { + write( 0, i ); + write( 2, i ); + write( 1, 0 ); + write( 3, 0 ); + } + + write( 0, 0x2A ); + write( 1, 0x80 ); +} + +inline void YM2612_Impl::write( int addr, int data ) +{ + require( (unsigned) addr < 4 ); + require( (unsigned) data <= 0xFF ); + + switch( addr ) + { + case 0: + YM2612.OPNAadr = data; + break; + + case 2: + YM2612.OPNBadr = data; + break; + + case 1: { + int opn_addr = YM2612.OPNAadr; + if ( opn_addr < 0x30 ) + { + YM2612.REG [0] [opn_addr] = data; + YM_SET( opn_addr, data ); + } + else if ( YM2612.REG [0] [opn_addr] != data ) { + YM2612.REG [0] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr, data ); + else + CHANNEL_SET( opn_addr, data ); + } + break; + } + + case 3: { + int opn_addr = YM2612.OPNBadr; + if ( opn_addr >= 0x30 && YM2612.REG [1] [opn_addr] != data ) + { + YM2612.REG [1] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr + 0x100, data ); + else + CHANNEL_SET( opn_addr + 0x100, data ); + } + break; + } + } +} + +void YM2612_Emu::write( int addr, int data ) { + impl->write( addr, data ); +} + +inline void YM2612_Impl::run_timer( int length ) +{ + int i = YM2612.TimerBase * length; + + if (YM2612.Mode & 1) // Timer A ON ? + { +// if ((YM2612.TimerAcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerAcnt -= i) <= 0) + { + // timer a overflow + + YM2612.Status |= (YM2612.Mode & 0x04) >> 2; + YM2612.TimerAcnt += YM2612.TimerAL; + + if (YM2612.Mode & 0x80) { + KEY_ON( YM2612.CHANNEL [2], 0 ); + KEY_ON( YM2612.CHANNEL [2], 1 ); + KEY_ON( YM2612.CHANNEL [2], 2 ); + KEY_ON( YM2612.CHANNEL [2], 3 ); + } + } + } + + if (YM2612.Mode & 2) // Timer B ON ? + { +// if ((YM2612.TimerBcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerBcnt -= i) <= 0) + { + // timer b overflow + YM2612.Status |= (YM2612.Mode & 0x08) >> 2; + YM2612.TimerBcnt += YM2612.TimerBL; + } + } +} + +void YM2612_Emu::run_timer( int length ) { + impl->run_timer( length ); +} + +void YM2612_Emu::mute_voices( int mask ) { + impl->mute_mask = mask; +} + +#include BLARGG_ENABLE_OPTIMIZER + +static const int no_lfo [2] = { 0, 0 }; + +static void update_env( channel_t& ch ) +{ + slot_t* sl = ch.SLOT; + for ( int n = 4; n--; sl++ ) + { + if ( (sl->Ecnt += sl->Einc) < sl->Ecmp ) + continue; + + switch ( sl->Ecurp ) { + case 0: + // Env_Attack_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = ENV_DECAY; + + sl->Einc = sl->EincD; + sl->Ecmp = sl->SLL; + sl->Ecurp = DECAY; + break; + + case 1: + // Env_Decay_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = sl->SLL; + + sl->Einc = sl->EincS; + sl->Ecmp = ENV_END; + sl->Ecurp = SUBSTAIN; + break; + + case 2: + // Env_Substain_Next(slot_t *SL) + if (sl->SEG & 8) // SSG envelope type + { + int release = sl->SEG & 1; + + if ( !release ) + { + // re KEY ON + + // sl->Fcnt = 0; + // sl->ChgEnM = ~0; + + sl->Ecnt = 0; + sl->Einc = sl->EincA; + sl->Ecmp = ENV_DECAY; + sl->Ecurp = ATTACK; + } + + set_seg( *sl, (sl->SEG << 1) & 4 ); + + if ( !release ) + break; + } + // fall through + + case 3: + // Env_Release_Next + sl->Ecnt = ENV_END; + sl->Einc = 0; + sl->Ecmp = ENV_END + 1; + break; + + // default: no op + } + } +} + +template +struct ym2612_update_chan { + static void func( const tables_t& g, channel_t&, YM2612_Emu::sample_t*, int ); +}; + +typedef void (*ym2612_update_chan_t)( const tables_t& g, channel_t&, YM2612_Emu::sample_t*, int ); + +template +void ym2612_update_chan::func( const tables_t& g, channel_t& ch, YM2612_Emu::sample_t* buf, int length ) +{ + int not_end = ch.SLOT [S3].Ecnt - ENV_END; + + // algo is a compile-time constant, so all conditions based on it are resolved during compilation + + // special cases + if ( algo == 7 ) + not_end |= ch.SLOT [S0].Ecnt - ENV_END; + if ( algo >= 5 ) + not_end |= ch.SLOT [S2].Ecnt - ENV_END; + if ( algo >= 4 ) + not_end |= ch.SLOT [S1].Ecnt - ENV_END; + + const int CH_LEFT = ch.LEFT; + const int CH_RIGHT = ch.RIGHT; + + int CH_S0_OUT_1 = ch.S0_OUT [1]; + + if ( !not_end ) + return; + + int in0 = ch.SLOT [S0].Fcnt; + int in1 = ch.SLOT [S1].Fcnt; + int in2 = ch.SLOT [S2].Fcnt; + int in3 = ch.SLOT [S3].Fcnt; + + const int* lfo_freq_env = g.LFO_ENV_FREQ_UP; + int lfo_step = 2; + if ( !g.LFOinc ) { + lfo_step = 0; + lfo_freq_env = no_lfo; + } + + const int* const TL_TAB = g.TL_TAB; // cache + +#define SINT( i, o ) (TL_TAB [g.SIN_TAB [(i)] + (o)]) + + const short* const ENV_TAB = g.ENV_TAB; // cache + + goto first_iter; + + while ( --length ) + { + // update envelope + update_env( ch ); + first_iter: + + // calc envelope + int const env_LFO = lfo_freq_env [0]; + + #define CALC_EN( x ) \ + int temp##x = ENV_TAB [ch.SLOT [S##x].Ecnt >> ENV_LBITS] + ch.SLOT [S##x].TLL; \ + int en##x = ((temp##x ^ ch.SLOT [S##x].env_xor) + (env_LFO >> ch.SLOT [S##x].AMS)) & \ + ((temp##x - ch.SLOT [S##x].env_max) >> 31); + + CALC_EN( 0 ) + CALC_EN( 1 ) + CALC_EN( 2 ) + CALC_EN( 3 ) + + // feedback + int CH_S0_OUT_0 = ch.S0_OUT [0]; + { + int temp = in0 + ((CH_S0_OUT_0 + CH_S0_OUT_1) >> ch.FB); + CH_S0_OUT_1 = CH_S0_OUT_0; + CH_S0_OUT_0 = SINT( (temp >> SIN_LBITS) & SIN_MASK, en0 ); + } + + int CH_OUTd; + if ( algo == 0 ) { + int temp = in1 + CH_S0_OUT_1; + temp = in2 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 1 ) { + int temp = in2 + CH_S0_OUT_1 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 2 ) { + int temp = in2 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + CH_S0_OUT_1 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 3 ) { + int temp = in1 + CH_S0_OUT_1; + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 4 ) { + int temp = in3 + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ); + //DO_LIMIT + } + else if ( algo == 5 ) { + int temp = CH_S0_OUT_1; + CH_OUTd = SINT( ((in3 + temp) >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + temp) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( ((in2 + temp) >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 6 ) { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 7 ) { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ) + CH_S0_OUT_1; + //DO_LIMIT + } + + CH_OUTd >>= MAX_OUT_BITS - output_bits + 2; + + // update phase + const unsigned freq_LFO = ((ch.FMS * lfo_freq_env [1]) >> (LFO_HBITS - 1 + 1)) + (1L << (LFO_FMS_LBITS - 1)); + in0 += (ch.SLOT [S0].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in1 += (ch.SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + + int t0 = buf [0] + (CH_OUTd & CH_LEFT); + int t1 = buf [1] + (CH_OUTd & CH_RIGHT); + + ch.S0_OUT [0] = CH_S0_OUT_0; + buf [0] = t0; + buf [1] = t1; + buf += 2; + + lfo_freq_env += lfo_step; + } + + ch.S0_OUT [1] = CH_S0_OUT_1; + + ch.SLOT [S0].Fcnt = in0; + ch.SLOT [S1].Fcnt = in1; + ch.SLOT [S2].Fcnt = in2; + ch.SLOT [S3].Fcnt = in3; + + update_env( ch ); +} + +static const ym2612_update_chan_t UPDATE_CHAN [8] = { + &ym2612_update_chan<0>::func, + &ym2612_update_chan<1>::func, + &ym2612_update_chan<2>::func, + &ym2612_update_chan<3>::func, + &ym2612_update_chan<4>::func, + &ym2612_update_chan<5>::func, + &ym2612_update_chan<6>::func, + &ym2612_update_chan<7>::func +}; + +void YM2612_Impl::run( YM2612_Emu::sample_t* buf, int length ) +{ + require( length % 2 == 0 ); // generates pairs of samples + require( length <= max_length * 2 ); + + length >>= 1; + + // Mise à jour des pas des compteurs-frequences s'ils ont ete modifies + + for ( int chi = 0; chi < channel_count; chi++ ) + { + channel_t& ch = YM2612.CHANNEL [chi]; + if ( ch.SLOT [0].Finc != -1 ) + continue; + + int i2 = 0; + if ( chi == 2 && (YM2612.Mode & 0x40) ) + i2 = 2; + + for ( int i = 0; i < 4; i++ ) + { + // static int seq [4] = { 2, 1, 3, 0 }; + // if ( i2 ) i2 = seq [i]; + + slot_t& sl = ch.SLOT [i]; + int finc = g.FINC_TAB [ch.FNUM [i2]] >> (7 - ch.FOCT [i2]); + int ksr = ch.KC [i2] >> sl.KSR_S; // keycode attenuation + sl.Finc = (finc + sl.DT [ch.KC [i2]]) * sl.MUL; + if (sl.KSR != ksr) // si le KSR a change alors + { // les differents taux pour l'enveloppe sont mis à jour + sl.KSR = ksr; + + sl.EincA = sl.AR [ksr]; + sl.EincD = sl.DR [ksr]; + sl.EincS = sl.SR [ksr]; + sl.EincR = sl.RR [ksr]; + + if (sl.Ecurp == ATTACK) { + sl.Einc = sl.EincA; + } + else if (sl.Ecurp == DECAY) { + sl.Einc = sl.EincD; + } + else if (sl.Ecnt < ENV_END) { + if (sl.Ecurp == SUBSTAIN) + sl.Einc = sl.EincS; + else if (sl.Ecurp == RELEASE) + sl.Einc = sl.EincR; + } + } + + if ( i2 ) + i2 = (i2 ^ 2) ^ (i2 >> 1); + } + } + + if ( g.LFOinc ) + { + // Precalcul LFO wav + for(int i = 0; i < length; i++) + { + int j = ((YM2612.LFOcnt += g.LFOinc) >> LFO_LBITS) & LFO_MASK; + + g.LFO_ENV_FREQ_UP [i * 2] = g.LFO_ENV_TAB [j]; + g.LFO_ENV_FREQ_UP [i * 2 + 1] = g.LFO_FREQ_TAB [j]; + } + } + + for ( int i = 0; i < channel_count; i++ ) + if ( !(mute_mask & (1 << i)) && (i != 5 || !YM2612.DAC) ) + UPDATE_CHAN [YM2612.CHANNEL [i].ALGO]( g, YM2612.CHANNEL [i], buf, length ); +} + +void YM2612_Emu::run( sample_t* buf, int length ) { + impl->run( buf, length ); +} + diff -r feeda0dda3ce -r 252843aac42f Plugins/Input/console/ym2612.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugins/Input/console/ym2612.h Tue Nov 01 19:57:26 2005 -0800 @@ -0,0 +1,38 @@ + +// Sega Genesis YM2612 FM Sound Chip Emulator + +// Game_Music_Emu 0.2.4. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. +// Copyright (C) 2002 Stéphane Dallongeville + +#ifndef YM2612_H +#define YM2612_H + +#include "blargg_common.h" + +struct YM2612_Impl; + +class YM2612_Emu { +public: + YM2612_Emu(); + ~YM2612_Emu(); + + blargg_err_t set_rate( long sample_rate, long clock_rate ); + + void reset(); + + enum { channel_count = 6 }; + void mute_voices( int mask ); + + void write( int addr, int data ); + + void run_timer( int ); + + typedef BOOST::int16_t sample_t; + void run( sample_t*, int count ); + +private: + YM2612_Impl* impl; +}; + +#endif +