vbagx/source/vba/apu/Gb_Oscs.cpp

666 lines
15 KiB
C++
Raw Permalink Normal View History

2008-10-18 08:49:04 +02:00
// Gb_Snd_Emu 0.2.0. http://www.slack.net/~ant/
#include "Gb_Apu.h"
/* Copyright (C) 2003-2007 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 = 0;
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;
med_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( unsigned addr ) const
{
2012-06-27 01:08:42 +02:00
if ( enabled && mode != Gb_Apu::mode_agb )
2008-10-18 08:49:04 +02:00
{
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 index, int reg, int old_data, int data )
{
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 )
{
// 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;
}
else
{
// Output amplitude transitions
int delta = vol;
do
{
ph = (ph + 1) & 7;
if ( ph == 0 || ph == duty )
{
good_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;
}
// 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 )
2012-06-27 01:08:42 +02:00
s = (s >> 1 | mask) ^ (mask & (0 - ((s - 1) & 2)));
2008-10-18 08:49:04 +02:00
}
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;
}
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;
{
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;
}
// Generate wave
if ( time < end_time )
{
unsigned const mask = this->lfsr_mask();
unsigned bits = this->phase;
int per = period2( period1 * 8 );
if ( period2_index() >= 0xE )
{
time = end_time;
}
else if ( !vol )
{
// 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 );
}
else
{
// Output amplitude transitions
int delta = -vol;
do
{
unsigned changed = bits + 1;
bits = bits >> 1 & mask;
if ( changed & 2 )
{
bits |= ~mask;
delta = -delta;
med_synth->offset_inline( time, delta, out );
}
time += per;
}
while ( time < end_time );
if ( delta == vol )
last_amp += delta;
}
this->phase = bits;
}
}
void Gb_Wave::run( blip_time_t time, blip_time_t end_time )
{
// Calc volume
static byte const volumes [8] = { 0, 4, 2, 1, 3, 3, 3, 3 };
int const volume_shift = 2;
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];
// 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 )
playing = (int) enabled;
amp = (sample_buf << (phase << 2 & 4) & 0xF0) * playing;
}
amp = ((amp * volume_mul) >> (volume_shift + 4)) - dac_bias;
}
update_amp( time, amp );
}
// Generate wave
time += delay;
if ( time < end_time )
{
byte const* wave = this->wave_ram;
// wave size and bank
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);
}
int ph = this->phase ^ swap_banks;
ph = (ph + 1) & wave_mask; // pre-advance
int const per = this->period();
if ( !playing )
{
// 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;
}
else
{
// Output amplitude transitions
int lamp = this->last_amp + dac_bias;
do
{
// Extract nybble
int nybble = wave [ph >> 1] << (ph << 2 & 4) & 0xF0;
ph = (ph + 1) & wave_mask;
// Scale by volume
int amp = (nybble * volume_mul) >> (volume_shift + 4);
int delta = amp - lamp;
if ( delta )
{
lamp = amp;
med_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;
}