package audio.gme; // Sega Master System SN76489 PSG sound chip 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 SmsOsc { static final int masterVolume = (int) (0.40 * 65536 / 128); BlipBuffer output; int outputSelect; final BlipBuffer [] outputs = new BlipBuffer [4]; int delay; int lastAmp; int volume; void reset() { delay = 0; lastAmp = 0; volume = 0; outputSelect = 3; output = outputs [outputSelect]; } } final class SmsSquare extends SmsOsc { int period; int phase; void reset() { period = 0; phase = 0; super.reset(); } void run( int time, int endTime ) { final int period = this.period; int amp = volume; if ( period > 128 ) amp = (amp * 2) & -phase; { int delta = amp - lastAmp; if ( delta != 0 ) { lastAmp = amp; output.addDelta( time, delta * masterVolume ); } } time += delay; delay = 0; if ( period != 0 ) { if ( time < endTime ) { if ( volume == 0 || period <= 128 ) // ignore 16kHz and higher { // keep calculating phase int count = (endTime - time + period - 1) / period; phase = (phase + count) & 1; time += count * period; } else { final BlipBuffer output = this.output; int delta = (amp - volume) * (2 * masterVolume); do { output.addDelta( time, delta = -delta ); } while ( (time += period) < endTime ); phase = (delta >= 0 ? 1 : 0); lastAmp = volume * (phase << 1); } } delay = time - endTime; } } } final class SmsNoise extends SmsOsc { int shifter; int feedback; int select; void reset() { select = 0; shifter = 0x8000; feedback = 0x9000; super.reset(); } void run( int time, int endTime, int period ) { // TODO: probably also not zero-centered final BlipBuffer output = this.output; int amp = volume; if ( (shifter & 1) != 0 ) amp = -amp; { int delta = amp - lastAmp; if ( delta != 0 ) { lastAmp = amp; output.addDelta( time, delta * masterVolume ); } } time += delay; if ( volume == 0 ) time = endTime; if ( time < endTime ) { final int feedback = this.feedback; int shifter = this.shifter; int delta = amp * (2 * masterVolume); if ( (period *= 2) == 0 ) period = 16; do { int changed = shifter + 1; shifter = (feedback & -(shifter & 1)) ^ (shifter >> 1); if ( (changed & 2) != 0 ) // true if bits 0 and 1 differ output.addDelta( time, delta = -delta ); } while ( (time += period) < endTime ); this.shifter = shifter; lastAmp = (delta < 0 ? -volume : volume); } delay = time - endTime; } } final class SmsApu { int lastTime; int latch; int noiseFeedback; int loopedFeedback; static final int oscCount = 4; final SmsSquare [] squares = new SmsSquare [3]; final SmsNoise noise = new SmsNoise(); final SmsOsc [] oscs = new SmsOsc [oscCount]; static final int [] noisePeriods = { 0x100, 0x200, 0x400 }; private void runUntil( int endTime ) { if ( endTime > lastTime ) { // run oscillators for ( int i = oscCount; --i >= 0; ) { SmsOsc osc = oscs [i]; if ( osc.output != null ) { if ( i < 3 ) { squares [i].run( lastTime, endTime ); } else { int period = squares [2].period; if ( noise.select < 3 ) period = noisePeriods [noise.select]; noise.run( lastTime, endTime, period ); } } } lastTime = endTime; } } public SmsApu() { for ( int i = 0; i < 3; i++ ) oscs [i] = squares [i] = new SmsSquare(); oscs [3] = noise; } public void setOutput( BlipBuffer center, BlipBuffer left, BlipBuffer right ) { for ( int i = 0; i < oscCount; i++ ) { SmsOsc osc = oscs [i]; osc.outputs [1] = right; osc.outputs [2] = left; osc.outputs [3] = center; osc.output = osc.outputs [osc.outputSelect]; } } public void reset( int feedback, int noiseWidth ) { lastTime = 0; latch = 0; // convert to "Galios configuration" loopedFeedback = 1 << (noiseWidth - 1); noiseFeedback = 0; while ( --noiseWidth >= 0 ) { noiseFeedback = (noiseFeedback << 1) | (feedback & 1); feedback >>= 1; } squares [0].reset(); squares [1].reset(); squares [2].reset(); noise.reset(); } public void reset() { reset( 0x0009, 16 ); } public void writeGG( int time, int data ) { runUntil( time ); for ( int i = 0; i < oscCount; i++ ) { SmsOsc osc = oscs [i]; int flags = data >> i; BlipBuffer oldOutput = osc.output; osc.outputSelect = (flags >> 3 & 2) | (flags & 1); osc.output = osc.outputs [osc.outputSelect]; if ( osc.output != oldOutput && osc.lastAmp != 0 ) { if ( oldOutput != null ) oldOutput.addDelta( time, -osc.lastAmp * SmsOsc.masterVolume ); osc.lastAmp = 0; } } } static final int [] volumes = { 64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0 }; public void writeData( int time, int data ) { runUntil( time ); if ( (data & 0x80) != 0 ) latch = data; int index = (latch >> 5) & 3; if ( (latch & 0x10) != 0 ) { oscs [index].volume = volumes [data & 15]; } else if ( index < 3 ) { SmsSquare sq = squares [index]; if ( (data & 0x80) != 0 ) sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF); else sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00); } else { noise.select = data & 3; noise.feedback = ((data & 0x04) != 0) ? noiseFeedback : loopedFeedback; noise.shifter = 0x8000; } } public void endFrame( int endTime ) { if ( endTime > lastTime ) runUntil( endTime ); lastTime -= endTime; } }