Fixed the noise channel. Fixed speech in IM. Added 0xED57 to play WBIII

This commit is contained in:
2026-05-15 22:09:20 +01:00
parent f52a5cbfdb
commit 4e745b4fbc
5 changed files with 411 additions and 380 deletions

View File

@@ -22,7 +22,9 @@ namespace Core.Audio
private int[] _counters = new int[4];
private int[] _polarities = new int[4] { 1, 1, 1, 1 }; // 1 = High, -1 = Low
private ushort _lfsr = 0x8000; // Linear Feedback Shift Register (For Noise)
private bool _noiseFlipFlop = false;
// --- FILTER VARIABLES ---
private float _previousSample = 0f;
private float _previousFiltered = 0f;
// The SN76489 Volume Table reduces amplitude by exactly 2 decibels per step.
private static readonly float[] VolumeTable = {
@@ -84,39 +86,34 @@ namespace Core.Audio
_counters[3]--;
if (_counters[3] <= 0)
{
// Reload the counter
// 1. Reload the counter
int shiftRate = Registers[6] & 0x03;
if (shiftRate == 0) _counters[3] = 0x10; // Fast
else if (shiftRate == 1) _counters[3] = 0x20; // Medium
else if (shiftRate == 2) _counters[3] = 0x40; // Slow
else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2
// THE FIX: Toggle the internal clock (Divide by 2!)
_noiseFlipFlop = !_noiseFlipFlop;
// 2. NO FLIP FLOP! Shift the LFSR immediately!
bool isWhiteNoise = (Registers[6] & 0x04) != 0;
// Read the bits BEFORE shifting
int bit0 = _lfsr & 1;
int bit3 = (_lfsr >> 3) & 1;
// The Master System physically XORs bit 0 and bit 3 for White Noise.
// For Periodic Noise, it just feeds bit 0 straight back in.
int feedback = isWhiteNoise ? (bit0 ^ bit3) : bit0;
// Shift the register right and push the feedback into the highest bit (Bit 15)
_lfsr = (ushort)((_lfsr >> 1) | (feedback << 15));
// Only shift the random static when the clock goes High
if (_noiseFlipFlop)
{
int tappedBit = _lfsr & 1;
_lfsr >>= 1;
if (tappedBit == 1)
{
bool isWhiteNoise = (Registers[6] & 0x04) != 0;
if (isWhiteNoise) _lfsr ^= 0x0009; // SMS-specific XOR mask
_lfsr |= 0x8000; // Inject the high bit
}
// The noise channel output is driven directly by the tapped bit
_polarities[3] = (tappedBit == 1) ? 1 : -1;
}
// The noise speaker is driven directly by the lowest bit
_polarities[3] = (_lfsr & 1) == 1 ? 1 : -1;
}
}
private void MixAndOutputSample()
{
// If the UI hasn't hooked up the speakers yet, just throw the audio away!
if (AudioDevice == null) return;
float sample = 0f;
@@ -124,9 +121,16 @@ namespace Core.Audio
// Mix Tone Channels 0, 1, 2
for (int i = 0; i < 3; i++)
{
// HARDWARE QUIRK: If the tone frequency is 1 or 0, the channel outputs a
// constant DC voltage instead of vibrating, meaning it is effectively silent.
if (Registers[i * 2] > 1)
int tone = Registers[i * 2];
// THE FIX 1: Catch ALL ultrasonic frequencies (1 through 5) to stop Aliasing!
if (tone >= 0 && tone <= 5)
{
// THE FIX 2: Scale it by 0.5f. A fast square wave spends 50% of its time
// high, so it naturally averages out to half volume. This prevents clipping!
sample += 0.5f * VolumeTable[Registers[(i * 2) + 1]];
}
else
{
sample += _polarities[i] * VolumeTable[Registers[(i * 2) + 1]];
}
@@ -135,10 +139,18 @@ namespace Core.Audio
// Mix Noise Channel 3
sample += _polarities[3] * VolumeTable[Registers[7]];
// Divide by 4 so all 4 channels together never exceed 1.0f (which would cause horrible distortion!)
// Divide by 4 so all 4 channels together never exceed 1.0f
sample /= 4.0f;
AudioDevice.AddSample(sample);
// THE FIX 3: The DC Blocker (1-Pole High-Pass Filter)
// PCM speech creates a massive DC offset (pushes the speaker cone forward and leaves it there).
// This filter smoothly pulls the speaker cone back to center at 35Hz, removing the hum and distortion!
float filteredSample = sample - _previousSample + 0.995f * _previousFiltered;
_previousSample = sample;
_previousFiltered = filteredSample;
AudioDevice.AddSample(filteredSample);
}
public void WritePort7F(byte value)
{

View File

@@ -1340,6 +1340,18 @@ namespace Core.Cpu
case 0x56: // IM 1
InterruptMode = 1;
return 8;
case 0x57: // LD A, I
{
AF.High = I; // Copy I into A
byte flags57 = (byte)(AF.Low & 0x01); // Preserve Carry
if ((AF.High & 0x80) != 0) flags57 |= 0x80; // S flag
if (AF.High == 0) flags57 |= 0x40; // Z flag
if (IFF2) flags57 |= 0x04; // P/V flag gets IFF2
flags57 |= (byte)(AF.High & 0x28);
AF.Low = flags57;
return 9;
}
case 0x58: // IN E, (C)
byte inVal58 = ReadPort(BC.Word);
DE.Low = inVal58;

View File

@@ -19,7 +19,8 @@ namespace Core
public ushort? Breakpoint { get; set; } = null;
// NTSC SMS T-States per frame
public const int TStatesPerFrame = 59736;
public const int TStatesPerFrame = 59736; //NTSC
//public const int TStatesPerFrame = 49780; //PAL
public SmsMachine()
{
@@ -46,7 +47,7 @@ namespace Core
public void RunFrame()
{
int tStatesThisFrame = 0;
while (tStatesThisFrame < 59736) // Standard NTSC frame time
while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time
{
// 1. Run one CPU instruction
int cycles = Cpu.Step();