Files
ZXSpectrum48K/Core/Audio/Ay38912.cs
2026-04-30 14:35:12 +01:00

69 lines
2.4 KiB
C#

using System;
namespace Core.Audio
{
public class Ay38912
{
private readonly byte[] _registers = new byte[16];
private byte _selectedRegister = 0;
// The AY-3-8912 uses logarithmic volume, not linear!
// This table converts the 0-15 register value into a precise float multiplier.
private readonly float[] _volumeTable = {
0.0000f, 0.0137f, 0.0205f, 0.0291f, 0.0423f, 0.0618f, 0.0847f, 0.1369f,
0.1691f, 0.2647f, 0.3527f, 0.4499f, 0.5704f, 0.6873f, 0.8482f, 1.0000f
};
// --- Hardware Port Intercepts ---
public void SelectRegister(byte register)
{
_selectedRegister = (byte)(register & 0x0F); // Only 16 registers exist
}
public void WriteRegister(byte value)
{
_registers[_selectedRegister] = value;
}
public byte ReadRegister()
{
return _registers[_selectedRegister];
}
// --- Audio Math Generators ---
// The AY clock on a Spectrum 128 is 1.7734 MHz.
// The chip divides this by 16 internally, giving a base clock of ~110,837 Hz.
private float GetFrequency(int registerLow, int registerHigh)
{
int period = _registers[registerLow] | ((_registers[registerHigh] & 0x0F) << 8);
if (period == 0) return 0f; // Prevent divide-by-zero
return 110837.5f / period;
}
private float GetVolume(int volumeRegister)
{
byte vol = _registers[volumeRegister];
// Bit 4 indicates if the channel is using the hardware Envelope generator.
// For Version 1 of our chip, if the envelope is active, we will just
// output half volume so the game doesn't go silent!
if ((vol & 0x10) != 0)
{
return 0.5f;
}
return _volumeTable[vol & 0x0F];
}
// --- Public Data for your Audio Engine ---
public float FreqA => GetFrequency(0, 1);
public float FreqB => GetFrequency(2, 3);
public float FreqC => GetFrequency(4, 5);
// Register 7 is the Mixer. Bit 0, 1, and 2 turn the tone channels OFF when set to 1.
public float VolA => (_registers[7] & 0x01) == 0 ? GetVolume(8) : 0f;
public float VolB => (_registers[7] & 0x02) == 0 ? GetVolume(9) : 0f;
public float VolC => (_registers[7] & 0x04) == 0 ? GetVolume(10) : 0f;
}
}