303 lines
6.4 KiB
C#
303 lines
6.4 KiB
C#
using System;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Common.NumberExtensions;
|
|
|
|
namespace BizHawk.Emulation.Cores.Components
|
|
{
|
|
public class VRC6Alt
|
|
{
|
|
// http://wiki.nesdev.com/w/index.php/VRC6_audio
|
|
|
|
Pulse pulse1, pulse2;
|
|
Saw saw;
|
|
|
|
Action<int> enqueuer;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="freq">frequency of the M2 clock in hz</param>
|
|
/// <param name="enqueuer">a place to dump deltas to</param>
|
|
public VRC6Alt(Action<int> enqueuer)
|
|
{
|
|
this.enqueuer = enqueuer;
|
|
pulse1 = new Pulse(PulseAddDiff);
|
|
pulse2 = new Pulse(PulseAddDiff);
|
|
saw = new Saw(SawAddDiff);
|
|
}
|
|
|
|
// the two pulse channels are about the same volume as 2a03 pulse channels.
|
|
// everything is flipped, though; but that's taken care of in the classes
|
|
void PulseAddDiff(int value)
|
|
{
|
|
enqueuer(value * 360);
|
|
}
|
|
// saw ends up being not that loud because of differences in implementation
|
|
void SawAddDiff(int value)
|
|
{
|
|
enqueuer(value * 360);
|
|
}
|
|
|
|
// state
|
|
bool masterenable;
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.BeginSection(nameof(VRC6Alt));
|
|
ser.Sync(nameof(masterenable), ref masterenable);
|
|
ser.BeginSection("Pulse1");
|
|
pulse1.SyncState(ser);
|
|
ser.EndSection();
|
|
ser.BeginSection("Pulse2");
|
|
pulse2.SyncState(ser);
|
|
ser.EndSection();
|
|
ser.BeginSection("Saw");
|
|
saw.SyncState(ser);
|
|
ser.EndSection();
|
|
ser.EndSection();
|
|
}
|
|
|
|
public void Write9000(byte value) { pulse1.Write0(value); }
|
|
public void Write9001(byte value) { pulse1.Write1(value); }
|
|
public void Write9002(byte value) { pulse1.Write2(value); }
|
|
|
|
public void Write9003(byte value)
|
|
{
|
|
masterenable = !value.Bit(0);
|
|
int RSHIFT = 0;
|
|
if (value.Bit(1))
|
|
RSHIFT = 4;
|
|
if (value.Bit(2))
|
|
RSHIFT = 8;
|
|
pulse1.SetRSHIFT(RSHIFT);
|
|
pulse2.SetRSHIFT(RSHIFT);
|
|
saw.SetRSHIFT(RSHIFT);
|
|
}
|
|
|
|
public void WriteA000(byte value) { pulse2.Write0(value); }
|
|
public void WriteA001(byte value) { pulse2.Write1(value); }
|
|
public void WriteA002(byte value) { pulse2.Write2(value); }
|
|
|
|
public void WriteB000(byte value) { saw.Write0(value); }
|
|
public void WriteB001(byte value) { saw.Write1(value); }
|
|
public void WriteB002(byte value) { saw.Write2(value); }
|
|
|
|
public void Clock()
|
|
{
|
|
if (masterenable)
|
|
{
|
|
pulse1.Clock();
|
|
pulse2.Clock();
|
|
saw.Clock();
|
|
}
|
|
}
|
|
|
|
class Saw
|
|
{
|
|
Action<int> SendDiff;
|
|
public Saw(Action<int> SendDiff) { this.SendDiff = SendDiff; }
|
|
|
|
// set by regs
|
|
/// <summary>rate of increment for accumulator</summary>
|
|
byte A;
|
|
/// <summary>frequency. actually a reload value</summary>
|
|
int F;
|
|
/// <summary>enable</summary>
|
|
bool E;
|
|
/// <summary>reload shift, from $9003</summary>
|
|
int RSHIFT;
|
|
|
|
// internal state
|
|
/// <summary>frequency counter</summary>
|
|
int count;
|
|
/// <summary>accumulator</summary>
|
|
byte accum;
|
|
/// <summary>saw reset counter</summary>
|
|
int acount;
|
|
/// <summary>latched output, 0..31</summary>
|
|
int output;
|
|
|
|
public void SetRSHIFT(int RSHIFT)
|
|
{
|
|
this.RSHIFT = RSHIFT;
|
|
}
|
|
|
|
void SendNew()
|
|
{
|
|
int newvalue = accum >> 3;
|
|
if (newvalue != output)
|
|
{
|
|
SendDiff(output - newvalue); // intentionally flipped
|
|
output = newvalue;
|
|
}
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync(nameof(A), ref A);
|
|
ser.Sync(nameof(F), ref F);
|
|
ser.Sync(nameof(E), ref E);
|
|
ser.Sync(nameof(RSHIFT), ref RSHIFT);
|
|
ser.Sync(nameof(count), ref count);
|
|
ser.Sync(nameof(accum), ref accum);
|
|
ser.Sync(nameof(acount), ref acount);
|
|
ser.Sync(nameof(output), ref output);
|
|
}
|
|
|
|
public void Write0(byte value)
|
|
{
|
|
A = (byte)(value & 63);
|
|
}
|
|
public void Write1(byte value)
|
|
{
|
|
F &= 0xf00;
|
|
F |= value;
|
|
}
|
|
public void Write2(byte value)
|
|
{
|
|
F &= 0x0ff;
|
|
F |= value << 8 & 0xf00;
|
|
E = value.Bit(7);
|
|
if (!E)
|
|
{
|
|
accum = 0;
|
|
SendNew();
|
|
}
|
|
}
|
|
|
|
public void Clock()
|
|
{
|
|
if (!E)
|
|
return;
|
|
count--;
|
|
if (count < 0)
|
|
{
|
|
count = F >> RSHIFT;
|
|
acount++;
|
|
if (acount % 2 == 0)
|
|
{
|
|
if (acount < 14)
|
|
{
|
|
accum += A;
|
|
}
|
|
else
|
|
{
|
|
accum = 0;
|
|
acount = 0;
|
|
}
|
|
SendNew();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Pulse
|
|
{
|
|
Action<int> SendDiff;
|
|
public Pulse(Action<int> SendDiff) { this.SendDiff = SendDiff; }
|
|
|
|
// set by regs
|
|
/// <summary>volume, 0..15</summary>
|
|
int V;
|
|
/// <summary>duty comparison. forced to max when x000.7 == 1</summary>
|
|
int D;
|
|
/// <summary>frequency. actually a reload value</summary>
|
|
int F;
|
|
/// <summary>enable</summary>
|
|
bool E;
|
|
/// <summary>reload shift, from $9003</summary>
|
|
int RSHIFT;
|
|
|
|
// internal state
|
|
/// <summary>frequency counter</summary>
|
|
int count;
|
|
/// <summary>duty counter</summary>
|
|
int duty;
|
|
/// <summary>latched output, 0..15</summary>
|
|
int output;
|
|
|
|
public void SetRSHIFT(int RSHIFT)
|
|
{
|
|
this.RSHIFT = RSHIFT;
|
|
}
|
|
|
|
void SendNew()
|
|
{
|
|
int newvalue;
|
|
if (duty <= D)
|
|
newvalue = V;
|
|
else
|
|
newvalue = 0;
|
|
if (newvalue != output)
|
|
{
|
|
SendDiff(output - newvalue); // intentionally flipped
|
|
output = newvalue;
|
|
}
|
|
}
|
|
|
|
void SendNewZero()
|
|
{
|
|
if (0 != output)
|
|
{
|
|
SendDiff(output - 0); // intentionally flipped
|
|
output = 0;
|
|
}
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync(nameof(V), ref V);
|
|
ser.Sync(nameof(D), ref D);
|
|
ser.Sync(nameof(F), ref F);
|
|
ser.Sync(nameof(E), ref E);
|
|
ser.Sync(nameof(RSHIFT), ref RSHIFT);
|
|
ser.Sync(nameof(count), ref count);
|
|
ser.Sync(nameof(duty), ref duty);
|
|
ser.Sync(nameof(output), ref output);
|
|
}
|
|
|
|
public void Write0(byte value)
|
|
{
|
|
V = value & 15;
|
|
if (value.Bit(7))
|
|
D = 16;
|
|
else
|
|
D = value >> 4 & 7;
|
|
SendNew(); // this actually happens, right?
|
|
}
|
|
public void Write1(byte value)
|
|
{
|
|
F &= 0xf00;
|
|
F |= value;
|
|
}
|
|
public void Write2(byte value)
|
|
{
|
|
F &= 0x0ff;
|
|
F |= value << 8 & 0xf00;
|
|
E = value.Bit(7);
|
|
if (E)
|
|
SendNew();
|
|
else
|
|
SendNewZero();
|
|
}
|
|
|
|
public void Clock()
|
|
{
|
|
if (!E)
|
|
return;
|
|
count--;
|
|
if (count < 0)
|
|
{
|
|
count = F >> RSHIFT;
|
|
duty--;
|
|
if (duty < 0)
|
|
duty += 16;
|
|
SendNew();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|