summaryrefslogtreecommitdiff
path: root/plugins/gme/game-music-emu-0.6pre/gme/Gb_Oscs.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/gme/game-music-emu-0.6pre/gme/Gb_Oscs.cpp')
-rw-r--r--plugins/gme/game-music-emu-0.6pre/gme/Gb_Oscs.cpp712
1 files changed, 712 insertions, 0 deletions
diff --git a/plugins/gme/game-music-emu-0.6pre/gme/Gb_Oscs.cpp b/plugins/gme/game-music-emu-0.6pre/gme/Gb_Oscs.cpp
new file mode 100644
index 00000000..b3a8a6ed
--- /dev/null
+++ b/plugins/gme/game-music-emu-0.6pre/gme/Gb_Oscs.cpp
@@ -0,0 +1,712 @@
+// Gb_Snd_Emu 0.1.4. http://www.slack.net/~ant/
+
+#include "Gb_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"
+
+bool const cgb_02 = false; // enables bug in early CGB units that causes problems in some games
+bool const cgb_05 = false; // enables CGB-05 zombie behavior
+
+int const trigger_mask = 0x80;
+int const length_enabled = 0x40;
+
+void Gb_Osc::reset()
+{
+ output = NULL;
+ last_amp = 0;
+ delay = 0;
+ phase = 0;
+ enabled = false;
+}
+
+inline void Gb_Osc::update_amp( blip_time_t time, int new_amp )
+{
+ output->set_modified();
+ int delta = new_amp - last_amp;
+ if ( delta )
+ {
+ last_amp = new_amp;
+ fast_synth->offset( time, delta, output );
+ }
+}
+
+// Units
+
+void Gb_Osc::clock_length()
+{
+ if ( (regs [4] & length_enabled) && length_ctr )
+ {
+ if ( --length_ctr <= 0 )
+ enabled = false;
+ }
+}
+
+inline int Gb_Env::reload_env_timer()
+{
+ int raw = regs [2] & 7;
+ env_delay = (raw ? raw : 8);
+ return raw;
+}
+
+void Gb_Env::clock_envelope()
+{
+ if ( env_enabled && --env_delay <= 0 && reload_env_timer() )
+ {
+ int v = volume + (regs [2] & 0x08 ? +1 : -1);
+ if ( 0 <= v && v <= 15 )
+ volume = v;
+ else
+ env_enabled = false;
+ }
+}
+
+inline void Gb_Sweep_Square::reload_sweep_timer()
+{
+ sweep_delay = (regs [0] & period_mask) >> 4;
+ if ( !sweep_delay )
+ sweep_delay = 8;
+}
+
+void Gb_Sweep_Square::calc_sweep( bool update )
+{
+ int const shift = regs [0] & shift_mask;
+ int const delta = sweep_freq >> shift;
+ sweep_neg = (regs [0] & 0x08) != 0;
+ int const freq = sweep_freq + (sweep_neg ? -delta : delta);
+
+ if ( freq > 0x7FF )
+ {
+ enabled = false;
+ }
+ else if ( shift && update )
+ {
+ sweep_freq = freq;
+
+ regs [3] = freq & 0xFF;
+ regs [4] = (regs [4] & ~0x07) | (freq >> 8 & 0x07);
+ }
+}
+
+void Gb_Sweep_Square::clock_sweep()
+{
+ if ( --sweep_delay <= 0 )
+ {
+ reload_sweep_timer();
+ if ( sweep_enabled && (regs [0] & period_mask) )
+ {
+ calc_sweep( true );
+ calc_sweep( false );
+ }
+ }
+}
+
+int Gb_Wave::access( int addr ) const
+{
+ if ( enabled )
+ {
+ addr = phase & (bank_size - 1);
+ if ( mode == Gb_Apu::mode_dmg )
+ {
+ addr++;
+ if ( delay > clk_mul )
+ return -1; // can only access within narrow time window while playing
+ }
+ addr >>= 1;
+ }
+ return addr & 0x0F;
+}
+
+// write_register
+
+int Gb_Osc::write_trig( int frame_phase, int max_len, int old_data )
+{
+ int data = regs [4];
+
+ if ( (frame_phase & 1) && !(old_data & length_enabled) && length_ctr )
+ {
+ if ( (data & length_enabled) || cgb_02 )
+ length_ctr--;
+ }
+
+ if ( data & trigger_mask )
+ {
+ enabled = true;
+ if ( !length_ctr )
+ {
+ length_ctr = max_len;
+ if ( (frame_phase & 1) && (data & length_enabled) )
+ length_ctr--;
+ }
+ }
+
+ if ( !length_ctr )
+ enabled = false;
+
+ return data & trigger_mask;
+}
+
+inline void Gb_Env::zombie_volume( int old, int data )
+{
+ int v = volume;
+ if ( mode == Gb_Apu::mode_agb || cgb_05 )
+ {
+ // CGB-05 behavior, very close to AGB behavior as well
+ if ( (old ^ data) & 8 )
+ {
+ if ( !(old & 8) )
+ {
+ v++;
+ if ( old & 7 )
+ v++;
+ }
+
+ v = 16 - v;
+ }
+ else if ( (old & 0x0F) == 8 )
+ {
+ v++;
+ }
+ }
+ else
+ {
+ // CGB-04&02 behavior, very close to MGB behavior as well
+ if ( !(old & 7) && env_enabled )
+ v++;
+ else if ( !(old & 8) )
+ v += 2;
+
+ if ( (old ^ data) & 8 )
+ v = 16 - v;
+ }
+ volume = v & 0x0F;
+}
+
+bool Gb_Env::write_register( int frame_phase, int reg, int old, int data )
+{
+ int const max_len = 64;
+
+ switch ( reg )
+ {
+ case 1:
+ length_ctr = max_len - (data & (max_len - 1));
+ break;
+
+ case 2:
+ if ( !dac_enabled() )
+ enabled = false;
+
+ zombie_volume( old, data );
+
+ if ( (data & 7) && env_delay == 8 )
+ {
+ env_delay = 1;
+ clock_envelope(); // TODO: really happens at next length clock
+ }
+ break;
+
+ case 4:
+ if ( write_trig( frame_phase, max_len, old ) )
+ {
+ volume = regs [2] >> 4;
+ reload_env_timer();
+ env_enabled = true;
+ if ( frame_phase == 7 )
+ env_delay++;
+ if ( !dac_enabled() )
+ enabled = false;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Gb_Square::write_register( int frame_phase, int reg, int old_data, int data )
+{
+ bool result = Gb_Env::write_register( frame_phase, reg, old_data, data );
+ if ( result )
+ delay = (delay & (4 * clk_mul - 1)) + period();
+ return result;
+}
+
+inline void Gb_Noise::write_register( int frame_phase, int reg, int old_data, int data )
+{
+ if ( Gb_Env::write_register( frame_phase, reg, old_data, data ) )
+ {
+ phase = 0x7FFF;
+ delay += 8 * clk_mul;
+ }
+}
+
+inline void Gb_Sweep_Square::write_register( int frame_phase, int reg, int old_data, int data )
+{
+ if ( reg == 0 && sweep_enabled && sweep_neg && !(data & 0x08) )
+ enabled = false; // sweep negate disabled after used
+
+ if ( Gb_Square::write_register( frame_phase, reg, old_data, data ) )
+ {
+ sweep_freq = frequency();
+ sweep_neg = false;
+ reload_sweep_timer();
+ sweep_enabled = (regs [0] & (period_mask | shift_mask)) != 0;
+ if ( regs [0] & shift_mask )
+ calc_sweep( false );
+ }
+}
+
+void Gb_Wave::corrupt_wave()
+{
+ int pos = ((phase + 1) & (bank_size - 1)) >> 1;
+ if ( pos < 4 )
+ wave_ram [0] = wave_ram [pos];
+ else
+ for ( int i = 4; --i >= 0; )
+ wave_ram [i] = wave_ram [(pos & ~3) + i];
+}
+
+inline void Gb_Wave::write_register( int frame_phase, int reg, int old_data, int data )
+{
+ int const max_len = 256;
+
+ switch ( reg )
+ {
+ case 0:
+ if ( !dac_enabled() )
+ enabled = false;
+ break;
+
+ case 1:
+ length_ctr = max_len - data;
+ break;
+
+ case 4:
+ bool was_enabled = enabled;
+ if ( write_trig( frame_phase, max_len, old_data ) )
+ {
+ if ( !dac_enabled() )
+ enabled = false;
+ else if ( mode == Gb_Apu::mode_dmg && was_enabled &&
+ (unsigned) (delay - 2 * clk_mul) < 2 * clk_mul )
+ corrupt_wave();
+
+ phase = 0;
+ delay = period() + 6 * clk_mul;
+ }
+ }
+}
+
+void Gb_Apu::write_osc( int reg, int old_data, int data )
+{
+ int index = (reg * 3 + 3) >> 4; // avoids divide
+ assert( index == reg / 5 );
+ reg -= index * 5;
+ switch ( index )
+ {
+ case 0: square1.write_register( frame_phase, reg, old_data, data ); break;
+ case 1: square2.write_register( frame_phase, reg, old_data, data ); break;
+ case 2: wave .write_register( frame_phase, reg, old_data, data ); break;
+ case 3: noise .write_register( frame_phase, reg, old_data, data ); break;
+ }
+}
+
+// Synthesis
+
+void Gb_Square::run( blip_time_t time, blip_time_t end_time )
+{
+ // Calc duty and phase
+ static byte const duty_offsets [4] = { 1, 1, 3, 7 };
+ static byte const duties [4] = { 1, 2, 4, 6 };
+ int const duty_code = regs [1] >> 6;
+ int duty_offset = duty_offsets [duty_code];
+ int duty = duties [duty_code];
+ if ( mode == Gb_Apu::mode_agb )
+ {
+ // AGB uses inverted duty
+ duty_offset -= duty;
+ duty = 8 - duty;
+ }
+ int ph = (this->phase + duty_offset) & 7;
+
+ // Determine what will be generated
+ int vol = 0;
+ Blip_Buffer* const out = this->output;
+ if ( out )
+ {
+ int amp = dac_off_amp;
+ if ( dac_enabled() )
+ {
+ if ( enabled )
+ vol = this->volume;
+
+ amp = -dac_bias;
+ if ( mode == Gb_Apu::mode_agb )
+ amp = -(vol >> 1);
+
+ // Play inaudible frequencies as constant amplitude
+ if ( frequency() >= 0x7FA && delay < 32 * clk_mul )
+ {
+ amp += (vol * duty) >> 3;
+ vol = 0;
+ }
+
+ if ( ph < duty )
+ {
+ amp += vol;
+ vol = -vol;
+ }
+ }
+ update_amp( time, amp );
+ }
+
+ // Generate wave
+ time += delay;
+ if ( time < end_time )
+ {
+ int const per = this->period();
+ if ( !vol )
+ {
+ #if GB_APU_FAST
+ time = end_time;
+ #else
+ // Maintain phase when not playing
+ int count = (end_time - time + per - 1) / per;
+ ph += count; // will be masked below
+ time += (blip_time_t) count * per;
+ #endif
+ }
+ else
+ {
+ // Output amplitude transitions
+ int delta = vol;
+ do
+ {
+ ph = (ph + 1) & 7;
+ if ( ph == 0 || ph == duty )
+ {
+ norm_synth->offset_inline( time, delta, out );
+ delta = -delta;
+ }
+ time += per;
+ }
+ while ( time < end_time );
+
+ if ( delta != vol )
+ last_amp -= delta;
+ }
+ this->phase = (ph - duty_offset) & 7;
+ }
+ delay = time - end_time;
+}
+
+#if !GB_APU_FAST
+// Quickly runs LFSR for a large number of clocks. For use when noise is generating
+// no sound.
+static unsigned run_lfsr( unsigned s, unsigned mask, int count )
+{
+ bool const optimized = true; // set to false to use only unoptimized loop in middle
+
+ // optimization used in several places:
+ // ((s & (1 << b)) << n) ^ ((s & (1 << b)) << (n + 1)) = (s & (1 << b)) * (3 << n)
+
+ if ( mask == 0x4000 && optimized )
+ {
+ if ( count >= 32767 )
+ count %= 32767;
+
+ // Convert from Fibonacci to Galois configuration,
+ // shifted left 1 bit
+ s ^= (s & 1) * 0x8000;
+
+ // Each iteration is equivalent to clocking LFSR 255 times
+ while ( (count -= 255) > 0 )
+ s ^= ((s & 0xE) << 12) ^ ((s & 0xE) << 11) ^ (s >> 3);
+ count += 255;
+
+ // Each iteration is equivalent to clocking LFSR 15 times
+ // (interesting similarity to single clocking below)
+ while ( (count -= 15) > 0 )
+ s ^= ((s & 2) * (3 << 13)) ^ (s >> 1);
+ count += 15;
+
+ // Remaining singles
+ while ( --count >= 0 )
+ s = ((s & 2) * (3 << 13)) ^ (s >> 1);
+
+ // Convert back to Fibonacci configuration
+ s &= 0x7FFF;
+ }
+ else if ( count < 8 || !optimized )
+ {
+ // won't fully replace upper 8 bits, so have to do the unoptimized way
+ while ( --count >= 0 )
+ s = (s >> 1 | mask) ^ (mask & -((s - 1) & 2));
+ }
+ else
+ {
+ if ( count > 127 )
+ {
+ count %= 127;
+ if ( !count )
+ count = 127; // must run at least once
+ }
+
+ // Need to keep one extra bit of history
+ s = s << 1 & 0xFF;
+
+ // Convert from Fibonacci to Galois configuration,
+ // shifted left 2 bits
+ s ^= (s & 2) * 0x80;
+
+ // Each iteration is equivalent to clocking LFSR 7 times
+ // (interesting similarity to single clocking below)
+ while ( (count -= 7) > 0 )
+ s ^= ((s & 4) * (3 << 5)) ^ (s >> 1);
+ count += 7;
+
+ // Remaining singles
+ while ( --count >= 0 )
+ s = ((s & 4) * (3 << 5)) ^ (s >> 1);
+
+ // Convert back to Fibonacci configuration and
+ // repeat last 8 bits above significant 7
+ s = (s << 7 & 0x7F80) | (s >> 1 & 0x7F);
+ }
+
+ return s;
+}
+#endif
+
+void Gb_Noise::run( blip_time_t time, blip_time_t end_time )
+{
+ // Determine what will be generated
+ int vol = 0;
+ Blip_Buffer* const out = this->output;
+ if ( out )
+ {
+ int amp = dac_off_amp;
+ if ( dac_enabled() )
+ {
+ if ( enabled )
+ vol = this->volume;
+
+ amp = -dac_bias;
+ if ( mode == Gb_Apu::mode_agb )
+ amp = -(vol >> 1);
+
+ if ( !(phase & 1) )
+ {
+ amp += vol;
+ vol = -vol;
+ }
+ }
+
+ // AGB negates final output
+ if ( mode == Gb_Apu::mode_agb )
+ {
+ vol = -vol;
+ amp = -amp;
+ }
+
+ update_amp( time, amp );
+ }
+
+ // Run timer and calculate time of next LFSR clock
+ static byte const period1s [8] = { 1, 2, 4, 6, 8, 10, 12, 14 };
+ int const period1 = period1s [regs [3] & 7] * clk_mul;
+
+ #if GB_APU_FAST
+ time += delay;
+ #else
+ {
+ int extra = (end_time - time) - delay;
+ int const per2 = this->period2();
+ time += delay + ((divider ^ (per2 >> 1)) & (per2 - 1)) * period1;
+
+ int count = (extra < 0 ? 0 : (extra + period1 - 1) / period1);
+ divider = (divider - count) & period2_mask;
+ delay = count * period1 - extra;
+ }
+ #endif
+
+ // Generate wave
+ if ( time < end_time )
+ {
+ unsigned const mask = this->lfsr_mask();
+ unsigned bits = this->phase;
+
+ int per = period2( period1 * 8 );
+ #if GB_APU_FAST
+ // Noise can be THE biggest time hog; adjust as necessary
+ int const min_period = 24;
+ if ( per < min_period )
+ per = min_period;
+ #endif
+ if ( period2_index() >= 0xE )
+ {
+ time = end_time;
+ }
+ else if ( !vol )
+ {
+ #if GB_APU_FAST
+ time = end_time;
+ #else
+ // Maintain phase when not playing
+ int count = (end_time - time + per - 1) / per;
+ time += (blip_time_t) count * per;
+ bits = run_lfsr( bits, ~mask, count );
+ #endif
+ }
+ else
+ {
+ Blip_Synth_Fast const* const synth = fast_synth; // cache
+
+ // Output amplitude transitions
+ int delta = -vol;
+ do
+ {
+ unsigned changed = bits + 1;
+ bits = bits >> 1 & mask;
+ if ( changed & 2 )
+ {
+ bits |= ~mask;
+ delta = -delta;
+ synth->offset_inline( time, delta, out );
+ }
+ time += per;
+ }
+ while ( time < end_time );
+
+ if ( delta == vol )
+ last_amp += delta;
+ }
+ this->phase = bits;
+ }
+
+ #if GB_APU_FAST
+ delay = time - end_time;
+ #endif
+}
+
+void Gb_Wave::run( blip_time_t time, blip_time_t end_time )
+{
+ // Calc volume
+#if GB_APU_NO_AGB
+ static byte const shifts [4] = { 4+4, 0+4, 1+4, 2+4 };
+ int const volume_idx = regs [2] >> 5 & 3;
+ int const volume_shift = shifts [volume_idx];
+ int const volume_mul = 1;
+#else
+ static byte const volumes [8] = { 0, 4, 2, 1, 3, 3, 3, 3 };
+ int const volume_shift = 2 + 4;
+ int const volume_idx = regs [2] >> 5 & (agb_mask | 3); // 2 bits on DMG/CGB, 3 on AGB
+ int const volume_mul = volumes [volume_idx];
+#endif
+
+ // Determine what will be generated
+ int playing = false;
+ Blip_Buffer* const out = this->output;
+ if ( out )
+ {
+ int amp = dac_off_amp;
+ if ( dac_enabled() )
+ {
+ // Play inaudible frequencies as constant amplitude
+ amp = 8 << 4; // really depends on average of all samples in wave
+
+ // if delay is larger, constant amplitude won't start yet
+ if ( frequency() <= 0x7FB || delay > 15 * clk_mul )
+ {
+ if ( volume_mul && volume_shift != 4+4 )
+ playing = (int) enabled;
+
+ amp = (sample_buf << (phase << 2 & 4) & 0xF0) * playing;
+ }
+
+ amp = ((amp * volume_mul) >> volume_shift) - dac_bias;
+ }
+ update_amp( time, amp );
+ }
+
+ // Generate wave
+ time += delay;
+ if ( time < end_time )
+ {
+ byte const* wave = this->wave_ram;
+
+ // wave size and bank
+ #if GB_APU_NO_AGB
+ int const wave_mask = 0x1F;
+ int const swap_banks = 0;
+ #else
+ int const size20_mask = 0x20;
+ int const flags = regs [0] & agb_mask;
+ int const wave_mask = (flags & size20_mask) | 0x1F;
+ int swap_banks = 0;
+ if ( flags & bank40_mask )
+ {
+ swap_banks = flags & size20_mask;
+ wave += bank_size/2 - (swap_banks >> 1);
+ }
+ #endif
+
+ int ph = this->phase ^ swap_banks;
+ ph = (ph + 1) & wave_mask; // pre-advance
+
+ int const per = this->period();
+ if ( !playing )
+ {
+ #if GB_APU_FAST
+ time = end_time;
+ #else
+ // Maintain phase when not playing
+ int count = (end_time - time + per - 1) / per;
+ ph += count; // will be masked below
+ time += (blip_time_t) count * per;
+ #endif
+ }
+ else
+ {
+ Blip_Synth_Fast const* const synth = fast_synth; // cache
+
+ // Output amplitude transitions
+ int lamp = this->last_amp + dac_bias;
+ do
+ {
+ // Extract nibble
+ int nibble = wave [ph >> 1] << (ph << 2 & 4) & 0xF0;
+ ph = (ph + 1) & wave_mask;
+
+ // Scale by volume
+ int amp = (nibble * volume_mul) >> volume_shift;
+
+ int delta = amp - lamp;
+ if ( delta )
+ {
+ lamp = amp;
+ synth->offset_inline( time, delta, out );
+ }
+ time += per;
+ }
+ while ( time < end_time );
+ this->last_amp = lamp - dac_bias;
+ }
+ ph = (ph - 1) & wave_mask; // undo pre-advance and mask position
+
+ // Keep track of last byte read
+ if ( enabled )
+ sample_buf = wave [ph >> 1];
+
+ this->phase = ph ^ swap_banks; // undo swapped banks
+ }
+ delay = time - end_time;
+}