Fixed the noise channel. Fixed speech in IM. Added 0xED57 to play WBIII
This commit is contained in:
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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}]";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user