655 lines
21 KiB
C#
655 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Emulation.Common;
|
|
|
|
//http://wiki.nesdev.com/w/index.php/APU_Mixer_Emulation
|
|
//http://wiki.nesdev.com/w/index.php/APU
|
|
//http://wiki.nesdev.com/w/index.php/APU_Pulse
|
|
//sequencer ref: http://wiki.nesdev.com/w/index.php/APU_Frame_Counter
|
|
|
|
//TODO - refactor length counter to be separate component
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
|
{
|
|
partial class NES
|
|
{
|
|
public class BisqAPU : ISoundProvider
|
|
{
|
|
class RegBitSetStore
|
|
{
|
|
public RegBitSetStore(int numUints)
|
|
{
|
|
data = new uint[numUints];
|
|
}
|
|
public uint[] data;
|
|
}
|
|
|
|
class RegBitSet
|
|
{
|
|
RegBitSetStore store;
|
|
int mask;
|
|
int bitno, nbits, dim, index;
|
|
public RegBitSet(RegBitSetStore store, int bitno, int nbits, int dim = 1, int index = 0)
|
|
{
|
|
this.store = store;
|
|
this.bitno = bitno;
|
|
this.nbits = nbits;
|
|
this.dim = dim;
|
|
this.index = index;
|
|
mask = ((1 << (nbits / 2)) << (nbits - (nbits / 2))) - 1;
|
|
}
|
|
|
|
public static implicit operator int(RegBitSet rhs) { return (int)rhs.get(); }
|
|
public static implicit operator uint(RegBitSet rhs) { return rhs.get(); }
|
|
public static explicit operator bool(RegBitSet rhs) { return rhs.get() != 0; }
|
|
|
|
public uint Value { get { return get(); } set { set(value); } }
|
|
|
|
public uint this[int offset]
|
|
{
|
|
get
|
|
{
|
|
return get(offset);
|
|
}
|
|
set
|
|
{
|
|
set(value, offset);
|
|
}
|
|
}
|
|
|
|
public uint get(int offset = 0)
|
|
{
|
|
return (uint)((store.data[index + offset] >> bitno) & mask);
|
|
}
|
|
|
|
public void set(uint val, int offset = 0)
|
|
{
|
|
long temp = (store.data[index + offset] & ~(mask << bitno))
|
|
| ((nbits > 1 ? val & mask : ((val != 0) ? 1 : 0)) << bitno);
|
|
store.data[index + offset] = (uint)temp;
|
|
}
|
|
|
|
public static uint operator ^(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() ^ rhs)); }
|
|
public static uint operator |(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() | rhs)); }
|
|
public static uint operator &(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() & rhs)); }
|
|
public static uint operator +(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() + rhs)); }
|
|
public static uint operator -(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() - rhs)); }
|
|
};
|
|
|
|
class SignedRegBitSet
|
|
{
|
|
RegBitSetStore store;
|
|
int mask;
|
|
int bitno, nbits, dim, index;
|
|
public SignedRegBitSet(RegBitSetStore store, int bitno, int nbits, int dim = 1, int index = 0)
|
|
{
|
|
this.store = store;
|
|
this.bitno = bitno;
|
|
this.nbits = nbits;
|
|
this.dim = dim;
|
|
this.index = index;
|
|
mask = ((1 << (nbits / 2)) << (nbits - (nbits / 2))) - 1;
|
|
}
|
|
|
|
public static implicit operator int(SignedRegBitSet rhs) { return rhs.get(); }
|
|
public static implicit operator uint(SignedRegBitSet rhs) { return (uint)rhs.get(); }
|
|
public static explicit operator bool(SignedRegBitSet rhs) { return rhs.get() != 0; }
|
|
|
|
public int Value { get { return get(); } set { set(value); } }
|
|
|
|
public int this[int offset]
|
|
{
|
|
get
|
|
{
|
|
return get(offset);
|
|
}
|
|
set
|
|
{
|
|
set(value, offset);
|
|
}
|
|
}
|
|
|
|
public int get(int offset = 0)
|
|
{
|
|
return (int)((store.data[index + offset] >> bitno) & mask);
|
|
}
|
|
|
|
public void set(int val, int offset = 0)
|
|
{
|
|
long temp = ((int)store.data[index + offset] & (int)~(mask << bitno))
|
|
| ((nbits > 1 ? val & mask : ((val != 0) ? 1 : 0)) << bitno);
|
|
store.data[index + offset] = (uint)temp;
|
|
}
|
|
|
|
public static int operator ^(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() ^ rhs)); }
|
|
public static int operator |(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() | rhs)); }
|
|
public static int operator &(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() & rhs)); }
|
|
public static int operator +(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() + rhs)); }
|
|
public static int operator -(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() - rhs)); }
|
|
|
|
};
|
|
|
|
|
|
public static bool CFG_USE_METASPU = true;
|
|
|
|
class APUdata
|
|
{
|
|
public APUdata()
|
|
{
|
|
bs = new RegBitSetStore(5);
|
|
ChannelsEnabled = new RegBitSet(bs, 15, 1, 5, 0);
|
|
frame_delay = new RegBitSet(bs, 0, 15, 5, 0);
|
|
frame = new RegBitSet(bs, 0, 8, 5, 1);
|
|
FiveCycleDivider = new RegBitSet(bs, 8, 1, 5, 1);
|
|
IRQdisable = new RegBitSet(bs, 9, 1, 5, 1);
|
|
DMC_CycleCost = new RegBitSet(bs, 10, 3, 5, 1);
|
|
IRQ_delay = new RegBitSet(bs, 0, 15, 5, 2);
|
|
}
|
|
public RegBitSetStore bs;
|
|
public RegBitSet ChannelsEnabled;
|
|
public RegBitSet frame_delay;
|
|
public RegBitSet frame;
|
|
public RegBitSet FiveCycleDivider;
|
|
public RegBitSet IRQdisable;
|
|
public RegBitSet DMC_CycleCost;
|
|
public RegBitSet IRQ_delay;
|
|
}
|
|
APUdata data = new APUdata();
|
|
|
|
class APUchannel
|
|
{
|
|
public APUchannel()
|
|
{
|
|
bs = new RegBitSetStore(11);
|
|
|
|
reg0 = new RegBitSet(bs,0, 8, 11, 0);
|
|
DutyCycle = new RegBitSet(bs,6, 2, 11, 0);
|
|
EnvDecayDisable = new RegBitSet(bs,4, 1, 11, 0);
|
|
EnvDecayRate = new RegBitSet(bs,0, 4, 11, 0);
|
|
EnvDecayLoopEnable = new RegBitSet(bs,5, 1, 11, 0);
|
|
FixedVolume = new RegBitSet(bs,0, 4, 11, 0);
|
|
LengthCounterDisable = new RegBitSet(bs,5, 1, 11, 0);
|
|
LinearCounterInit = new RegBitSet(bs,0, 7, 11, 0);
|
|
LinearCounterDisable = new RegBitSet(bs,7, 1, 11, 0);
|
|
|
|
reg1 = new RegBitSet(bs,8, 8, 11, 0);
|
|
SweepShift = new RegBitSet(bs,8, 3, 11, 0);
|
|
SweepDecrease = new RegBitSet(bs,11, 1, 11, 0);
|
|
SweepRate = new RegBitSet(bs,12, 3, 11, 0);
|
|
SweepEnable = new RegBitSet(bs,15, 1, 11, 0);
|
|
PCMlength = new RegBitSet(bs,8, 8, 11, 0);
|
|
|
|
reg2 = new RegBitSet(bs,16, 8, 11, 0);
|
|
NoiseFreq = new RegBitSet(bs,16, 4, 11, 0);
|
|
NoiseType = new RegBitSet(bs,23, 1, 11, 0);
|
|
WaveLength = new RegBitSet(bs,16, 11, 11, 0);
|
|
|
|
reg3 = new RegBitSet(bs,24, 8, 11, 0);
|
|
LengthCounterInit = new RegBitSet(bs,27, 5, 11, 0);
|
|
LoopEnabled = new RegBitSet(bs,30, 1, 11, 0);
|
|
IRQenable = new RegBitSet(bs,31, 1, 11, 0);
|
|
|
|
length_counter = new SignedRegBitSet(bs,0, 32, 11, 1);
|
|
linear_counter = new SignedRegBitSet(bs,0, 32, 11, 2);
|
|
address = new SignedRegBitSet(bs,0, 32, 11, 3);
|
|
envelope = new SignedRegBitSet(bs,0, 32, 11, 4);
|
|
sweep_delay = new SignedRegBitSet(bs,0, 32, 11, 5);
|
|
env_delay = new SignedRegBitSet(bs,0, 32, 11, 6);
|
|
wave_counter = new SignedRegBitSet(bs,0, 32, 11, 7);
|
|
hold = new SignedRegBitSet(bs,0, 32, 11, 8);
|
|
phase = new SignedRegBitSet(bs,0, 32, 11, 9);
|
|
level = new SignedRegBitSet(bs,0, 32, 11, 10);
|
|
}
|
|
public RegBitSetStore bs;
|
|
// 4000, 4004, 400C, 4012:
|
|
public RegBitSet reg0, DutyCycle, EnvDecayDisable, EnvDecayRate, EnvDecayLoopEnable, FixedVolume, LengthCounterDisable, LinearCounterInit, LinearCounterDisable;
|
|
// 4001, 4005, 4013:
|
|
public RegBitSet reg1, SweepShift, SweepDecrease, SweepRate, SweepEnable, PCMlength;
|
|
// 4002, 4006, 400A, 400E:
|
|
public RegBitSet reg2, NoiseFreq, NoiseType, WaveLength;
|
|
// 4003, 4007, 400B, 400F, 4010:
|
|
public RegBitSet reg3, LengthCounterInit, LoopEnabled, IRQenable;
|
|
// Internals:
|
|
public SignedRegBitSet length_counter, linear_counter, address, envelope, sweep_delay, env_delay, wave_counter, hold, phase, level;
|
|
}
|
|
|
|
APUchannel[] channels = new APUchannel[5] { new APUchannel(), new APUchannel(), new APUchannel(), new APUchannel(), new APUchannel() };
|
|
|
|
static readonly byte[] LengthCounters = new byte[32]
|
|
{ 10,254,20, 2,40, 4,80, 6, 160, 8,60,10,14,12,26,14,
|
|
12, 16,24,18,48,20,96,22, 192,24,72,26,16,28,32,30 };
|
|
|
|
static readonly ushort[] NoisePeriods = new ushort[16]
|
|
{ 2,4,8,16,32,48,64,80,101,127,190,254,381,508,1017,2034 };
|
|
|
|
static readonly ushort[] DMCperiods = new ushort[16]
|
|
{ 428,380,340,320, 286,254,226,214, 190,160,142,128, 106,84,72,54 };
|
|
/*
|
|
For PAL:
|
|
static const u16 DMCperiods[16] =
|
|
{ 0x18E,0x162,0x13C,0x12A, 0x114,0x0EC,0x0D2,0x0C6,
|
|
0x0B0,0x094,0x084,0x076, 0x062,0x04E,0x042,0x032 };
|
|
*/
|
|
|
|
const int frame_period = 7458;
|
|
|
|
// Utility function for sound
|
|
bool count(ref int v, int reset)
|
|
{
|
|
if (--v < 0)
|
|
{
|
|
v = reset;
|
|
return true;
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
bool count(ref int v, int reset, int n_at_time)
|
|
{
|
|
if ((v -= n_at_time) < 0)
|
|
{
|
|
v += reset;
|
|
return true;
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
|
|
int tick(int c)
|
|
{
|
|
APUchannel ch = channels[c];
|
|
int wl = ch.WaveLength;
|
|
if (c != 4) ++wl;
|
|
if (c < 2) wl *= 2;
|
|
|
|
if (c == 3) wl = NoisePeriods[ch.NoiseFreq];
|
|
|
|
//if(c != 4) wl = wl * (IO::UISpeed);
|
|
// ^ Match to the UI speed (but don't for DPCM, because it would skew the timings)
|
|
|
|
int volume = (bool)ch.length_counter ? (bool)ch.EnvDecayDisable ? (int)ch.FixedVolume : ch.envelope : 0;
|
|
// Sample may change at wavelen intervals.
|
|
int ref_S = ch.level;
|
|
int ref_ch_wave_counter = ch.wave_counter;
|
|
if (!(data.ChannelsEnabled[c] != 0)
|
|
|| !count(ref ref_ch_wave_counter, wl))
|
|
{
|
|
ch.wave_counter.Value = ref_ch_wave_counter;
|
|
return ref_S;
|
|
}
|
|
ch.wave_counter.Value = ref_ch_wave_counter;
|
|
switch (c)
|
|
{
|
|
case 0:
|
|
default:
|
|
case 1: // Square wave. With four different 8-step binary waveforms (32 bits of data total).
|
|
ch.phase.Value++;
|
|
if (wl < 8) return ref_S;
|
|
if ((bool)ch.SweepEnable && !(bool)ch.SweepDecrease)
|
|
if (wl + (wl >> ch.SweepShift) >= 0x800)
|
|
return ref_S;
|
|
return ref_S = ch.level.Value = (0xF33C0C04u & (1u << (ch.phase % 8 + ch.DutyCycle * 8))) != 0 ? volume : 0;
|
|
case 2: // Triangle wave
|
|
if ((bool)ch.length_counter && (bool)ch.linear_counter && wl >= 3) ++ch.phase.Value;
|
|
return ref_S = ch.level.Value = (ch.phase & 15) ^ (((ch.phase & 16) != 0) ? 15 : 0);
|
|
|
|
case 3: // Noise: Linear feedback shift register
|
|
if (!(bool)ch.hold) ch.hold.Value = 1;
|
|
ch.hold.Value = (ch.hold >> 1)
|
|
| (((ch.hold ^ (ch.hold >> ((bool)ch.NoiseType ? 6 : 1))) & 1) << 14);
|
|
return ref_S = ch.level.Value = ((ch.hold & 1) != 0) ? 0 : volume;
|
|
|
|
case 4: // Delta modulation channel (DMC)
|
|
// hold = 8 bit value
|
|
// phase = number of bits buffered
|
|
// length_counter =
|
|
if (wl == 0) return ref_S;
|
|
if (ch.phase == 0) // Nothing in sample buffer?
|
|
{
|
|
if (!(bool)ch.length_counter && (bool)ch.LoopEnabled) // Loop?
|
|
{
|
|
ch.length_counter.Value = ch.PCMlength * 16 + 1;
|
|
ch.address.Value = (int)((ch.reg0 | 0x300) << 6);
|
|
}
|
|
if (ch.length_counter > 0) // Load next 8 bits if available
|
|
{
|
|
//==========================TODO============== DMC COST=====================
|
|
//for(unsigned t = data.DMC_CycleCost; t > 1; --t)
|
|
// CPU::RB(u16(ch.address) | 0x8000); // timing
|
|
ch.hold.Value = nes.ReadMemory((ushort)((ch.address.Value++) | 0x8000)); // Fetch byte
|
|
ch.phase.Value = 8;
|
|
--ch.length_counter.Value;
|
|
}
|
|
else // Otherwise, disable channel or issue IRQ
|
|
{
|
|
if ((bool)ch.IRQenable)
|
|
{
|
|
//CPU::reg.APU_DMC_IRQ = true;
|
|
dmc_irq = true;
|
|
SyncIRQ();
|
|
}
|
|
|
|
data.ChannelsEnabled[4] = 0;
|
|
}
|
|
}
|
|
if (ch.phase != 0) // Update the signal if sample buffer nonempty
|
|
{
|
|
int v = ch.linear_counter;
|
|
if (((ch.hold << --ch.phase.Value) & 0x80)!=0) v += 2; else v -= 2;
|
|
if (v >= 0 && v <= 0x7F) ch.linear_counter.Value = v;
|
|
}
|
|
return ref_S = ch.level = ch.linear_counter;
|
|
}
|
|
}
|
|
|
|
bool dmc_irq;
|
|
bool sequencer_irq;
|
|
public bool irq_pending;
|
|
void SyncIRQ()
|
|
{
|
|
irq_pending = sequencer_irq | dmc_irq;
|
|
}
|
|
|
|
NES nes;
|
|
public BisqAPU(NES nes)
|
|
{
|
|
this.nes = nes;
|
|
}
|
|
|
|
public void RunOne()
|
|
{
|
|
if (data.IRQ_delay != 0x7FFF)
|
|
{
|
|
if (data.IRQ_delay > 0) --data.IRQ_delay.Value;
|
|
else { sequencer_irq = true; SyncIRQ(); data.IRQ_delay.Value = 0x7FFF; }
|
|
}
|
|
if (data.frame_delay > 0)
|
|
--data.frame_delay.Value;
|
|
else
|
|
{
|
|
bool Do240 = true, Do120 = false;
|
|
data.frame_delay.Value += frame_period;
|
|
switch (data.frame.Value++)
|
|
{
|
|
case 0:
|
|
if (!(bool)data.IRQdisable && !(bool)data.FiveCycleDivider)
|
|
data.IRQ_delay.Value = frame_period * 4 + 2;
|
|
// passthru
|
|
goto case 2;
|
|
case 2:
|
|
Do120 = true;
|
|
break;
|
|
case 1:
|
|
data.frame_delay.Value -= 2;
|
|
break;
|
|
case 3:
|
|
data.frame.Value = 0;
|
|
if ((bool)data.FiveCycleDivider)
|
|
data.frame_delay.Value += frame_period - 6;
|
|
break;
|
|
}
|
|
// Some events are invoked at 96 Hz or 120 Hz rate. Others, 192 Hz or 240 Hz.
|
|
for (int c = 0; c < 4; ++c)
|
|
{
|
|
APUchannel ch = channels[c];
|
|
int wl = ch.WaveLength;
|
|
|
|
// 96/120 Hz events:
|
|
if (Do120)
|
|
{
|
|
// Length tick (all channels except DMC, but different disable bit for triangle wave)
|
|
if ((bool)ch.length_counter
|
|
&& !(c == 2 ? (bool)ch.LinearCounterDisable : (bool)ch.LengthCounterDisable))
|
|
ch.length_counter.Value -= 1;
|
|
|
|
// Sweep tick (square waves only)
|
|
int ref_ch_sweep_delay = ch.sweep_delay;
|
|
if (c < 2 && count(ref ref_ch_sweep_delay, ch.SweepRate))
|
|
if (wl >= 8 && (bool)ch.SweepEnable && (bool)ch.SweepShift)
|
|
{
|
|
int s = wl >> ch.SweepShift;
|
|
wl += ((bool)ch.SweepDecrease ? ((c != 0) ? -s : ~s) : s);
|
|
if (wl < 0x800) ch.WaveLength.Value = (uint)wl;
|
|
}
|
|
|
|
ch.sweep_delay.Value = ref_ch_sweep_delay;
|
|
}
|
|
|
|
// 240/192 Hz events:
|
|
if (Do240)
|
|
{
|
|
// Linear tick (triangle wave only) (all ticks)
|
|
if (c == 2)
|
|
ch.linear_counter.Value = (bool)ch.LinearCounterDisable
|
|
? ch.LinearCounterInit
|
|
: (ch.linear_counter > 0 ? ch.linear_counter - 1 : 0);
|
|
|
|
// Envelope tick (square and noise channels) (all ticks)
|
|
int ref_ch_env_delay = ch.env_delay;
|
|
if (c != 2 && count(ref ref_ch_env_delay, ch.EnvDecayRate))
|
|
if (ch.envelope > 0 || (bool)ch.EnvDecayLoopEnable)
|
|
ch.envelope.Value = (ch.envelope - 1) & 15;
|
|
ch.env_delay.Value = ref_ch_env_delay;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Mix the audio: Get the momentary sample from each channel and mix them.
|
|
// #define s(c) tick<c>()
|
|
//v = [](float m,float n, float d) { return n!=0.f ? m/n : d; };
|
|
Func<float,float,float,float> v = (float m,float n, float d) => ( n!=0.0f ? m/n : d );
|
|
|
|
short sample = (short)(30000 *
|
|
(
|
|
// Square 0 and 1
|
|
v(95.88f, (100.0f + v(8128.0f, tick(0) + tick(1), -100.0f)), 0.0f)
|
|
// Triangle, noise, DMC
|
|
+ v(159.79f, (100.0f + v(1.0f, tick(2)/8227.0f + tick(3)/12241.0f + tick(4)/22638.0f, -1000.0f)), 0.0f)
|
|
// GamePak audio (these volume values are bogus, but sound acceptable)
|
|
+ v(95.88f, (100.0f + v(32512.0f, /*GamePak::ExtAudio()*/ 0, -100.0f)), 0.0f)
|
|
- 0.5f
|
|
));
|
|
|
|
EmitSample(sample);
|
|
|
|
////this (and the similar line below) is a crude hack
|
|
////we should be generating logic to suppress the $4015 clear when the assert signal is set instead
|
|
////be sure to test "apu_test" if you mess with this
|
|
//sequencer_irq |= sequencer_irq_assert;
|
|
}
|
|
|
|
void WriteC(int chno, int index, byte value)
|
|
{
|
|
APUchannel ch = channels[chno];
|
|
switch (index)
|
|
{
|
|
case 0:
|
|
if ((bool)ch.LinearCounterDisable) ch.linear_counter.Value = value & 0x7F;
|
|
ch.reg0.Value = value;
|
|
break;
|
|
case 1:
|
|
ch.reg1.Value = value;
|
|
ch.sweep_delay.Value = ch.SweepRate;
|
|
break;
|
|
case 2:
|
|
ch.reg2.Value = value;
|
|
break;
|
|
case 3:
|
|
ch.reg3.Value = value;
|
|
if (data.ChannelsEnabled[chno]!=0)
|
|
ch.length_counter.Value = LengthCounters[ch.LengthCounterInit];
|
|
ch.linear_counter.Value = ch.LinearCounterInit;
|
|
ch.env_delay.Value = ch.EnvDecayRate;
|
|
ch.envelope.Value = 15;
|
|
if (index < 8) ch.phase.Value = 0;
|
|
break;
|
|
case 0x12:
|
|
ch.reg0.Value = value;
|
|
ch.address.Value = ((int)ch.reg0 | 0x300) << 6;
|
|
break;
|
|
case 0x10:
|
|
ch.reg3.Value = value;
|
|
ch.WaveLength.Value = (uint)(DMCperiods[value & 0x0F] - 1);
|
|
if (!(bool)ch.IRQenable) { dmc_irq = false; SyncIRQ(); }
|
|
break;
|
|
case 0x13: // sample length
|
|
ch.reg1.Value = value;
|
|
if (ch.length_counter == 0)
|
|
ch.length_counter.Value = ch.PCMlength * 16 + 1;
|
|
break;
|
|
case 0x11: // dac value
|
|
ch.linear_counter.Value = value & 0x7F;
|
|
break;
|
|
case 0x15:
|
|
for (int c = 0; c < 5; ++c)
|
|
data.ChannelsEnabled[c] = (uint)((value >> c) & 1); //noteworthy tweak
|
|
for (int c = 0; c < 5; ++c)
|
|
if (data.ChannelsEnabled[c]==0)
|
|
channels[c].length_counter.Value = 0;
|
|
else if (c == 4 && channels[c].length_counter == 0)
|
|
{
|
|
APUchannel chh = channels[c];
|
|
chh.length_counter.Value = chh.PCMlength * 16 + 1;
|
|
chh.address.Value = ((int)chh.reg0 | 0x300) << 6;
|
|
chh.phase.Value = 0;
|
|
}
|
|
//CPU::reg.APU_DMC_IRQ = false;
|
|
dmc_irq = false; SyncIRQ();
|
|
break;
|
|
case 0x17:
|
|
data.IRQdisable.Value = (uint)(value & 0x40);
|
|
data.FiveCycleDivider.Value = (uint)(value & 0x80);
|
|
// apu_test 1-len_ctr: Writing $80 to $4017 should clock length immediately
|
|
// But Writing $00 to $4017 shouldn't clock length immediately
|
|
data.frame_delay.Value &= 1;
|
|
data.frame.Value = 0;
|
|
data.IRQ_delay.Value = 0x7FFF;
|
|
if ((bool)data.IRQdisable) { sequencer_irq = false; SyncIRQ(); }
|
|
if (!(bool)data.FiveCycleDivider)
|
|
{
|
|
data.frame.Value = 1;
|
|
data.frame_delay.Value += frame_period;
|
|
|
|
if (!(bool)data.IRQdisable)
|
|
{
|
|
data.IRQ_delay.Value = data.frame_delay + frame_period * 3 + 1 - 3;
|
|
// ^ "- 3" makes apu_test "4-jitter" not complain
|
|
// that "Frame irq is set too late"
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public byte ReadReg(int addr)
|
|
{
|
|
byte res = 0;
|
|
for (int c = 0; c < 5; ++c) res |= (byte)(((bool)channels[c].length_counter ? 1 << c : 0));
|
|
if (sequencer_irq) res |= 0x40; sequencer_irq = false; SyncIRQ();
|
|
if (dmc_irq) res |= 0x80;
|
|
return res;
|
|
}
|
|
|
|
public void WriteReg(int addr, byte val)
|
|
{
|
|
int index = addr - 0x4000;
|
|
WriteC((index / 4) % 5, index < 0x10 ? index % 4 : index, val);
|
|
}
|
|
|
|
public void NESSoftReset()
|
|
{
|
|
}
|
|
|
|
public void DiscardSamples()
|
|
{
|
|
metaspu.buffer.clear();
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
}
|
|
|
|
double accumulate;
|
|
double timer;
|
|
Queue<int> squeue = new Queue<int>();
|
|
int last_hwsamp;
|
|
int panic_sample, panic_count;
|
|
void EmitSample(int samp)
|
|
{
|
|
//kill the annoying hum that is a consequence of the shitty code below
|
|
if (samp == panic_sample)
|
|
panic_count++;
|
|
else panic_count = 0;
|
|
if (panic_count > 178977)
|
|
samp = 0;
|
|
else
|
|
panic_sample = samp;
|
|
|
|
int this_samp = samp;
|
|
const double kMixRate = 44100.0 / 1789772.0;
|
|
const double kInvMixRate = (1 / kMixRate);
|
|
timer += kMixRate;
|
|
accumulate += samp;
|
|
if (timer <= 1)
|
|
return;
|
|
|
|
accumulate -= samp;
|
|
timer -= 1;
|
|
double ratio = (timer / kMixRate);
|
|
double fractional = (this_samp - last_hwsamp) * ratio;
|
|
double factional_remainder = (this_samp - last_hwsamp) * (1 - ratio);
|
|
accumulate += fractional;
|
|
|
|
accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels
|
|
int outsamp = (int)(accumulate / kInvMixRate);
|
|
if (CFG_USE_METASPU)
|
|
metaspu.buffer.enqueue_sample((short)outsamp, (short)outsamp);
|
|
else squeue.Enqueue(outsamp);
|
|
accumulate = factional_remainder;
|
|
|
|
last_hwsamp = this_samp;
|
|
}
|
|
|
|
MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V);
|
|
public int MaxVolume { get; set; } // not supported
|
|
|
|
void ISoundProvider.GetSamples(short[] samples)
|
|
{
|
|
if (CFG_USE_METASPU)
|
|
{
|
|
metaspu.GetSamples(samples);
|
|
//foreach(short sample in samples) bw.Write((short)sample);
|
|
}
|
|
else
|
|
MyGetSamples(samples);
|
|
|
|
//mix in the cart's extra sound circuit
|
|
nes.board.ApplyCustomAudio(samples);
|
|
}
|
|
|
|
//static BinaryWriter bw = new BinaryWriter(new FileStream("d:\\out.raw",FileMode.Create,FileAccess.Write,FileShare.Read));
|
|
void MyGetSamples(short[] samples)
|
|
{
|
|
//Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2);
|
|
|
|
for (int i = 0; i < samples.Length / 2; i++)
|
|
{
|
|
int samp = 0;
|
|
if (squeue.Count != 0)
|
|
samp = squeue.Dequeue();
|
|
|
|
samples[i * 2 + 0] = (short)(samp);
|
|
samples[i * 2 + 1] = (short)(samp);
|
|
//bw.Write((short)samp);
|
|
}
|
|
}
|
|
|
|
} //class BisqAPU
|
|
|
|
|
|
}
|
|
|
|
} |