// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ #include "Sap_Apu.h" #include /* Copyright (C) 2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" int const max_frequency = 12000; // pure waves above this frequency are silenced static void gen_poly( blargg_ulong mask, int count, byte* out ) { blargg_ulong 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; blargg_ulong const poly5_mask = (1UL << poly5_len) - 1; blargg_ulong const poly5 = 0x167C6EA1; inline blargg_ulong run_poly5( blargg_ulong in, int shift ) { return (in << shift & poly5_mask) | (in >> (poly5_len - shift)); } #define POLY_MASK( width, tap1, tap2 ) \ ((1UL << (width - 1 - tap1)) | (1UL << (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 ); blargg_ulong n = poly5 [3] * 0x1000000L + poly5 [2] * 0x10000L + poly5 [1] * 0x100L + poly5 [0]; blargg_ulong rev = n & 1; for ( int i = 1; i < poly5_len; i++ ) rev |= (n >> i & 1) << (poly5_len - i); dprintf( "poly5: 0x%08lX\n", rev ); } } Sap_Apu::Sap_Apu() { impl = 0; for ( int i = 0; i < osc_count; i++ ) osc_output( i, 0 ); } 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 blargg_long 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 * 0x100L + 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 ) { output->set_modified(); 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; 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 blargg_ulong 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; } // 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 ) { blargg_long 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, unsigned addr, int data ) { run_until( time ); int i = (addr ^ 0xD200) >> 1; if ( 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; }