package audio.gme; // Nintendo Game Boy sound emulator // http://www.slack.net/~ant/ /* 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 */ class GbOsc { static final boolean gbc_02 = false; // TODO: allow to be set? static final int trigger_mask = 0x80; static final int length_enabled = 0x40; static final int dac_bias = 7; BlipBuffer output; int output_select; final int [] regs = new int [5]; int vol_unit; int delay; int last_amp; int length; int enabled; void reset() { output = null; output_select = 0; delay = 0; last_amp = 0; length = 64; enabled = 0; for ( int i = 5; --i >= 0; ) regs [i] = 0; } void clock_length() { if ( (regs [4] & length_enabled) != 0 && length != 0 ) { if ( --length <= 0 ) enabled = 0; } } int frequency() { return (regs [4] & 7) * 0x100 + regs [3]; } boolean write_register( int frame_phase, int reg, int old_data, int data ) { return false; } int write_trig( int frame_phase, int max_len, int old_data ) { int data = regs [4]; if ( gbc_02 && (frame_phase & 1) != 0 && (old_data & length_enabled) == 0 && length != 0 ) length--; if ( (data & trigger_mask) != 0 ) { enabled = 1; if ( length == 0 ) { length = max_len; if ( gbc_02 && (frame_phase & 1) != 0 && (data & length_enabled) != 0 ) length--; } } if ( gbc_02 && length == 0 ) enabled = 0; return data & trigger_mask; } } class GbEnv extends GbOsc { int env_delay; int volume; int dac_enabled() { return regs [2] & 0xF8; } void reset() { env_delay = 0; volume = 0; super.reset(); } int reload_env_timer() { int raw = regs [2] & 7; env_delay = (raw != 0 ? raw : 8); return raw; } void clock_envelope() { if ( --env_delay <= 0 && reload_env_timer() != 0 ) { int v = volume + ((regs [2] & 0x08) != 0 ? +1 : -1); if ( 0 <= v && v <= 15 ) volume = v; } } boolean write_register( int frame_phase, int reg, int old_data, int data ) { final int max_len = 64; switch ( reg ) { case 1: length = max_len - (data & (max_len - 1)); break; case 2: if ( dac_enabled() == 0 ) enabled = 0; // TODO: once zombie mode used, envelope not clocked? if ( ((old_data ^ data) & 8) != 0 ) { int step = 0; if ( (old_data & 7) != 0 ) step = +1; else if ( (data & 7) != 0 ) step = -1; if ( (data & 8) != 0 ) step = -step; volume = (15 + step - volume) & 15; } else { int step = ((old_data & 7) != 0 ? 2 : 0) | ((data & 7) != 0 ? 0 : 1); volume = (volume + step) & 15; } break; case 4: if ( write_trig( frame_phase, max_len, old_data ) != 0 ) { volume = regs [2] >> 4; reload_env_timer(); if ( frame_phase == 7 ) env_delay++; if ( dac_enabled() == 0 ) enabled = 0; return true; } } return false; } } class GbSquare extends GbEnv { int phase; final int period() { return (2048 - frequency()) * 4; } void reset() { phase = 0; super.reset(); delay = 0x40000000; // TODO: less hacky (never clocked until first trigger) } boolean write_register( int frame_phase, int reg, int old_data, int data ) { boolean result = super.write_register( frame_phase, reg, old_data, data ); if ( result ) delay = period(); return result; } static final byte [] duty_offsets = { 1, 1, 3, 7 }; static final byte [] duties = { 1, 2, 4, 6 }; void run( int time, int end_time ) { final int duty_code = regs [1] >> 6; final int duty_offset = duty_offsets [duty_code]; final int duty = duties [duty_code]; int playing = 0; int amp = 0; int phase = (this.phase + duty_offset) & 7; if ( output != null ) { if ( volume != 0 ) { playing = -enabled; if ( phase < duty ) amp = volume & playing; // Treat > 16 kHz as DC if ( frequency() > 2041 && delay < 32 ) { amp = (volume * duty) >> 3 & playing; playing = 0; } } if ( dac_enabled() == 0 ) { playing = 0; amp = 0; } else { amp -= dac_bias; } int delta = amp - last_amp; if ( delta != 0 ) { last_amp = amp; output.addDelta( time, delta * vol_unit ); } } time += delay; if ( time < end_time ) { final int period = this.period(); if ( playing == 0 ) { // maintain phase int count = (end_time - time + period - 1) / period; phase = (phase + count) & 7; time += count * period; } else { final BlipBuffer output = this.output; // TODO: eliminate ugly +dac_bias -dac_bias adjustments int delta = ((amp + dac_bias) * 2 - volume) * vol_unit; do { if ( (phase = (phase + 1) & 7) == 0 || phase == duty ) output.addDelta( time, delta = -delta ); } while ( (time += period) < end_time ); last_amp = (delta < 0 ? 0 : volume) - dac_bias; } this.phase = (phase - duty_offset) & 7; } delay = time - end_time; } } final class GbSweepSquare extends GbSquare { static final int period_mask = 0x70; static final int shift_mask = 0x07; int sweep_freq; int sweep_delay; int sweep_enabled; int sweep_neg; void reset() { sweep_freq = 0; sweep_delay = 0; sweep_enabled = 0; sweep_neg = 0; super.reset(); } void reload_sweep_timer() { sweep_delay = (regs [0] & period_mask) >> 4; if ( sweep_delay == 0 ) sweep_delay = 8; } void calc_sweep( boolean update ) { int freq = sweep_freq; int shift = regs [0] & shift_mask; int delta = freq >> shift; sweep_neg = regs [0] & 0x08; if ( sweep_neg != 0 ) delta = -delta; freq += delta; if ( freq > 0x7FF ) { enabled = 0; } else if ( shift != 0 && update ) { sweep_freq = freq; regs [3] = freq & 0xFF; regs [4] = (regs [4] & ~0x07) | (freq >> 8 & 0x07); } } void clock_sweep() { if ( --sweep_delay <= 0 ) { reload_sweep_timer(); if ( sweep_enabled != 0 && (regs [0] & period_mask) != 0 ) { calc_sweep( true ); calc_sweep( false ); } } } boolean write_register( int frame_phase, int reg, int old_data, int data ) { if ( reg == 0 && (sweep_neg & 0x08 & ~data) != 0 ) enabled = 0; if ( super.write_register( frame_phase, reg, old_data, data ) ) { sweep_freq = frequency(); reload_sweep_timer(); sweep_enabled = regs [0] & (period_mask | shift_mask); if ( (regs [0] & shift_mask) != 0 ) calc_sweep( false ); } return false; } } final class GbNoise extends GbEnv { int bits; boolean write_register( int frame_phase, int reg, int old_data, int data ) { if ( reg == 3 ) { int p = period(); if ( p != 0 ) delay %= p; // TODO: not entirely correct } if ( super.write_register( frame_phase, reg, old_data, data ) ) bits = 0x7FFF; return false; } static final byte [] noise_periods = { 8, 16, 32, 48, 64, 80, 96, 112 }; int period() { int shift = regs [3] >> 4; int p = noise_periods [regs [3] & 7] << shift; if ( shift >= 0x0E ) p = 0; return p; } void run( int time, int end_time ) { int feedback = (1 << 14) >> (regs [3] & 8); int playing = 0; int amp = 0; if ( output != null ) { if ( volume != 0 ) { playing = -enabled; if ( (bits & 1) == 0 ) amp = volume & playing; } if ( dac_enabled() != 0 ) { amp -= dac_bias; } else { amp = 0; playing = 0; } int delta = amp - last_amp; if ( delta != 0 ) { last_amp = amp; output.addDelta( time, delta * vol_unit ); } } time += delay; if ( time < end_time ) { final int period = this.period(); if ( period == 0 ) { time = end_time; } else { int bits = this.bits; if ( playing == 0 ) { // maintain phase int count = (end_time - time + period - 1) / period; time += count * period; // TODO: be sure this doesn't drag performance too much bits ^= (feedback << 1) & -(bits & 1); feedback *= 3; do { bits = (bits >> 1) ^ (feedback & -(bits & 2)); } while ( --count > 0 ); bits &= ~(feedback << 1); } else { final BlipBuffer output = this.output; // TODO: eliminate ugly +dac_bias -dac_bias adjustments int delta = ((amp + dac_bias) * 2 - volume) * vol_unit; do { int changed = bits + 1; bits >>= 1; if ( (changed & 2) != 0 ) { bits |= feedback; output.addDelta( time, delta = -delta ); } } while ( (time += period) < end_time ); last_amp = (delta < 0 ? 0 : volume) - dac_bias; } this.bits = bits; } } delay = time - end_time; } } final class GbWave extends GbOsc { int wave_pos; int sample_buf_high; int sample_buf; static final int wave_size = 32; int [] wave = new int [wave_size]; int period() { return (2048 - frequency()) * 2; } int dac_enabled() { return regs [0] & 0x80; } int access( int addr ) { if ( enabled != 0 ) addr = 0xFF30 + (wave_pos >> 1); return addr; } void reset() { wave_pos = 0; sample_buf_high = 0; sample_buf = 0; length = 256; super.reset(); } boolean write_register( int frame_phase, int reg, int old_data, int data ) { final int max_len = 256; switch ( reg ) { case 1: length = max_len - data; break; case 4: if ( write_trig( frame_phase, max_len, old_data ) != 0 ) { wave_pos = 0; delay = period() + 6; sample_buf = sample_buf_high; } // fall through case 0: if ( dac_enabled() == 0 ) enabled = 0; } return false; } void run( int time, int end_time ) { int volume_shift = regs [2] >> 5 & 3; int playing = 0; if ( output != null ) { playing = -enabled; if ( --volume_shift < 0 ) { volume_shift = 7; playing = 0; } int amp = sample_buf & playing; if ( frequency() > 0x7FB && delay < 16 ) { // 16 kHz and above, act as DC at mid-level // (really depends on average level of entire wave, // but this is good enough) amp = 8; playing = 0; } amp >>= volume_shift; if ( dac_enabled() == 0 ) { playing = 0; amp = 0; } else { amp -= dac_bias; } int delta = amp - last_amp; if ( delta != 0 ) { last_amp = amp; output.addDelta( time, delta * vol_unit ); } } time += delay; if ( time < end_time ) { int wave_pos = (this.wave_pos + 1) & (wave_size - 1); final int period = this.period(); if ( playing == 0 ) { // maintain phase int count = (end_time - time + period - 1) / period; wave_pos += count; // will be masked below time += count * period; } else { final BlipBuffer output = this.output; int last_amp = this.last_amp + dac_bias; do { int amp = wave [wave_pos] >> volume_shift; wave_pos = (wave_pos + 1) & (wave_size - 1); int delta; if ( (delta = amp - last_amp) != 0 ) { last_amp = amp; output.addDelta( time, delta * vol_unit ); } } while ( (time += period) < end_time ); this.last_amp = last_amp - dac_bias; } wave_pos = (wave_pos - 1) & (wave_size - 1); this.wave_pos = wave_pos; if ( enabled != 0 ) { sample_buf_high = wave [wave_pos & ~1]; sample_buf = wave [wave_pos]; } } delay = time - end_time; } } final public class GbApu { public GbApu() { oscs [0] = square1; oscs [1] = square2; oscs [2] = wave; oscs [3] = noise; reset(); } // Resets oscillators and internal state public void setOutput( BlipBuffer center, BlipBuffer left, BlipBuffer right ) { outputs [1] = right; outputs [2] = left; outputs [3] = center; for ( int i = osc_count; --i >= 0; ) oscs [i].output = outputs [oscs [i].output_select]; } private void update_volume() { final int unit = (int) (1.0 / osc_count / 15 / 8 * 65536); // TODO: doesn't handle left != right volume (not worth the complexity) int data = regs [vol_reg - startAddr]; int left = data >> 4 & 7; int right = data & 7; int vol_unit = (left > right ? left : right) * unit; for ( int i = osc_count; --i >= 0; ) oscs [i].vol_unit = vol_unit; } private void reset_regs() { for ( int i = 0x20; --i >= 0; ) regs [i] = 0; for ( int i = osc_count; --i >= 0; ) oscs [i].reset(); update_volume(); } static final int initial_wave [] = { 0x84,0x40,0x43,0xAA,0x2D,0x78,0x92,0x3C, 0x60,0x59,0x59,0xB0,0x34,0xB8,0x2E,0xDA }; public void reset() { frame_time = 0; last_time = 0; frame_phase = 0; reset_regs(); for ( int i = 16; --i >= 0; ) write( 0, i + wave_ram, initial_wave [i] ); } private void run_until( int end_time ) { assert end_time >= last_time; // end_time must not be before previous time if ( end_time == last_time ) return; while ( true ) { // run oscillators int time = end_time; if ( time > frame_time ) time = frame_time; square1.run( last_time, time ); square2.run( last_time, time ); wave .run( last_time, time ); noise .run( last_time, time ); last_time = time; if ( time == end_time ) break; // run frame sequencer frame_time += frame_period; switch ( frame_phase++ ) { case 2: case 6: // 128 Hz square1.clock_sweep(); case 0: case 4: // 256 Hz square1.clock_length(); square2.clock_length(); wave .clock_length(); noise .clock_length(); break; case 7: // 64 Hz frame_phase = 0; square1.clock_envelope(); square2.clock_envelope(); noise .clock_envelope(); } } } // Runs all oscillators up to specified time, ends current time frame, then // starts a new frame at time 0 public void endFrame( int end_time ) { if ( end_time > last_time ) run_until( end_time ); assert frame_time >= end_time; frame_time -= end_time; assert last_time >= end_time; last_time -= end_time; } static void silence_osc( int time, GbOsc osc ) { int amp = osc.last_amp; if ( amp != 0 ) { osc.last_amp = 0; if ( osc.output != null ) osc.output.addDelta( time, -amp * osc.vol_unit ); } } // Reads and writes at addr must satisfy start_addr <= addr <= end_addr public static final int startAddr = 0xFF10; public static final int endAddr = 0xFF3F; public void write( int time, int addr, int data ) { assert startAddr <= addr && addr <= endAddr; assert 0 <= data && data < 0x100; if ( addr < status_reg && (regs [status_reg - startAddr] & power_mask) == 0 ) return; run_until( time ); int reg = addr - startAddr; if ( addr < wave_ram ) { int old_data = regs [reg]; regs [reg] = data; if ( addr < vol_reg ) { int index = reg / 5; GbOsc osc = oscs [index]; int r = reg - index * 5; osc.regs [r] = data; osc.write_register( frame_phase, r, old_data, data ); } else if ( addr == vol_reg && data != old_data ) { for ( int i = osc_count; --i >= 0; ) silence_osc( time, oscs [i] ); update_volume(); } else if ( addr == stereo_reg ) { for ( int i = osc_count; --i >= 0; ) { GbOsc osc = oscs [i]; int bits = data >> i; osc.output_select = (bits >> 3 & 2) | (bits & 1); BlipBuffer output = outputs [osc.output_select]; if ( osc.output != output ) { silence_osc( time, osc ); osc.output = output; } } } else if ( addr == status_reg && ((data ^ old_data) & power_mask) != 0 ) { frame_phase = 0; if ( (data & power_mask) == 0 ) { for ( int i = osc_count; --i >= 0; ) silence_osc( time, oscs [i] ); reset_regs(); } } } else // wave data { addr = wave.access( addr ); regs [addr - startAddr] = data; int index = (addr & 0x0F) * 2; wave.wave [index ] = data >> 4; wave.wave [index + 1] = data & 0x0F; } } static final int masks [] = { 0x80,0x3F,0x00,0xFF,0xBF, 0xFF,0x3F,0x00,0xFF,0xBF, 0x7F,0xFF,0x9F,0xFF,0xBF, 0xFF,0xFF,0x00,0x00,0xBF, 0x00,0x00,0x70, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; // Reads from address at specified time public int read( int time, int addr ) { assert startAddr <= addr && addr <= endAddr; run_until( time ); if ( addr >= wave_ram ) addr = wave.access( addr ); int index = addr - startAddr; int data = regs [index]; if ( index < masks.length ) data |= masks [index]; if ( addr == status_reg ) { data &= 0xF0; if ( square1.enabled != 0 ) data |= 1; if ( square2.enabled != 0 ) data |= 2; if ( wave .enabled != 0 ) data |= 4; if ( noise .enabled != 0 ) data |= 8; } return data; } static final int vol_reg = 0xFF24; static final int stereo_reg = 0xFF25; static final int status_reg = 0xFF26; static final int wave_ram = 0xFF30; static final int frame_period = 4194304 / 512; // 512 Hz static final int power_mask = 0x80; static final int osc_count = 4; final GbOsc [] oscs = new GbOsc [osc_count]; int frame_time; int last_time; int frame_phase; final BlipBuffer [] outputs = new BlipBuffer [4]; final GbSweepSquare square1 = new GbSweepSquare(); final GbSquare square2 = new GbSquare(); final GbWave wave = new GbWave(); final GbNoise noise = new GbNoise(); final int [] regs = new int [endAddr - startAddr + 1]; }