package audio.gme;
// Nintendo NES sound chip emulator
// http://www.slack.net/~ant/
/* Copyright (C) 2003-2010 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 NesOsc
{
static final int squareUnit = (int) (0.125 / 15 * 65535);
static final int triangleUnit = (int) (0.150 / 15 * 65535);
static final int noiseUnit = (int) (0.095 / 15 * 65535);
static final int dmcUnit = (int) (0.450 / 127 * 65535);
final int [] regs = new int [4];
final boolean [] regWritten = new boolean [4];
int lengthCounter;// length counter (0 if unused by oscillator)
int delay; // delay until next (potential) transition
int lastAmp; // last amplitude oscillator was outputting
void clockLength( int halt_mask )
{
if ( lengthCounter != 0 && (regs [0] & halt_mask) == 0 )
lengthCounter--;
}
int period() { return (regs [3] & 7) * 0x100 + (regs [2] & 0xFF); }
void reset()
{
delay = 0;
lastAmp = 0;
}
int updateAmp( int amp )
{
int delta = amp - lastAmp;
lastAmp = amp;
return delta;
}
}
class NesEnvelope extends NesOsc
{
int envVolume;
int envDelay;
void clockEnvelope()
{
int period = regs [0] & 15;
if ( regWritten [3] )
{
regWritten [3] = false;
envDelay = period;
envVolume = 15;
}
else if ( --envDelay < 0 )
{
envDelay = period;
if ( (envVolume | (regs [0] & 0x20)) != 0 )
envVolume = (envVolume - 1) & 15;
}
}
int volume()
{
if ( lengthCounter == 0 )
return 0;
if ( (regs [0] & 0x10) != 0 )
return regs [0] & 0x0F;
return envVolume;
}
void reset()
{
envVolume = 0;
envDelay = 0;
super.reset();
}
}
final class NesSquare extends NesEnvelope
{
static final int negateMask = 0x08;
static final int shiftMask = 0x07;
static final int phaseRange = 8;
int phase;
int sweepDelay;
void reset()
{
sweepDelay = 0;
super.reset();
}
void clockSweep( int negative_adjust )
{
int sweep = regs [1];
if ( --sweepDelay < 0 )
{
regWritten [1] = true;
int period = this.period();
int shift = sweep & shiftMask;
if ( shift != 0 && (sweep & 0x80) != 0 && period >= 8 )
{
int offset = period >> shift;
if ( (sweep & negateMask) != 0 )
offset = negative_adjust - offset;
if ( period + offset < 0x800 )
{
period += offset;
// rewrite period
regs [2] = period & 0xFF;
regs [3] = (regs [3] & ~7) | ((period >> 8) & 7);
}
}
}
if ( regWritten [1] )
{
regWritten [1] = false;
sweepDelay = (sweep >> 4) & 7;
}
}
void run( BlipBuffer output, int time, int endTime )
{
final int period = this.period();
final int timer_period = (period + 1) * 2;
int offset = period >> (regs [1] & shiftMask);
if ( (regs [1] & negateMask) != 0 )
offset = 0;
final int volume = this.volume();
if ( volume == 0 || period < 8 || (period + offset) > 0x7FF )
{
if ( lastAmp != 0 )
{
output.addDelta( time, lastAmp * -squareUnit );
lastAmp = 0;
}
time += delay;
int remain = endTime - time;
if ( remain > 0 )
{
int count = (remain + timer_period - 1) / timer_period;
phase = (phase + count) & (phaseRange - 1);
time += count * timer_period;
}
}
else
{
// handle duty select
int duty_select = (regs [0] >> 6) & 3;
int duty = 1 << duty_select; // 1, 2, 4, 2
int amp = 0;
if ( duty_select == 3 )
{
duty = 2; // negated 25%
amp = volume;
}
if ( phase < duty )
amp ^= volume;
{
int delta = updateAmp( amp );
if ( delta != 0 )
output.addDelta( time, delta * squareUnit );
}
time += delay;
if ( time < endTime )
{
int phase = this.phase; // cache
int delta = (amp * 2 - volume) * squareUnit;
do
{
if ( (phase = (phase + 1) & (phaseRange - 1)) == 0 ||
phase == duty )
output.addDelta( time, delta = -delta );
}
while ( (time += timer_period) < endTime );
this.phase = phase;
lastAmp = (delta < 0 ? 0 : volume);
}
}
delay = time - endTime;
}
}
final class NesTriangle extends NesOsc
{
static final int phaseRange = 16;
int phase;
int linearCounter;
void reset()
{
linearCounter = 0;
phase = phaseRange;
super.reset();
}
void clockLinearCounter()
{
if ( regWritten [3] )
linearCounter = regs [0] & 0x7F;
else if ( linearCounter != 0 )
linearCounter--;
if ( (regs [0] & 0x80) == 0 )
regWritten [3] = false;
}
int calc_amp()
{
int amp = phaseRange - phase;
if ( amp < 0 )
amp = phase - (phaseRange + 1);
return amp;
}
void run( BlipBuffer output, int time, int endTime )
{
final int timer_period = period() + 1;
// to do: track phase when period < 3
// to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks.
int delta = updateAmp( calc_amp() );
if ( delta != 0 )
output.addDelta( time, delta * triangleUnit );
time += delay;
if ( lengthCounter == 0 || linearCounter == 0 || timer_period < 3 )
{
time = endTime;
}
else if ( time < endTime )
{
int volume = triangleUnit;
if ( phase > phaseRange )
{
phase -= phaseRange;
volume = -volume;
}
do
{
if ( --phase != 0 )
{
output.addDelta( time, volume );
}
else
{
phase = phaseRange;
volume = -volume;
}
}
while ( (time += timer_period) < endTime );
if ( volume < 0 )
phase += phaseRange;
lastAmp = calc_amp();
}
delay = time - endTime;
}
}
final class NesNoise extends NesEnvelope
{
int lfsr;
static final int [] noisePeriods = {
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
};
void run( BlipBuffer output, int time, int endTime )
{
final int volume = this.volume();
int amp = (lfsr & 1) != 0 ? volume : 0;
{
int delta = updateAmp( amp );
if ( delta != 0 )
output.addDelta( time, delta * noiseUnit );
}
time += delay;
if ( time < endTime )
{
final int period = noisePeriods [regs [2] & 15];
final int tap = (regs [2] & 0x80) != 0 ? 8 : 13;
if ( volume == 0 )
{
// round to next multiple of period
time += (endTime - time + period - 1) / period * period;
// approximate noise cycling while muted, by shuffling up noise register
int feedback = (lfsr << tap) ^ (lfsr << 14);
lfsr = (feedback & 0x4000) | (lfsr >> 1);
}
else
{
int lfsr = this.lfsr; // cache
int delta = (amp * 2 - volume) * noiseUnit;
do
{
if ( ((lfsr + 1) & 2) != 0 )
output.addDelta( time, delta = -delta );
lfsr = ((lfsr << tap) ^ (lfsr << 14)) & 0x4000 | (lfsr >> 1);
}
while ( (time += period) < endTime );
this.lfsr = lfsr;
lastAmp = (delta < 0 ? 0 : volume);
}
}
delay = time - endTime;
}
void reset()
{
lfsr = 1 << 14;
super.reset();
}
}
final class NesDmc extends NesOsc
{
static final int loop_flag = 0x40;
int address; // address of next byte to read
int period;
//int length_counter; // bytes remaining to play (already defined in NesOsc)
int buf;
int bits_remain;
int bits;
boolean buf_full;
boolean silence;
int dac;
int irqEnabled;
int irqFlag;
boolean palMode;
// in DMC since it needs to clear it
int oscEnables;
NesCpu cpu;
void reset()
{
address = 0;
dac = 0;
buf = 0;
bits_remain = 1;
bits = 0;
buf_full = false;
silence = true;
irqFlag = 0;
irqEnabled = 0;
super.reset();
period = 0x1AC;
}
static final int [] dmc_period_table = {
428, 380, 340, 320, 286, 254, 226, 214, // NTSC
190, 160, 142, 128, 106, 84, 72, 54,
398, 354, 316, 298, 276, 236, 210, 198, // PAL
176, 148, 132, 118, 98, 78, 66, 50
};
void reload_sample()
{
address = 0x4000 + regs [2] * 0x40;
lengthCounter = regs [3] * 0x10 + 1;
}
static final int [] dac_table =
{
0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14,
15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27,
27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38,
39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49,
50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59,
59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67,
68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75,
76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83,
};
void write_register( int addr, int data )
{
if ( addr == 0 )
{
period = dmc_period_table [(data & 15) + (palMode ? 16 : 0)];
irqEnabled = 1;
if ( (data & 0xC0) != 0x80 )
{
irqEnabled = 0;
irqFlag = 0;
}
}
else if ( addr == 1 )
{
// adjust lastAmp so that "pop" amplitude will be properly non-linear
// with respect to change in dac
data &= 0x7F;
lastAmp = data - dac_table [data] + dac_table [dac];
dac = data;
}
}
void start()
{
reload_sample();
fill_buffer();
}
void fill_buffer()
{
if ( !buf_full && lengthCounter != 0 )
{
// Read byte via CPU
buf = cpu.cpuRead( 0x8000 + address );
address = (address + 1) & 0x7FFF;
buf_full = true;
if ( --lengthCounter == 0 ) // Reached end of sample
{
if ( (regs [0] & loop_flag) != 0 )
{
reload_sample();
}
else
{
oscEnables &= ~0x10;
irqFlag = irqEnabled;
}
}
}
}
void run( BlipBuffer output, int time, int endTime )
{
int delta = updateAmp( dac );
if ( delta != 0 )
output.addDelta( time, delta * dmcUnit );
time += delay;
if ( time < endTime )
{
if ( silence && !buf_full )
{
int count = (endTime - time + period - 1) / period;
bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1;
time += count * period;
}
else
{
do
{
if ( !silence )
{
int step;
int newDac = dac + (step = (bits << 2 & 4) - 2);
// if ( newDac >= 0 && newDac <= 0x7F )
if ( (byte) newDac >= 0 )
{
dac = newDac;
output.addDelta( time, step * dmcUnit );
}
bits >>= 1;
}
if ( --bits_remain == 0 )
{
bits_remain = 8;
silence = true;
if ( buf_full )
{
buf_full = false;
silence = false;
bits = buf;
fill_buffer();
}
}
}
while ( (time += period) < endTime );
lastAmp = dac;
}
}
delay = time - endTime;
}
}
public final class NesApu
{
public NesApu()
{
oscs [0] = square1;
oscs [1] = square2;
oscs [2] = triangle;
oscs [3] = noise;
oscs [4] = dmc;
}
public void setOutput( BlipBuffer b ) { output = b; }
// Resets oscillators and internal state
public void reset( NesCpu cpu, boolean palMode )
{
dmc.cpu = cpu;
dmc.palMode = palMode;
framePeriod = palMode ? 8314 : 7458;
frameTime = framePeriod;
lastTime = 0;
irqFlag = 0;
dmc.oscEnables = 0;
square1 .reset();
square2 .reset();
triangle.reset();
noise .reset();
dmc .reset();
write( 0, 0x4017, 0x00 );
write( 0, 0x4015, 0x00 );
for ( int addr = 0x4000; addr <= 0x4013; addr++ )
write( 0, addr, (addr & 3) != 0 ? 0x00 : 0x10 );
dmc.lastAmp = dmc.dac = 0; // prevents click
}
// Writes data to address at specified time
public static final int startAddr = 0x4000;
public static final int endAddr = 0x4017;
public void write( int time, int addr, int data )
{
assert 0 <= data && data < 0x100;
assert startAddr <= addr && addr <= endAddr;
runUntil( time );
if ( addr < 0x4014 )
{
// Write to channel
int index = (addr - startAddr) >> 2;
NesOsc osc = oscs [index];
int reg = addr & 3;
osc.regs [reg] = data;
osc.regWritten [reg] = true;
if ( index == 4 )
{
// handle DMC specially
dmc.write_register( reg, data );
}
else if ( reg == 3 )
{
// load length counter
if ( (dmc.oscEnables >> index & 1) != 0 )
osc.lengthCounter = length_table [data >> 3 & 0x1F];
// reset square phase
if ( index < 2 )
((NesSquare) osc).phase = NesSquare.phaseRange - 1;
}
}
else if ( addr == 0x4015 )
{
// Channel enables
for ( int i = oscCount; i-- > 0; )
if ( (data >> i & 1) == 0 )
oscs [i].lengthCounter = 0;
dmc.irqFlag = 0;
int justEnabled = data & ~dmc.oscEnables;
dmc.oscEnables = data;
if ( (justEnabled & 0x10) != 0 )
dmc.start();
}
else if ( addr == 0x4017 )
{
// Frame mode
frameMode = data;
if ( (data & 0x40) != 0 )
irqFlag = 0;
// mode 1
frameTime = time;
framePhase = 0;
if ( (data & 0x80) == 0 )
{
// mode 0
framePhase = 1;
frameTime += framePeriod;
}
}
}
// Reads from status register at specified time
public int read( int time )
{
runUntil( time );
int result = (dmc.irqFlag << 7) | (irqFlag << 6);
irqFlag = 0;
for ( int i = 0; i < oscCount; i++ )
if ( oscs [i].lengthCounter != 0 )
result |= 1 << i;
return result;
}
// Runs all oscillators up to specified time, ends current time frame, then
// starts a new frame at time 0
public void endFrame( int endTime )
{
if ( endTime > lastTime )
runUntil( endTime );
assert frameTime >= endTime;
frameTime -= endTime;
assert lastTime >= endTime;
lastTime -= endTime;
}
static final int [] length_table = {
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
};
static final int oscCount = 5;
final NesOsc [] oscs = new NesOsc [oscCount];
final NesSquare square1 = new NesSquare();
final NesSquare square2 = new NesSquare();
final NesTriangle triangle = new NesTriangle();
final NesNoise noise = new NesNoise();
final NesDmc dmc = new NesDmc();
BlipBuffer output;
int framePeriod;
int frameTime;
int framePhase;
int lastTime;
int frameMode;
int irqFlag;
void runUntil( int endTime )
{
assert endTime >= lastTime; // endTime must not be before previous time
if ( endTime == lastTime )
return;
while ( true )
{
// run oscillators
int time = endTime;
if ( time > frameTime )
time = frameTime;
square1 .run( output, lastTime, time );
square2 .run( output, lastTime, time );
triangle.run( output, lastTime, time );
noise .run( output, lastTime, time );
dmc .run( output, lastTime, time );
lastTime = time;
if ( time == endTime )
break;
// run frame sequencer
frameTime += framePeriod;
switch ( framePhase++ )
{
case 0:
if ( (frameMode & 0xC0) == 0 )
irqFlag = 1;
case 2:
// 120 Hz
square1 .clockLength( 0x20 );
square2 .clockLength( 0x20 );
triangle.clockLength( 0x80 ); // different bit for halt flag on triangle
noise .clockLength( 0x20 );
square1.clockSweep( -1 );
square2.clockSweep( 0 );
break;
case 3:
// 60 Hz
framePhase = 0;
if ( (frameMode & 0x80) != 0 )
frameTime += framePeriod; // frame 3 is almost twice as long in mode 1
break;
}
// 240 Hz
square1 .clockEnvelope();
square2 .clockEnvelope();
triangle.clockLinearCounter();
noise .clockEnvelope();
}
}
}