Essgee.Unity/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs

191 lines
3.6 KiB
C#
Raw Normal View History

2025-01-02 17:55:16 +08:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Essgee.Emulation.Audio
{
public partial class DMGAudio
{
public class Wave : IDMGAudioChannel
{
// NR30
bool isDacEnabled;
// NR31
byte lengthLoad;
// NR32
byte volumeCode;
// NR33
byte frequencyLSB;
// NR34
bool trigger, lengthEnable;
byte frequencyMSB;
// Wave
protected byte[] sampleBuffer;
int frequencyCounter, positionCounter, volume;
// Misc
bool isChannelEnabled;
int lengthCounter;
public int OutputVolume { get; private set; }
public bool IsActive { get { return isDacEnabled; } } // TODO: correct? lengthCounter check makes Zelda Oracle games hang
public Wave()
{
sampleBuffer = new byte[16];
}
public virtual void Reset()
{
for (var i = 0; i < sampleBuffer.Length; i++) sampleBuffer[i] = (byte)StandInfo.Random.Next(255);
frequencyCounter = positionCounter = 0;
volume = 15;
isChannelEnabled = isDacEnabled = false;
lengthCounter = 0;
OutputVolume = volume;
}
public void LengthCounterClock()
{
if (lengthCounter > 0 && lengthEnable)
{
lengthCounter--;
if (lengthCounter == 0)
isChannelEnabled = false;
}
}
public void SweepClock()
{
throw new Exception("Channel type does not support sweep");
}
public void VolumeEnvelopeClock()
{
throw new Exception("Channel type does not support envelope");
}
public void Step()
{
if (!isChannelEnabled) return;
frequencyCounter--;
if (frequencyCounter == 0)
{
frequencyCounter = (2048 - ((frequencyMSB << 8) | frequencyLSB)) * 2;
positionCounter++;
positionCounter %= 32;
var value = sampleBuffer[positionCounter / 2];
if ((positionCounter & 0b1) == 0) value >>= 4;
value &= 0b1111;
if (volumeCode != 0)
volume = value >> (volumeCode - 1);
else
volume = 0;
}
OutputVolume = isDacEnabled ? volume : 0;
}
private void Trigger()
{
isChannelEnabled = true;
if (lengthCounter == 0) lengthCounter = 256;
frequencyCounter = (2048 - ((frequencyMSB << 8) | frequencyLSB)) * 2;
positionCounter = 0;
}
public void WritePort(byte port, byte value)
{
switch (port)
{
case 0:
isDacEnabled = ((value >> 7) & 0b1) == 0b1;
break;
case 1:
lengthLoad = value;
lengthCounter = 256 - lengthLoad;
break;
case 2:
volumeCode = (byte)((value >> 5) & 0b11);
break;
case 3:
frequencyLSB = value;
break;
case 4:
trigger = ((value >> 7) & 0b1) == 0b1;
lengthEnable = ((value >> 6) & 0b1) == 0b1;
frequencyMSB = (byte)((value >> 0) & 0b111);
if (trigger) Trigger();
break;
}
}
public byte ReadPort(byte port)
{
switch (port)
{
case 0:
return (byte)(
0x7F |
(isDacEnabled ? (1 << 7) : 0));
case 1:
return 0xFF;
case 2:
return (byte)(
0x9F |
(volumeCode << 5));
case 4:
return (byte)(
0xBF |
(lengthEnable ? (1 << 6) : 0));
default:
return 0xFF;
}
}
// TODO: more details on behavior on access w/ channel enabled
public void WriteWaveRam(byte offset, byte value)
{
if (!isDacEnabled)
sampleBuffer[offset & (sampleBuffer.Length - 1)] = value;
else
sampleBuffer[positionCounter & (sampleBuffer.Length - 1)] = value;
}
public byte ReadWaveRam(byte offset)
{
if (!isDacEnabled)
return sampleBuffer[offset & (sampleBuffer.Length - 1)];
else
return 0xFF;
}
}
}
}