summaryrefslogtreecommitdiff
path: root/plugins/gme/game-music-emu-0.6pre/gme/Sap_Apu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/gme/game-music-emu-0.6pre/gme/Sap_Apu.cpp')
-rw-r--r--plugins/gme/game-music-emu-0.6pre/gme/Sap_Apu.cpp339
1 files changed, 339 insertions, 0 deletions
diff --git a/plugins/gme/game-music-emu-0.6pre/gme/Sap_Apu.cpp b/plugins/gme/game-music-emu-0.6pre/gme/Sap_Apu.cpp
new file mode 100644
index 00000000..23633dc4
--- /dev/null
+++ b/plugins/gme/game-music-emu-0.6pre/gme/Sap_Apu.cpp
@@ -0,0 +1,339 @@
+// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/
+
+#include "Sap_Apu.h"
+
+/* Copyright (C) 2006-2008 Shay Green. This module is free software; you
+can redistribute it and/or modify it under the terms of the GNU Lesser
+General Public License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version. This
+module is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details. You should have received a copy of the GNU Lesser General Public
+License along with this module; if not, write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include "blargg_source.h"
+
+int const max_frequency = 12000; // pure waves above this frequency are silenced
+
+static void gen_poly( unsigned mask, int count, byte out [] )
+{
+ unsigned n = 1;
+ do
+ {
+ int bits = 0;
+ int b = 0;
+ do
+ {
+ // implemented using "Galios configuration"
+ bits |= (n & 1) << b;
+ n = (n >> 1) ^ (mask * (n & 1));
+ }
+ while ( b++ < 7 );
+ *out++ = bits;
+ }
+ while ( --count );
+}
+
+// poly5
+int const poly5_len = (1 << 5) - 1;
+unsigned const poly5_mask = (1U << poly5_len) - 1;
+unsigned const poly5 = 0x167C6EA1;
+
+inline unsigned run_poly5( unsigned in, int shift )
+{
+ return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
+}
+
+#define POLY_MASK( width, tap1, tap2 ) \
+ ((1U << (width - 1 - tap1)) | (1U << (width - 1 - tap2)))
+
+Sap_Apu_Impl::Sap_Apu_Impl()
+{
+ gen_poly( POLY_MASK( 4, 1, 0 ), sizeof poly4, poly4 );
+ gen_poly( POLY_MASK( 9, 5, 0 ), sizeof poly9, poly9 );
+ gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
+
+ if ( 0 ) // comment out to recauculate poly5 constant
+ {
+ byte poly5 [4];
+ gen_poly( POLY_MASK( 5, 2, 0 ), sizeof poly5, poly5 );
+ unsigned n = poly5 [3] * 0x1000000 + poly5 [2] * 0x10000 +
+ poly5 [1] * 0x100 + poly5 [0];
+ unsigned rev = n & 1;
+ for ( int i = 1; i < poly5_len; i++ )
+ rev |= (n >> i & 1) << (poly5_len - i);
+ dprintf( "poly5: 0x%08lX\n", rev );
+ }
+}
+
+void Sap_Apu::set_output( Blip_Buffer* b )
+{
+ for ( int i = 0; i < osc_count; ++i )
+ set_output( i, b );
+}
+
+Sap_Apu::Sap_Apu()
+{
+ impl = NULL;
+ set_output( NULL );
+}
+
+void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
+{
+ impl = new_impl;
+ last_time = 0;
+ poly5_pos = 0;
+ poly4_pos = 0;
+ polym_pos = 0;
+ control = 0;
+
+ for ( int i = 0; i < osc_count; i++ )
+ memset( &oscs [i], 0, offsetof (osc_t,output) );
+}
+
+inline void Sap_Apu::calc_periods()
+{
+ // 15/64 kHz clock
+ int divider = 28;
+ if ( this->control & 1 )
+ divider = 114;
+
+ for ( int i = 0; i < osc_count; i++ )
+ {
+ osc_t* const osc = &oscs [i];
+
+ int const osc_reload = osc->regs [0]; // cache
+ int period = (osc_reload + 1) * divider;
+ static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
+ if ( this->control & fast_bits [i] )
+ {
+ period = osc_reload + 4;
+ if ( i & 1 )
+ {
+ period = osc_reload * 0x100 + osc [-1].regs [0] + 7;
+ if ( !(this->control & fast_bits [i - 1]) )
+ period = (period - 6) * divider;
+
+ if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
+ dprintf( "Use of slave channel in 16-bit mode not supported\n" );
+ }
+ }
+ osc->period = period;
+ }
+}
+
+void Sap_Apu::run_until( blip_time_t end_time )
+{
+ calc_periods();
+ Sap_Apu_Impl* const impl = this->impl; // cache
+
+ // 17/9-bit poly selection
+ byte const* polym = impl->poly17;
+ int polym_len = poly17_len;
+ if ( this->control & 0x80 )
+ {
+ polym_len = poly9_len;
+ polym = impl->poly9;
+ }
+ polym_pos %= polym_len;
+
+ for ( int i = 0; i < osc_count; i++ )
+ {
+ osc_t* const osc = &oscs [i];
+ blip_time_t time = last_time + osc->delay;
+ blip_time_t const period = osc->period;
+
+ // output
+ Blip_Buffer* output = osc->output;
+ if ( output )
+ {
+ int const osc_control = osc->regs [1]; // cache
+ int volume = (osc_control & 0x0F) * 2;
+ if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
+ ((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
+ {
+ if ( !(osc_control & 0x10) )
+ volume >>= 1; // inaudible frequency = half volume
+
+ int delta = volume - osc->last_amp;
+ if ( delta )
+ {
+ osc->last_amp = volume;
+ output->set_modified();
+ impl->synth.offset( last_time, delta, output );
+ }
+
+ // TODO: doesn't maintain high pass flip-flop (very minor issue)
+ }
+ else
+ {
+ // high pass
+ static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
+ blip_time_t period2 = 0; // unused if no high pass
+ blip_time_t time2 = end_time;
+ if ( this->control & hipass_bits [i] )
+ {
+ period2 = osc [2].period;
+ time2 = last_time + osc [2].delay;
+ if ( osc->invert )
+ {
+ // trick inner wave loop into inverting output
+ osc->last_amp -= volume;
+ volume = -volume;
+ }
+ }
+
+ if ( time < end_time || time2 < end_time )
+ {
+ // poly source
+ static byte const poly1 [] = { 0x55, 0x55 }; // square wave
+ byte const* poly = poly1;
+ int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
+ int poly_pos = osc->phase & 1;
+ int poly_inc = 1;
+ if ( !(osc_control & 0x20) )
+ {
+ poly = polym;
+ poly_len = polym_len;
+ poly_pos = polym_pos;
+ if ( osc_control & 0x40 )
+ {
+ poly = impl->poly4;
+ poly_len = poly4_len;
+ poly_pos = poly4_pos;
+ }
+ poly_inc = period % poly_len;
+ poly_pos = (poly_pos + osc->delay) % poly_len;
+ }
+ poly_inc -= poly_len; // allows more optimized inner loop below
+
+ // square/poly5 wave
+ unsigned wave = poly5;
+ check( poly5 & 1 ); // low bit is set for pure wave
+ int poly5_inc = 0;
+ if ( !(osc_control & 0x80) )
+ {
+ wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
+ poly5_inc = period % poly5_len;
+ }
+
+ output->set_modified();
+
+ // Run wave and high pass interleved with each catching up to the other.
+ // Disabled high pass has no performance effect since inner wave loop
+ // makes no compromise for high pass, and only runs once in that case.
+ int osc_last_amp = osc->last_amp;
+ do
+ {
+ // run high pass
+ if ( time2 < time )
+ {
+ int delta = -osc_last_amp;
+ if ( volume < 0 )
+ delta += volume;
+ if ( delta )
+ {
+ osc_last_amp += delta - volume;
+ volume = -volume;
+ impl->synth.offset( time2, delta, output );
+ }
+ }
+ while ( time2 <= time ) // must advance *past* time to avoid hang
+ time2 += period2;
+
+ // run wave
+ blip_time_t end = end_time;
+ if ( end > time2 )
+ end = time2;
+ while ( time < end )
+ {
+ if ( wave & 1 )
+ {
+ int amp = volume * (poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
+ if ( (poly_pos += poly_inc) < 0 )
+ poly_pos += poly_len;
+ int delta = amp - osc_last_amp;
+ if ( delta )
+ {
+ osc_last_amp = amp;
+ impl->synth.offset( time, delta, output );
+ }
+ }
+ wave = run_poly5( wave, poly5_inc );
+ time += period;
+ }
+ }
+ while ( time < end_time || time2 < end_time );
+
+ osc->phase = poly_pos;
+ osc->last_amp = osc_last_amp;
+ }
+
+ osc->invert = 0;
+ if ( volume < 0 )
+ {
+ // undo inversion trickery
+ osc->last_amp -= volume;
+ osc->invert = 1;
+ }
+ }
+ }
+
+ // maintain divider
+ blip_time_t remain = end_time - time;
+ if ( remain > 0 )
+ {
+ int count = (remain + period - 1) / period;
+ osc->phase ^= count;
+ time += count * period;
+ }
+ osc->delay = time - end_time;
+ }
+
+ // advance polies
+ blip_time_t duration = end_time - last_time;
+ last_time = end_time;
+ poly4_pos = (poly4_pos + duration) % poly4_len;
+ poly5_pos = (poly5_pos + duration) % poly5_len;
+ polym_pos += duration; // will get %'d on next call
+}
+
+void Sap_Apu::write_data( blip_time_t time, int addr, int data )
+{
+ run_until( time );
+ int i = (addr - 0xD200) >> 1;
+ if ( (unsigned) i < osc_count )
+ {
+ oscs [i].regs [addr & 1] = data;
+ }
+ else if ( addr == 0xD208 )
+ {
+ control = data;
+ }
+ else if ( addr == 0xD209 )
+ {
+ oscs [0].delay = 0;
+ oscs [1].delay = 0;
+ oscs [2].delay = 0;
+ oscs [3].delay = 0;
+ }
+ /*
+ // TODO: are polynomials reset in this case?
+ else if ( addr == 0xD20F )
+ {
+ if ( (data & 3) == 0 )
+ polym_pos = 0;
+ }
+ */
+}
+
+void Sap_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 );
+}