BizHawk/BizHawk.Emulation.Common/Sound/MMC5Audio.cs

321 lines
6.5 KiB
C#

using System;
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
namespace BizHawk.Emulation.Common.Components
{
public class MMC5Audio
{
class Pulse
{
// regs
private int V;
private int T;
private int L;
private int D;
private bool LenCntDisable;
private bool ConstantVolume;
private bool Enable;
// envelope
private bool estart;
private int etime;
private int ecount;
// length
private static readonly int[] lenlookup =
{
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
};
int length;
// pulse
private int sequence;
private static readonly int[,] sequencelookup =
{
{0,0,0,0,0,0,0,1},
{0,0,0,0,0,0,1,1},
{0,0,0,0,1,1,1,1},
{1,1,1,1,1,1,0,0}
};
private int clock;
private int output;
private readonly Action<int> SendDiff;
public Pulse(Action<int> SendDiff)
{
this.SendDiff = SendDiff;
}
public void SyncState(Serializer ser)
{
ser.Sync("V", ref V);
ser.Sync("T", ref T);
ser.Sync("L", ref L);
ser.Sync("D", ref D);
ser.Sync("LenCntDisable", ref LenCntDisable);
ser.Sync("ConstantVolume", ref ConstantVolume);
ser.Sync("Enable", ref Enable);
ser.Sync("estart", ref estart);
ser.Sync("etime", ref etime);
ser.Sync("ecount", ref ecount);
ser.Sync("length", ref length);
ser.Sync("sequence", ref sequence);
ser.Sync("clock", ref clock);
ser.Sync("output", ref output);
}
public void Write0(byte val)
{
V = val & 15;
ConstantVolume = val.Bit(4);
LenCntDisable = val.Bit(5);
D = val >> 6;
}
public void Write2(byte val)
{
T &= 0x700;
T |= val;
}
public void Write3(byte val)
{
T &= 0xff;
T |= val << 8 & 0x700;
L = val >> 3;
estart = true;
if (Enable)
length = lenlookup[L];
sequence = 0;
}
public void SetEnable(bool val)
{
Enable = val;
if (!Enable)
length = 0;
}
public bool ReadLength()
{
return length > 0;
}
public void ClockFrame()
{
// envelope
if (estart)
{
estart = false;
ecount = 15;
etime = V;
}
else
{
etime--;
if (etime < 0)
{
etime = V;
if (ecount > 0)
{
ecount--;
}
else if (LenCntDisable)
{
ecount = 15;
}
}
}
// length
if (Enable && !LenCntDisable && length > 0)
{
length--;
}
}
public void Clock()
{
clock--;
if (clock < 0)
{
clock = T * 2 + 1;
sequence--;
if (sequence < 0)
sequence += 8;
int sequenceval = sequencelookup[D, sequence];
int newvol = 0;
if (sequenceval > 0 && length > 0)
{
if (ConstantVolume)
newvol = V;
else
newvol = ecount;
}
if (newvol != output)
{
//Console.WriteLine("{0},{1}", newvol, output);
SendDiff(output - newvol);
output = newvol;
}
}
}
}
private readonly Pulse[] pulse = new Pulse[2];
/// <summary>
///
/// </summary>
/// <param name="addr">0x5000..0x5015</param>
/// <param name="val"></param>
public void WriteExp(int addr, byte val)
{
switch (addr)
{
case 0x5000: pulse[0].Write0(val); break;
case 0x5002: pulse[0].Write2(val); break;
case 0x5003: pulse[0].Write3(val); break;
case 0x5004: pulse[1].Write0(val); break;
case 0x5006: pulse[1].Write2(val); break;
case 0x5007: pulse[1].Write3(val); break;
case 0x5010: // pcm mode/irq
PCMRead = val.Bit(0);
PCMEnableIRQ = val.Bit(7);
RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
break;
case 0x5011: // PCM value
if (!PCMRead)
WritePCM(val);
break;
case 0x5015:
pulse[0].SetEnable(val.Bit(0));
pulse[1].SetEnable(val.Bit(1));
break;
}
}
public byte Read5015()
{
byte ret = 0;
if (pulse[0].ReadLength())
ret |= 1;
if (pulse[1].ReadLength())
ret |= 2;
return ret;
}
public byte Read5010()
{
byte ret = 0;
if (PCMEnableIRQ && PCMIRQTriggered)
{
ret |= 0x80;
}
PCMIRQTriggered = false; // ack
RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
return ret;
}
public byte Peek5010()
{
byte ret = 0;
if (PCMEnableIRQ && PCMIRQTriggered)
{
ret |= 0x80;
}
return ret;
}
/// <summary>
/// call for 8000:bfff reads
/// </summary>
/// <param name="val"></param>
public void ReadROMTrigger(byte val)
{
if (PCMRead)
WritePCM(val);
}
void WritePCM(byte val)
{
if (val == 0)
{
PCMIRQTriggered = true;
}
else
{
PCMIRQTriggered = false;
// can't set diff here, because APU cycle clock might be wrong
PCMNextVal = val;
}
RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
}
private readonly Action<bool> RaiseIRQ;
const int framereload = 7458; // ???
private int frame;
private bool PCMRead;
private bool PCMEnableIRQ;
private bool PCMIRQTriggered;
private byte PCMVal;
private byte PCMNextVal;
public void SyncState(Serializer ser)
{
ser.BeginSection("MMC5Audio");
ser.Sync("frame", ref frame);
ser.BeginSection("Pulse0");
pulse[0].SyncState(ser);
ser.EndSection();
ser.BeginSection("Pulse1");
pulse[1].SyncState(ser);
ser.EndSection();
ser.Sync("PCMRead", ref PCMRead);
ser.Sync("PCMEnableIRQ", ref PCMEnableIRQ);
ser.Sync("PCMIRQTriggered", ref PCMIRQTriggered);
ser.Sync("PCMVal", ref PCMVal);
ser.Sync("PCMNextVal", ref PCMNextVal);
ser.EndSection();
if (ser.IsReader)
RaiseIRQ(PCMEnableIRQ && PCMIRQTriggered);
}
public void Clock()
{
pulse[0].Clock();
pulse[1].Clock();
frame++;
if (frame == framereload)
{
frame = 0;
pulse[0].ClockFrame();
pulse[1].ClockFrame();
}
if (PCMNextVal != PCMVal)
{
enqueuer(20 * (PCMVal - PCMNextVal));
PCMVal = PCMNextVal;
}
}
private readonly Action<int> enqueuer;
private void PulseAddDiff(int value)
{
enqueuer(value * 370);
//Console.WriteLine(value);
}
public MMC5Audio(Action<int> enqueuer, Action<bool> RaiseIRQ)
{
this.enqueuer = enqueuer;
this.RaiseIRQ = RaiseIRQ;
for (int i = 0; i < pulse.Length; i++)
pulse[i] = new Pulse(PulseAddDiff);
}
}
}