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[] _counters = new int[4];
private int[] _polarities = new int[4] { 1, 1, 1, 1 }; // 1 = High, -1 = Low 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 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. // The SN76489 Volume Table reduces amplitude by exactly 2 decibels per step.
private static readonly float[] VolumeTable = { private static readonly float[] VolumeTable = {
@@ -84,39 +86,34 @@ namespace Core.Audio
_counters[3]--; _counters[3]--;
if (_counters[3] <= 0) if (_counters[3] <= 0)
{ {
// Reload the counter // 1. Reload the counter
int shiftRate = Registers[6] & 0x03; int shiftRate = Registers[6] & 0x03;
if (shiftRate == 0) _counters[3] = 0x10; // Fast if (shiftRate == 0) _counters[3] = 0x10; // Fast
else if (shiftRate == 1) _counters[3] = 0x20; // Medium else if (shiftRate == 1) _counters[3] = 0x20; // Medium
else if (shiftRate == 2) _counters[3] = 0x40; // Slow else if (shiftRate == 2) _counters[3] = 0x40; // Slow
else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2 else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2
// THE FIX: Toggle the internal clock (Divide by 2!) // 2. NO FLIP FLOP! Shift the LFSR immediately!
_noiseFlipFlop = !_noiseFlipFlop;
// 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; bool isWhiteNoise = (Registers[6] & 0x04) != 0;
if (isWhiteNoise) _lfsr ^= 0x0009; // SMS-specific XOR mask
_lfsr |= 0x8000; // Inject the high bit // Read the bits BEFORE shifting
} int bit0 = _lfsr & 1;
int bit3 = (_lfsr >> 3) & 1;
// The noise channel output is driven directly by the tapped bit // The Master System physically XORs bit 0 and bit 3 for White Noise.
_polarities[3] = (tappedBit == 1) ? 1 : -1; // 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));
// The noise speaker is driven directly by the lowest bit
_polarities[3] = (_lfsr & 1) == 1 ? 1 : -1;
} }
} }
private void MixAndOutputSample() private void MixAndOutputSample()
{ {
// If the UI hasn't hooked up the speakers yet, just throw the audio away!
if (AudioDevice == null) return; if (AudioDevice == null) return;
float sample = 0f; float sample = 0f;
@@ -124,9 +121,16 @@ namespace Core.Audio
// Mix Tone Channels 0, 1, 2 // Mix Tone Channels 0, 1, 2
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
// HARDWARE QUIRK: If the tone frequency is 1 or 0, the channel outputs a int tone = Registers[i * 2];
// constant DC voltage instead of vibrating, meaning it is effectively silent.
if (Registers[i * 2] > 1) // 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]]; sample += _polarities[i] * VolumeTable[Registers[(i * 2) + 1]];
} }
@@ -135,10 +139,18 @@ namespace Core.Audio
// Mix Noise Channel 3 // Mix Noise Channel 3
sample += _polarities[3] * VolumeTable[Registers[7]]; 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; 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) public void WritePort7F(byte value)
{ {

View File

@@ -1340,6 +1340,18 @@ namespace Core.Cpu
case 0x56: // IM 1 case 0x56: // IM 1
InterruptMode = 1; InterruptMode = 1;
return 8; 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) case 0x58: // IN E, (C)
byte inVal58 = ReadPort(BC.Word); byte inVal58 = ReadPort(BC.Word);
DE.Low = inVal58; DE.Low = inVal58;

View File

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

View File

@@ -4,8 +4,15 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<!-- This specific line is what maps Label, Button, TextBox, and STAThread -->
<UseWindowsForms>true</UseWindowsForms>
<!-- Single File Settings -->
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -352,5 +359,4 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Core\Core.csproj" /> <ProjectReference Include="..\Core\Core.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -18,7 +18,8 @@ namespace Desktop
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb); private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
private NAudioPlayer _audioPlayer; private NAudioPlayer _audioPlayer;
private Task _emulatorTask; private Task _emulatorTask;
private double TargetFrameTime = 16.667f; private double TargetFrameTime = 16.667f; //NTSC
//private double TargetFrameTime = 20; //PAL
public int TotalFrameCount = 0; public int TotalFrameCount = 0;
public double FrameTime { get; private set; } = 0; public double FrameTime { get; private set; } = 0;
public double FramesPerSecond { get; private set; } = 0; public double FramesPerSecond { get; private set; } = 0;
@@ -152,7 +153,6 @@ namespace Desktop
} }
} }
// --- METRICS MATH ---
double currentTime = _stopwatch.Elapsed.TotalMilliseconds; double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
TotalFrameCount++; TotalFrameCount++;
framesThisSecond++; framesThisSecond++;
@@ -165,7 +165,7 @@ namespace Desktop
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{ {
this.Text = $"Parsons Master System - {_currentRomName} [FPS/FT: {FramesPerSecond:F0}/{FrameTime:F1}]"; this.Text = $"Parsons Master System 2026 - {_currentRomName} [FPS/FT: {FramesPerSecond:F0}/{FrameTime:F1}]";
}); });
} }