GBA.Unity/Assets/emulator/GbaAudio.cs
2024-08-16 14:51:15 +08:00

483 lines
16 KiB
C#

using System;
using static OptimeGBA.Bits;
using System.Runtime.CompilerServices;
namespace OptimeGBA
{
public enum ResamplingMode
{
None,
Linear,
Sinc,
SincLowPass,
}
public sealed class CircularBufferOverwriting<T>
{
public uint Size;
public T[] Buffer;
public uint ReadPos = 0;
public uint WritePos = 0;
public CircularBufferOverwriting(uint size)
{
Size = size;
Buffer = new T[Size];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Insert(T data)
{
Buffer[WritePos++] = data;
if (WritePos >= Size)
{
WritePos = 0;
}
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Pop()
{
T data = Buffer[ReadPos++];
if (ReadPos >= Size)
{
ReadPos = 0;
}
return data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek(int offset)
{
return Buffer[(ReadPos + offset) % Size];
}
public void Reset()
{
ReadPos = 0;
WritePos = 0;
}
}
public sealed class CircularBuffer<T>
{
public uint Size;
public T[] Buffer;
public T EmptyValue;
public uint ReadPos = 0;
public uint WritePos = 0;
public uint Entries = 0;
public uint TotalPops = 0;
public uint EmptyPops = 0;
public uint FullInserts = 0;
public uint Collisions = 0;
public CircularBuffer(uint size, T emptyValue)
{
Size = size;
Buffer = new T[Size];
EmptyValue = emptyValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Insert(T data)
{
if (Entries < Size)
{
if (ReadPos == WritePos) Collisions++;
Entries++;
Buffer[WritePos++] = data;
if (WritePos >= Size)
{
WritePos = 0;
}
return true;
}
FullInserts++;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Pop()
{
T data;
TotalPops++;
if (Entries > 0)
{
Entries--;
data = Buffer[ReadPos++];
if (ReadPos >= Size)
{
ReadPos = 0;
}
}
else
{
EmptyPops++;
data = EmptyValue;
}
return data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Peek(int offset)
{
return Buffer[(ReadPos + offset) % Size];
}
public void Reset()
{
Entries = 0;
ReadPos = 0;
WritePos = 0;
}
}
public sealed class GbaAudio
{
Gba Gba;
Scheduler Scheduler;
public GbaAudio(Gba gba, Scheduler scheduler)
{
Gba = gba;
Scheduler = scheduler;
Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample, Sample);
}
public GbAudio GbAudio = new GbAudio();
public bool DebugEnableA = true;
public bool DebugEnableB = true;
public CircularBuffer<byte> A = new CircularBuffer<byte>(32, 0);
public CircularBuffer<byte> B = new CircularBuffer<byte>(32, 0);
public CircularBufferOverwriting<short> VisBufA = new CircularBufferOverwriting<short>(1024);
public CircularBufferOverwriting<short> VisBufB = new CircularBufferOverwriting<short>(1024);
public short CurrentValueA;
public short CurrentValueB;
public short PreviousValueA;
public short PreviousValueB;
public long LastSampleTimeA;
public long LastSampleTimeB;
public long IntervalA = 1;
public long IntervalB = 1;
uint BiasLevel = 0x100;
uint AmplitudeRes;
// SOUNDCNT_H
uint SoundVolume = 0; // 0-1
byte DmaSoundAVolume = 0; // 2
byte DmaSoundBVolume = 0; // 3
bool DmaSoundAEnableRight = false; // 8
bool DmaSoundAEnableLeft = false; // 9
bool DmaSoundATimerSelect = false; // 10
bool DmaSoundBEnableRight = false; // 11
bool DmaSoundBEnableLeft = false; // 12
bool DmaSoundBTimerSelect = false; // 13
bool MasterEnable = false;
public byte ReadHwio8(uint addr)
{
byte val = 0;
switch (addr)
{
case 0x4000082: // SOUNDCNT_H B0
val |= (byte)((SoundVolume >> 0) & 0b11); // 0-1
val |= (byte)(DmaSoundAVolume << 2); // 2
val |= (byte)(DmaSoundBVolume << 3); // 3
break;
case 0x4000083: // SOUNDCNT_H B1
if (DmaSoundAEnableRight) val = BitSet(val, 8 - 8); // 8
if (DmaSoundAEnableLeft) val = BitSet(val, 9 - 8); // 9
if (DmaSoundATimerSelect) val = BitSet(val, 10 - 8); // 10
if (DmaSoundBEnableRight) val = BitSet(val, 12 - 8); // 12
if (DmaSoundBEnableLeft) val = BitSet(val, 13 - 8); // 13
if (DmaSoundBTimerSelect) val = BitSet(val, 14 - 8); // 14
break;
// Special case, because SOUNDCNT_X contains both GB Audio status and GBA audio status
case 0x4000084:
// NR52
byte i = 0;
i |= 0b01110000;
if (GbAudio.noise_enabled && GbAudio.noise_dacEnabled) i |= (byte)BIT_3;
if (GbAudio.wave_enabled && GbAudio.wave_dacEnabled) i |= (byte)BIT_2;
if (GbAudio.pulse2_enabled && GbAudio.pulse2_dacEnabled) i |= (byte)BIT_1;
if (GbAudio.pulse1_enabled && GbAudio.pulse1_dacEnabled) i |= (byte)BIT_0;
if (MasterEnable) i |= (byte)BIT_7;
return i;
case 0x4000088: // SOUNDBIAS B0
val |= (byte)(BiasLevel << 1);
break;
case 0x4000089: // SOUNDBIAS B1
val |= (byte)(BiasLevel >> 7);
val |= (byte)(AmplitudeRes << 6);
break;
}
if (addr >= 0x4000060 && addr <= 0x4000084)
{
// GB Registers
val = GbAudio.ReadHwio8(addr & 0xFF);
}
else if (addr >= 0x4000090 && addr <= 0x400009F)
{
// Wave RAM
val = GbAudio.ReadHwio8(addr & 0xFF);
}
return val;
}
public void WriteHwio8(uint addr, byte val)
{
if (addr >= 0x4000060 && addr <= 0x400009F)
{
GbAudio.WriteHwio8(addr & 0xFF, val);
}
switch (addr)
{
case 0x4000082: // SOUNDCNT_H B0
SoundVolume = (uint)(val & 0b11); // 0-1
DmaSoundAVolume = (byte)((val >> 2) & 1); // 2
DmaSoundBVolume = (byte)((val >> 3) & 1); // 3
break;
case 0x4000083: // SOUNDCNT_H B1
DmaSoundAEnableRight = BitTest(val, 8 - 8); // 8
DmaSoundAEnableLeft = BitTest(val, 9 - 8); // 9
DmaSoundATimerSelect = BitTest(val, 10 - 8); // 10
if (BitTest(val, 11 - 8)) A.Reset();
DmaSoundBEnableRight = BitTest(val, 12 - 8); // 12
DmaSoundBEnableLeft = BitTest(val, 13 - 8); // 13
DmaSoundBTimerSelect = BitTest(val, 14 - 8); // 14
if (BitTest(val, 15 - 8)) B.Reset();
break;
case 0x4000084: // SOUNDCNT_X
MasterEnable = BitTest(val, 7);
break;
case 0x4000088: // SOUNDBIAS B0
BiasLevel &= 0b110000000;
BiasLevel |= (uint)((val >> 1) & 0b1111111);
break;
case 0x4000089: // SOUNDBIAS B1
BiasLevel &= 0b001111111;
BiasLevel |= (uint)((val & 0b11) << 7);
AmplitudeRes &= 0;
AmplitudeRes |= (uint)((val >> 6) & 0b11);
break;
case 0x40000A0:
case 0x40000A1:
case 0x40000A2:
case 0x40000A3:
// Gba.Arm7.Error("FIFO Insert");
A.Insert(val);
break;
case 0x40000A4:
case 0x40000A5:
case 0x40000A6:
case 0x40000A7:
// Gba.Arm7.Error("FIFO Insert");
B.Insert(val);
break;
}
}
public bool CollectSamples = true;
public bool EnablePsg = true;
public bool EnableFifo = true;
public BlipBuf BlipBuf = new BlipBuf(32, true, 2);
public ResamplingMode ResamplingMode = ResamplingMode.Sinc;
public const int SampleRate = 32768;
const int CyclesPerSample = 16777216 / SampleRate;
// public CircularBuffer<short> SampleBuffer = new CircularBuffer<short>(32768, 0);
public const uint SampleBufferMax = 256;
public float[] SampleBuffer = new float[SampleBufferMax];
public uint SampleBufferPos = 0;
public bool AudioReady;
public uint VisSamplingTimer = 0;
public void Sample(long cyclesLate)
{
GbAudio.Tick(CyclesPerSample / 4); // convert to GB sample rate
BlipBuf.ReadOutSample();
double fifoASinc = BlipBuf.CurrentValL;
double fifoBSinc = BlipBuf.CurrentValR;
short fifoA = 0;
short fifoB = 0;
short psgA = 0;
short psgB = 0;
if (MasterEnable)
{
if (EnablePsg)
{
psgA += GbAudio.Out1;
psgB += GbAudio.Out2;
}
if (EnableFifo)
{
switch (ResamplingMode)
{
case ResamplingMode.None:
if (DebugEnableA)
{
if (DmaSoundAEnableLeft) fifoA += CurrentValueA;
if (DmaSoundAEnableRight) fifoB += CurrentValueA;
}
if (DebugEnableB)
{
if (DmaSoundBEnableLeft) fifoA += CurrentValueB;
if (DmaSoundBEnableRight) fifoB += CurrentValueB;
}
break;
case ResamplingMode.Linear:
long current = Scheduler.CurrentTicks - cyclesLate;
if (DebugEnableA)
{
double ratio = (current - LastSampleTimeA) / (double)IntervalA;
double valDouble = (PreviousValueA + ratio * (double)(CurrentValueA - PreviousValueA));
short val = (short)valDouble;
if (DmaSoundAEnableLeft) fifoA += val;
if (DmaSoundAEnableRight) fifoB += val;
}
if (DebugEnableB)
{
double ratio = (current - LastSampleTimeB) / (double)IntervalB;
double valDouble = (PreviousValueB + ratio * (double)(CurrentValueB - PreviousValueB));
short val = (short)valDouble;
if (DmaSoundBEnableLeft) fifoA += val;
if (DmaSoundBEnableRight) fifoB += val;
}
break;
case ResamplingMode.Sinc:
case ResamplingMode.SincLowPass:
if (DebugEnableA)
{
if (DmaSoundAEnableLeft) fifoA += (short)(fifoASinc);
if (DmaSoundAEnableRight) fifoB += (short)(fifoASinc);
}
if (DebugEnableB)
{
if (DmaSoundBEnableLeft) fifoA += (short)(fifoBSinc);
if (DmaSoundBEnableRight) fifoB += (short)(fifoBSinc);
}
break;
}
}
}
if (++VisSamplingTimer >= 4)
{
VisSamplingTimer = 0;
VisBufA.Insert(CurrentValueA);
VisBufB.Insert(CurrentValueB);
}
SampleBuffer[SampleBufferPos++] = ((fifoA + psgA) * 64f)/ SampleRate;
SampleBuffer[SampleBufferPos++] = ((fifoB + psgB) * 64f)/ SampleRate;
if (SampleBufferPos >= SampleBufferMax)
{
SampleBufferPos = 0;
Gba.AudioCallback(SampleBuffer);
}
Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample - cyclesLate, Sample);
}
public void TimerOverflowFifoA(long cyclesLate, uint timerId)
{
LastSampleTimeA = Scheduler.CurrentTicks - cyclesLate;
PreviousValueA = CurrentValueA;
IntervalA = Gba.Timers.T[timerId].Interval;
CurrentValueA = (short)((sbyte)A.Pop() << DmaSoundAVolume);
BlipBuf.SetValue(0, (float)LastSampleTimeA / CyclesPerSample, (float)CurrentValueA, 0);
if (A.Entries <= 16)
{
Gba.Dma.RepeatFifoA();
}
}
public void TimerOverflowFifoB(long cyclesLate, uint timerId)
{
LastSampleTimeB = Scheduler.CurrentTicks - cyclesLate;
PreviousValueB = CurrentValueB;
IntervalB = Gba.Timers.T[timerId].Interval;
CurrentValueB = (short)((sbyte)B.Pop() << DmaSoundBVolume);
BlipBuf.SetValue(1, (float)LastSampleTimeB / CyclesPerSample, 0, (float)CurrentValueB);
if (B.Entries <= 16)
{
Gba.Dma.RepeatFifoB();
}
}
// Called when Timer 0 or 1 overflows.
public void TimerOverflow(long cyclesLate, uint timerId)
{
if (timerId == 0)
{
if (!DmaSoundATimerSelect)
{
TimerOverflowFifoA(cyclesLate, timerId);
}
if (!DmaSoundBTimerSelect)
{
TimerOverflowFifoB(cyclesLate, timerId);
}
}
else if (timerId == 1)
{
if (DmaSoundATimerSelect)
{
TimerOverflowFifoA(cyclesLate, timerId);
}
if (DmaSoundBTimerSelect)
{
TimerOverflowFifoB(cyclesLate, timerId);
}
}
}
}
}