nes: vrc6: new audio implementation. sounds awesome
This commit is contained in:
parent
a4b442abda
commit
49f16bcb20
|
@ -532,6 +532,7 @@
|
||||||
<Compile Include="Consoles\Sega\Genesis\IO.cs" />
|
<Compile Include="Consoles\Sega\Genesis\IO.cs" />
|
||||||
<Compile Include="Consoles\Sega\Genesis\MemoryMap.68000.cs" />
|
<Compile Include="Consoles\Sega\Genesis\MemoryMap.68000.cs" />
|
||||||
<Compile Include="Consoles\Sega\Genesis\MemoryMap.Z80.cs" />
|
<Compile Include="Consoles\Sega\Genesis\MemoryMap.Z80.cs" />
|
||||||
|
<Compile Include="Sound\VRC6Alt.cs" />
|
||||||
<Compile Include="Sound\YM2612.cs" />
|
<Compile Include="Sound\YM2612.cs" />
|
||||||
<Compile Include="Consoles\Sega\SMS\BIOS.cs" />
|
<Compile Include="Consoles\Sega\SMS\BIOS.cs" />
|
||||||
<Compile Include="Sound\Utilities\SoundMixer.cs" />
|
<Compile Include="Sound\Utilities\SoundMixer.cs" />
|
||||||
|
|
|
@ -12,7 +12,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
int prg_bank_mask_8k, chr_bank_mask_1k;
|
int prg_bank_mask_8k, chr_bank_mask_1k;
|
||||||
bool newer_variant;
|
bool newer_variant;
|
||||||
|
|
||||||
Sound.VRC6 VRC6Sound = new Sound.VRC6();
|
//Sound.VRC6 VRC6Sound = new Sound.VRC6();
|
||||||
|
Sound.VRC6Alt VRC6Sound;
|
||||||
|
|
||||||
//state
|
//state
|
||||||
int prg_bank_16k, prg_bank_8k;
|
int prg_bank_16k, prg_bank_8k;
|
||||||
|
@ -29,6 +30,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
prg_banks_8k.Dispose();
|
prg_banks_8k.Dispose();
|
||||||
chr_banks_1k.Dispose();
|
chr_banks_1k.Dispose();
|
||||||
|
VRC6Sound.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SyncState(Serializer ser)
|
public override void SyncState(Serializer ser)
|
||||||
|
@ -93,6 +95,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
SyncPRG();
|
SyncPRG();
|
||||||
SetMirrorType(EMirrorType.Vertical);
|
SetMirrorType(EMirrorType.Vertical);
|
||||||
|
|
||||||
|
VRC6Sound = new Sound.VRC6Alt((uint)NES.cpuclockrate);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public override byte ReadPRG(int addr)
|
public override byte ReadPRG(int addr)
|
||||||
|
@ -246,6 +250,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
|
||||||
public override void ClockCPU()
|
public override void ClockCPU()
|
||||||
{
|
{
|
||||||
|
VRC6Sound.Clock();
|
||||||
|
|
||||||
if (!irq_enabled) return;
|
if (!irq_enabled) return;
|
||||||
|
|
||||||
if (irq_mode)
|
if (irq_mode)
|
||||||
|
@ -265,6 +271,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
|
||||||
public override void ApplyCustomAudio(short[] samples)
|
public override void ApplyCustomAudio(short[] samples)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
short[] fmsamples = new short[samples.Length];
|
short[] fmsamples = new short[samples.Length];
|
||||||
VRC6Sound.GetSamples(fmsamples);
|
VRC6Sound.GetSamples(fmsamples);
|
||||||
int len = samples.Length;
|
int len = samples.Length;
|
||||||
|
@ -272,6 +279,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
{
|
{
|
||||||
samples[i] = (short)((samples[i] >> 1) + (fmsamples[i] >> 1));
|
samples[i] = (short)((samples[i] >> 1) + (fmsamples[i] >> 1));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
VRC6Sound.ApplyCustomAudio(samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,314 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Sound
|
||||||
|
{
|
||||||
|
public class VRC6Alt : IDisposable
|
||||||
|
{
|
||||||
|
// http://wiki.nesdev.com/w/index.php/VRC6_audio
|
||||||
|
// $9003 not implemented
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#region blip-buf interface
|
||||||
|
|
||||||
|
Sound.Utilities.BlipBuffer blip;
|
||||||
|
// yes, some of this is copy+pasted from the FDS, and more or less from the NES
|
||||||
|
// as soon as i decide that i like it and i use it a third time, i'll put it in a class
|
||||||
|
|
||||||
|
struct Delta
|
||||||
|
{
|
||||||
|
public uint time;
|
||||||
|
public int value;
|
||||||
|
public Delta(uint time, int value)
|
||||||
|
{
|
||||||
|
this.time = time;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Delta> dlist = new List<Delta>();
|
||||||
|
|
||||||
|
uint sampleclock = 0;
|
||||||
|
const int blipsize = 4096;
|
||||||
|
|
||||||
|
short[] mixout = new short[blipsize];
|
||||||
|
|
||||||
|
public void ApplyCustomAudio(short[] samples)
|
||||||
|
{
|
||||||
|
int nsamp = samples.Length / 2;
|
||||||
|
if (nsamp > blipsize) // oh well.
|
||||||
|
nsamp = blipsize;
|
||||||
|
uint targetclock = (uint)blip.ClocksNeeded(nsamp);
|
||||||
|
foreach (var d in dlist)
|
||||||
|
blip.AddDelta(d.time * targetclock / sampleclock, d.value);
|
||||||
|
dlist.Clear();
|
||||||
|
blip.EndFrame(targetclock);
|
||||||
|
sampleclock = 0;
|
||||||
|
blip.ReadSamples(mixout, nsamp, false);
|
||||||
|
|
||||||
|
for (int i = 0, j = 0; i < nsamp; i++, j += 2)
|
||||||
|
{
|
||||||
|
int s = mixout[i] +samples[j];
|
||||||
|
if (s > 32767)
|
||||||
|
samples[j] = 32767;
|
||||||
|
else if (s <= -32768)
|
||||||
|
samples[j] = -32768;
|
||||||
|
else
|
||||||
|
samples[j] = (short)s;
|
||||||
|
// nes audio is mono, so we can ignore the original value of samples[j+1]
|
||||||
|
samples[j + 1] = samples[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
Pulse pulse1, pulse2;
|
||||||
|
Saw saw;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="freq">frequency of the M2 clock in hz</param>
|
||||||
|
public VRC6Alt(uint freq)
|
||||||
|
{
|
||||||
|
if (freq > 0)
|
||||||
|
{
|
||||||
|
blip = new Utilities.BlipBuffer(blipsize);
|
||||||
|
blip.SetRates(freq, 44100);
|
||||||
|
}
|
||||||
|
pulse1 = new Pulse(PulseAddDiff);
|
||||||
|
pulse2 = new Pulse(PulseAddDiff);
|
||||||
|
saw = new Saw(SawAddDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (blip != null)
|
||||||
|
{
|
||||||
|
blip.Dispose();
|
||||||
|
blip = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseAddDiff(int value)
|
||||||
|
{
|
||||||
|
dlist.Add(new Delta(sampleclock, value * 360));
|
||||||
|
}
|
||||||
|
void SawAddDiff(int value)
|
||||||
|
{
|
||||||
|
dlist.Add(new Delta(sampleclock, value * 180));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SyncState(Serializer ser)
|
||||||
|
{
|
||||||
|
ser.BeginSection("VRC6Alt");
|
||||||
|
pulse1.SyncState(ser);
|
||||||
|
pulse2.SyncState(ser);
|
||||||
|
saw.SyncState(ser);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
pulse1.Clock();
|
||||||
|
pulse2.Clock();
|
||||||
|
saw.Clock();
|
||||||
|
sampleclock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Saw
|
||||||
|
{
|
||||||
|
Action<int> SendDiff;
|
||||||
|
public Saw(Action<int> SendDiff) { this.SendDiff = SendDiff; }
|
||||||
|
|
||||||
|
// set by regs
|
||||||
|
byte A;
|
||||||
|
int F;
|
||||||
|
bool E;
|
||||||
|
// internal state
|
||||||
|
int count;
|
||||||
|
byte accum;
|
||||||
|
int acount;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
void SendNew()
|
||||||
|
{
|
||||||
|
int newvalue = accum >> 3;
|
||||||
|
if (newvalue != value)
|
||||||
|
{
|
||||||
|
SendDiff(value - newvalue); // intentionally flipped
|
||||||
|
value = newvalue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SyncState(Serializer ser)
|
||||||
|
{
|
||||||
|
ser.Sync("A", ref A);
|
||||||
|
ser.Sync("F", ref F);
|
||||||
|
ser.Sync("E", ref E);
|
||||||
|
ser.Sync("count", ref count);
|
||||||
|
ser.Sync("accum", ref accum);
|
||||||
|
ser.Sync("acount", ref acount);
|
||||||
|
ser.Sync("value", ref value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
int V;
|
||||||
|
int D;
|
||||||
|
int F;
|
||||||
|
bool E;
|
||||||
|
// internal state
|
||||||
|
int count;
|
||||||
|
int duty;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
void SendNew()
|
||||||
|
{
|
||||||
|
int newvalue;
|
||||||
|
if (duty <= D)
|
||||||
|
newvalue = V;
|
||||||
|
else
|
||||||
|
newvalue = 0;
|
||||||
|
if (newvalue != value)
|
||||||
|
{
|
||||||
|
SendDiff(value - newvalue); // intentionally flipped
|
||||||
|
value = newvalue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SendNewZero()
|
||||||
|
{
|
||||||
|
if (0 != value)
|
||||||
|
{
|
||||||
|
SendDiff(value - 0); // intentionally flipped
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SyncState(Serializer ser)
|
||||||
|
{
|
||||||
|
ser.Sync("V", ref V);
|
||||||
|
ser.Sync("D", ref D);
|
||||||
|
ser.Sync("F", ref F);
|
||||||
|
ser.Sync("E", ref E);
|
||||||
|
ser.Sync("count", ref count);
|
||||||
|
ser.Sync("duty", ref duty);
|
||||||
|
ser.Sync("value", ref value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
duty--;
|
||||||
|
if (duty < 0)
|
||||||
|
duty += 16;
|
||||||
|
SendNew();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue