diff options
Diffstat (limited to 'plugins/gme/game-music-emu-0.6pre/gme/Sms_Apu.cpp')
-rw-r--r-- | plugins/gme/game-music-emu-0.6pre/gme/Sms_Apu.cpp | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/plugins/gme/game-music-emu-0.6pre/gme/Sms_Apu.cpp b/plugins/gme/game-music-emu-0.6pre/gme/Sms_Apu.cpp new file mode 100644 index 00000000..5ad62e43 --- /dev/null +++ b/plugins/gme/game-music-emu-0.6pre/gme/Sms_Apu.cpp @@ -0,0 +1,371 @@ +// Sms_Snd_Emu 0.1.1. http://www.slack.net/~ant/ + +#include "Sms_Apu.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const noise_osc = 3; + +void Sms_Apu::volume( double vol ) +{ + vol *= 0.85 / osc_count / 64; + norm_synth.volume( vol ); + fast_synth.volume( vol ); +} + +void Sms_Apu::treble_eq( blip_eq_t const& eq ) +{ + norm_synth.treble_eq( eq ); + fast_synth.treble_eq( eq ); +} + +inline int Sms_Apu::calc_output( int i ) const +{ + int flags = ggstereo >> i; + return (flags >> 3 & 2) | (flags & 1); +} + +void Sms_Apu::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) i < osc_count ); // fails if you pass invalid osc index + + if ( center ) + { + unsigned const divisor = 16384 * 16 * 2; + min_tone_period = ((unsigned) center->clock_rate() + divisor/2) / divisor; + } + + if ( !center || !left || !right ) + { + left = center; + right = center; + } + + Osc& o = oscs [i]; + o.outputs [0] = NULL; + o.outputs [1] = right; + o.outputs [2] = left; + o.outputs [3] = center; + o.output = o.outputs [calc_output( i )]; +} + +void Sms_Apu::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + for ( int i = osc_count; --i >= 0; ) + set_output( i, c, l, r ); +} + +static inline unsigned fibonacci_to_galois_lfsr( unsigned fibonacci, int width ) +{ + unsigned galois = 0; + while ( --width >= 0 ) + { + galois = (galois << 1) | (fibonacci & 1); + fibonacci >>= 1; + } + return galois; +} + +void Sms_Apu::reset( unsigned feedback, int noise_width ) +{ + last_time = 0; + latch = 0; + ggstereo = 0; + + // Calculate noise feedback values + if ( !feedback || !noise_width ) + { + feedback = 0x0009; + noise_width = 16; + } + looped_feedback = 1 << (noise_width - 1); + noise_feedback = fibonacci_to_galois_lfsr( feedback, noise_width ); + + // Reset oscs + for ( int i = osc_count; --i >= 0; ) + { + Osc& o = oscs [i]; + o.output = NULL; + o.last_amp = 0; + o.delay = 0; + o.phase = 0; + o.period = 0; + o.volume = 15; // silent + } + + oscs [noise_osc].phase = 0x8000; + write_ggstereo( 0, 0xFF ); +} + +Sms_Apu::Sms_Apu() +{ + min_tone_period = 7; + + // Clear outputs to NULL FIRST + ggstereo = 0; + set_output( NULL ); + + volume( 1.0 ); + reset(); +} + +void Sms_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); + if ( end_time <= last_time ) + return; + + // Synthesize each oscillator + for ( int idx = osc_count; --idx >= 0; ) + { + Osc& osc = oscs [idx]; + int vol = 0; + int amp = 0; + + // Determine what will be generated + Blip_Buffer* const out = osc.output; + if ( out ) + { + // volumes [i] ~= 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) + static unsigned char const volumes [16] = { + 64, 50, 40, 32, 25, 20, 16, 13, 10, 8, 6, 5, 4, 3, 2, 0 + }; + + vol = volumes [osc.volume]; + amp = (osc.phase & 1) * vol; + + // Square freq above 16 kHz yields constant amplitude at half volume + if ( idx != noise_osc && osc.period < min_tone_period ) + { + amp = vol >> 1; + vol = 0; + } + + // Update amplitude + int delta = amp - osc.last_amp; + if ( delta ) + { + osc.last_amp = amp; + norm_synth.offset( last_time, delta, out ); + out->set_modified(); + } + } + + // Generate wave + blip_time_t time = last_time + osc.delay; + if ( time < end_time ) + { + // Calculate actual period + int period = osc.period; + if ( idx == noise_osc ) + { + period = 0x20 << (period & 3); + if ( period == 0x100 ) + period = oscs [2].period * 2; + } + period *= 0x10; + if ( !period ) + period = 0x10; + + // Maintain phase when silent + int phase = osc.phase; + if ( !vol ) + { + int count = (end_time - time + period - 1) / period; + time += count * period; + if ( idx != noise_osc ) // TODO: maintain noise LFSR phase? + phase ^= count & 1; + } + else + { + int delta = amp * 2 - vol; + + if ( idx != noise_osc ) + { + // Square + do + { + delta = -delta; + norm_synth.offset( time, delta, out ); + time += period; + } + while ( time < end_time ); + phase = (delta >= 0); + } + else + { + // Noise + unsigned const feedback = (osc.period & 4 ? noise_feedback : looped_feedback); + do + { + unsigned changed = phase + 1; + phase = ((phase & 1) * feedback) ^ (phase >> 1); + if ( changed & 2 ) // true if bits 0 and 1 differ + { + delta = -delta; + fast_synth.offset_inline( time, delta, out ); + } + time += period; + } + while ( time < end_time ); + check( phase ); + } + osc.last_amp = (phase & 1) * vol; + out->set_modified(); + } + osc.phase = phase; + } + osc.delay = time - end_time; + } + last_time = end_time; +} + +void Sms_Apu::write_ggstereo( blip_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + ggstereo = data; + + for ( int i = osc_count; --i >= 0; ) + { + Osc& osc = oscs [i]; + + Blip_Buffer* old = osc.output; + osc.output = osc.outputs [calc_output( i )]; + if ( osc.output != old ) + { + int delta = -osc.last_amp; + if ( delta ) + { + osc.last_amp = 0; + if ( old ) + { + old->set_modified(); + fast_synth.offset( last_time, delta, old ); + } + } + } + } +} + +void Sms_Apu::write_data( blip_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + if ( data & 0x80 ) + latch = data; + + // We want the raw values written so our save state format can be + // as close to hardware as possible and unspecific to any emulator. + int idx = latch >> 5 & 3; + Osc& osc = oscs [idx]; + if ( latch & 0x10 ) + { + osc.volume = data & 0x0F; + } + else + { + if ( idx == noise_osc ) + osc.phase = 0x8000; // reset noise LFSR + + // Replace high 6 bits/low 4 bits of register with data + int lo = osc.period; + int hi = data << 4; + if ( idx == noise_osc || (data & 0x80) ) + { + hi = lo; + lo = data; + } + osc.period = (hi & 0x3F0) | (lo & 0x00F); + } +} + +void Sms_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + last_time -= end_time; + assert( last_time >= 0 ); +} + +#if SMS_APU_CUSTOM_STATE + #define REFLECT( x, y ) (save ? (io->y) = (x) : (x) = (io->y) ) +#else + #define REFLECT( x, y ) (save ? set_val( io->y, x ) : (void) ((x) = get_val( io->y ))) + + static unsigned get_val( byte const p [] ) + { + return p [3] * 0x1000000 + p [2] * 0x10000 + p [1] * 0x100 + p [0]; + } + + static void set_val( byte p [], unsigned n ) + { + p [0] = (byte) (n ); + p [1] = (byte) (n >> 8); + p [2] = (byte) (n >> 16); + p [3] = (byte) (n >> 24); + } +#endif + +inline const char* Sms_Apu::save_load( sms_apu_state_t* io, bool save ) +{ + #if !SMS_APU_CUSTOM_STATE + assert( sizeof (sms_apu_state_t) == 128 ); + #endif + + // Format of data, where later format is incompatible with earlier + int format = io->format0; + REFLECT( format, format ); + if ( format != io->format0 ) + return "Unsupported sound save state format"; + + // Version of data, where later versions just add fields to the end + int version = 0; + REFLECT( version, version ); + + REFLECT( latch, latch ); + REFLECT( ggstereo, ggstereo ); + + for ( int i = osc_count; --i >= 0; ) + { + Osc& osc = oscs [i]; + REFLECT( osc.period, periods [i] ); + REFLECT( osc.volume, volumes [i] ); + REFLECT( osc.delay, delays [i] ); + REFLECT( osc.phase, phases [i] ); + } + + return 0; +} + +void Sms_Apu::save_state( sms_apu_state_t* out ) +{ + save_load( out, true ); + #if !SMS_APU_CUSTOM_STATE + memset( out->unused, 0, sizeof out->unused ); + #endif +} + +blargg_err_t Sms_Apu::load_state( sms_apu_state_t const& in ) +{ + RETURN_ERR( save_load( CONST_CAST(sms_apu_state_t*,&in), false ) ); + write_ggstereo( 0, ggstereo ); + return blargg_ok; +} |