diff options
Diffstat (limited to 'plugins/gme/game-music-emu-0.6pre/gme/Nes_Fds_Apu.cpp')
-rw-r--r-- | plugins/gme/game-music-emu-0.6pre/gme/Nes_Fds_Apu.cpp | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/plugins/gme/game-music-emu-0.6pre/gme/Nes_Fds_Apu.cpp b/plugins/gme/game-music-emu-0.6pre/gme/Nes_Fds_Apu.cpp new file mode 100644 index 00000000..21602196 --- /dev/null +++ b/plugins/gme/game-music-emu-0.6pre/gme/Nes_Fds_Apu.cpp @@ -0,0 +1,280 @@ +// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/ + +#include "Nes_Fds_Apu.h" + +/* 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 fract_range = 65536; + +void Nes_Fds_Apu::reset() +{ + memset( regs_, 0, sizeof regs_ ); + memset( mod_wave, 0, sizeof mod_wave ); + + last_time = 0; + env_delay = 0; + sweep_delay = 0; + wave_pos = 0; + last_amp = 0; + wave_fract = fract_range; + mod_fract = fract_range; + mod_pos = 0; + mod_write_pos = 0; + + static byte const initial_regs [0x0B] = { + 0x80, // disable envelope + 0, 0, 0xC0, // disable wave and lfo + 0x80, // disable sweep + 0, 0, 0x80, // disable modulation + 0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does? + }; + for ( int i = 0; i < (int) sizeof initial_regs; i++ ) + { + // two writes to set both gain and period for envelope registers + write_( io_addr + wave_size + i, 0 ); + write_( io_addr + wave_size + i, initial_regs [i] ); + } +} + +void Nes_Fds_Apu::write_( unsigned addr, int data ) +{ + unsigned reg = addr - io_addr; + if ( reg < io_size ) + { + if ( reg < wave_size ) + { + if ( regs (0x4089) & 0x80 ) + regs_ [reg] = data & wave_sample_max; + } + else + { + regs_ [reg] = data; + switch ( addr ) + { + case 0x4080: + if ( data & 0x80 ) + env_gain = data & 0x3F; + else + env_speed = (data & 0x3F) + 1; + break; + + case 0x4084: + if ( data & 0x80 ) + sweep_gain = data & 0x3F; + else + sweep_speed = (data & 0x3F) + 1; + break; + + case 0x4085: + mod_pos = mod_write_pos; + regs (0x4085) = data & 0x7F; + break; + + case 0x4088: + if ( regs (0x4087) & 0x80 ) + { + int pos = mod_write_pos; + data &= 0x07; + mod_wave [pos ] = data; + mod_wave [pos + 1] = data; + mod_write_pos = (pos + 2) & (wave_size - 1); + mod_pos = (mod_pos + 2) & (wave_size - 1); + } + break; + } + } + } +} + +void Nes_Fds_Apu::set_tempo( double t ) +{ + lfo_tempo = lfo_base_tempo; + if ( t != 1.0 ) + { + lfo_tempo = int ((double) lfo_base_tempo / t + 0.5); + if ( lfo_tempo <= 0 ) + lfo_tempo = 1; + } +} + +void Nes_Fds_Apu::run_until( blip_time_t final_end_time ) +{ + int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082); + Blip_Buffer* const output_ = this->output_; + if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) ) + { + output_->set_modified(); + + // master_volume + #define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100 + static unsigned char const master_volumes [4] = { + MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 ) + }; + int const master_volume = master_volumes [regs (0x4089) & 0x03]; + + // lfo_period + blip_time_t lfo_period = regs (0x408A) * lfo_tempo; + if ( regs (0x4083) & 0x40 ) + lfo_period = 0; + + // sweep setup + blip_time_t sweep_time = last_time + sweep_delay; + blip_time_t const sweep_period = lfo_period * sweep_speed; + if ( !sweep_period || regs (0x4084) & 0x80 ) + sweep_time = final_end_time; + + // envelope setup + blip_time_t env_time = last_time + env_delay; + blip_time_t const env_period = lfo_period * env_speed; + if ( !env_period || regs (0x4080) & 0x80 ) + env_time = final_end_time; + + // modulation + int mod_freq = 0; + if ( !(regs (0x4087) & 0x80) ) + mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086); + + blip_time_t end_time = last_time; + do + { + // sweep + if ( sweep_time <= end_time ) + { + sweep_time += sweep_period; + int mode = regs (0x4084) >> 5 & 2; + int new_sweep_gain = sweep_gain + mode - 1; + if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode ) + sweep_gain = new_sweep_gain; + else + regs (0x4084) |= 0x80; // optimization only + } + + // envelope + if ( env_time <= end_time ) + { + env_time += env_period; + int mode = regs (0x4080) >> 5 & 2; + int new_env_gain = env_gain + mode - 1; + if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode ) + env_gain = new_env_gain; + else + regs (0x4080) |= 0x80; // optimization only + } + + // new end_time + blip_time_t const start_time = end_time; + end_time = final_end_time; + if ( end_time > env_time ) end_time = env_time; + if ( end_time > sweep_time ) end_time = sweep_time; + + // frequency modulation + int freq = wave_freq; + if ( mod_freq ) + { + // time of next modulation clock + blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq; + if ( end_time > mod_time ) + end_time = mod_time; + + // run modulator up to next clock and save old sweep_bias + int sweep_bias = regs (0x4085); + mod_fract -= (end_time - start_time) * mod_freq; + if ( mod_fract <= 0 ) + { + mod_fract += fract_range; + check( (unsigned) mod_fract <= fract_range ); + + static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 }; + int mod = mod_wave [mod_pos]; + mod_pos = (mod_pos + 1) & (wave_size - 1); + int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F; + if ( mod == 4 ) + new_sweep_bias = 0; + regs (0x4085) = new_sweep_bias; + } + + // apply frequency modulation + sweep_bias = (sweep_bias ^ 0x40) - 0x40; + int factor = sweep_bias * sweep_gain; + int extra = factor & 0x0F; + factor >>= 4; + if ( extra ) + { + factor--; + if ( sweep_bias >= 0 ) + factor += 3; + } + if ( factor > 193 ) factor -= 258; + if ( factor < -64 ) factor += 256; + freq += (freq * factor) >> 6; + if ( freq <= 0 ) + continue; + } + + // wave + int wave_fract = this->wave_fract; + blip_time_t delay = (wave_fract + freq - 1) / freq; + blip_time_t time = start_time + delay; + + if ( time <= end_time ) + { + // at least one wave clock within start_time...end_time + + blip_time_t const min_delay = fract_range / freq; + int wave_pos = this->wave_pos; + + int volume = env_gain; + if ( volume > vol_max ) + volume = vol_max; + volume *= master_volume; + + int const min_fract = min_delay * freq; + + do + { + // clock wave + int amp = regs_ [wave_pos] * volume; + wave_pos = (wave_pos + 1) & (wave_size - 1); + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset_inline( time, delta, output_ ); + } + + wave_fract += fract_range - delay * freq; + check( unsigned (fract_range - wave_fract) < freq ); + + // delay until next clock + delay = min_delay; + if ( wave_fract > min_fract ) + delay++; + check( delay && delay == (wave_fract + freq - 1) / freq ); + + time += delay; + } + while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong + + this->wave_pos = wave_pos; + } + this->wave_fract = wave_fract - (end_time - (time - delay)) * freq; + check( this->wave_fract > 0 ); + } + while ( end_time < final_end_time ); + + env_delay = env_time - final_end_time; check( env_delay >= 0 ); + sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 ); + } + last_time = final_end_time; +} |