From f52a5cbfdbda432d5f4a9da821be0358bf71fb02 Mon Sep 17 00:00:00 2001 From: parsons Date: Fri, 15 May 2026 16:34:34 +0100 Subject: [PATCH] Added VRAM Viewer and tried to fix noise channel --- Core/Audio/SmsApu.cs | 33 ++++-- Desktop/Desktop.csproj | 166 ++++++++++++++-------------- Desktop/Form1.Designer.cs | 13 ++- Desktop/Form1.cs | 11 ++ Desktop/ROMS/Golden Axe Warrior.sav | Bin 0 -> 32768 bytes Desktop/VramViewerForm.cs | 100 +++++++++++++++++ 6 files changed, 227 insertions(+), 96 deletions(-) create mode 100644 Desktop/ROMS/Golden Axe Warrior.sav create mode 100644 Desktop/VramViewerForm.cs diff --git a/Core/Audio/SmsApu.cs b/Core/Audio/SmsApu.cs index 338d34c..30e7253 100644 --- a/Core/Audio/SmsApu.cs +++ b/Core/Audio/SmsApu.cs @@ -22,6 +22,7 @@ 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; // The SN76489 Volume Table reduces amplitude by exactly 2 decibels per step. private static readonly float[] VolumeTable = { @@ -83,27 +84,33 @@ namespace Core.Audio _counters[3]--; if (_counters[3] <= 0) { - // Noise rate depends on Bits 0-1 of Register 6 + // 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! + else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2 - // Shift the Noise LFSR - int tappedBit = _lfsr & 1; - _lfsr >>= 1; + // THE FIX: Toggle the internal clock (Divide by 2!) + _noiseFlipFlop = !_noiseFlipFlop; - if (tappedBit == 1) + // Only shift the random static when the clock goes High + if (_noiseFlipFlop) { - bool isWhiteNoise = (Registers[6] & 0x04) != 0; - // The Sega Master System physically tapped bits 0 and 3 for its white noise - if (isWhiteNoise) _lfsr ^= 0x0009; + int tappedBit = _lfsr & 1; + _lfsr >>= 1; - _lfsr |= 0x8000; // Inject the high bit + 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; } - - _polarities[3] = (tappedBit == 1) ? 1 : -1; } } @@ -149,12 +156,14 @@ namespace Core.Audio // Volume registers (1, 3, 5, 7) and Noise Control (6) only hold 4 bits total. // We completely overwrite them. Registers[_latchedRegister] = (ushort)data; + if (_latchedRegister == 6) _lfsr = 0x8000; } else { // Tone registers (0, 2, 4) hold 10 bits. // A Latch byte ONLY overwrites the bottom 4 bits and leaves the top 6 alone! Registers[_latchedRegister] = (ushort)((Registers[_latchedRegister] & 0x03F0) | data); + if (_latchedRegister == 6) _lfsr = 0x8000; } } else diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj index 9b6c28d..3441ec4 100644 --- a/Desktop/Desktop.csproj +++ b/Desktop/Desktop.csproj @@ -90,255 +90,257 @@ + - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never - Always + Never + - Always + Never diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index fa9b97c..6786aa3 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -40,6 +40,7 @@ resetToolStripMenuItem = new ToolStripMenuItem(); helpToolStripMenuItem = new ToolStripMenuItem(); aboutToolStripMenuItem = new ToolStripMenuItem(); + vRAMViewerToolStripMenuItem = new ToolStripMenuItem(); menuStrip1.SuspendLayout(); SuspendLayout(); // @@ -90,7 +91,7 @@ // // viewToolStripMenuItem // - viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { debuggerToolStripMenuItem }); + viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { debuggerToolStripMenuItem, vRAMViewerToolStripMenuItem }); viewToolStripMenuItem.Name = "viewToolStripMenuItem"; viewToolStripMenuItem.Size = new Size(65, 29); viewToolStripMenuItem.Text = "View"; @@ -98,7 +99,7 @@ // debuggerToolStripMenuItem // debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem"; - debuggerToolStripMenuItem.Size = new Size(194, 34); + debuggerToolStripMenuItem.Size = new Size(270, 34); debuggerToolStripMenuItem.Text = "Debugger"; debuggerToolStripMenuItem.Click += debuggerToolStripMenuItem_Click; // @@ -129,6 +130,13 @@ aboutToolStripMenuItem.Size = new Size(164, 34); aboutToolStripMenuItem.Text = "About"; // + // vRAMViewerToolStripMenuItem + // + vRAMViewerToolStripMenuItem.Name = "vRAMViewerToolStripMenuItem"; + vRAMViewerToolStripMenuItem.Size = new Size(270, 34); + vRAMViewerToolStripMenuItem.Text = "VRAM Viewer"; + vRAMViewerToolStripMenuItem.Click += vramViewerToolStripMenuItem_Click; + // // ParsonsForm1 // AutoScaleDimensions = new SizeF(10F, 25F); @@ -159,5 +167,6 @@ private ToolStripMenuItem aboutToolStripMenuItem; private ToolStripMenuItem includedToolStripMenuItem; private ToolStripMenuItem selectROMToolStripMenuItem1; + private ToolStripMenuItem vRAMViewerToolStripMenuItem; } } diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index be26a00..b9859c8 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -14,6 +14,7 @@ namespace Desktop { public SmsMachine _machine = null!; private DebuggerForm _debugger; + private VramViewerForm _vramViewer; private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb); private NAudioPlayer _audioPlayer; private Task _emulatorTask; @@ -296,6 +297,16 @@ namespace Desktop _debugger.Show(); } + private void vramViewerToolStripMenuItem_Click(object sender, EventArgs e) + { + if (_vramViewer == null || _vramViewer.IsDisposed) + { + // Pass the VDP directly to the viewer! + _vramViewer = new VramViewerForm(_machine.VideoProcessor); + } + _vramViewer.Show(); + } + private void includedToolStripMenuItem_Click(object sender, EventArgs e) { diff --git a/Desktop/ROMS/Golden Axe Warrior.sav b/Desktop/ROMS/Golden Axe Warrior.sav new file mode 100644 index 0000000000000000000000000000000000000000..724dc973e202181577c8c9e39966fbb87468d419 GIT binary patch literal 32768 zcmeI%ze^io7{KwzlRy>+FCZOn34scwP94)m)T)C@M7uZ?3`LCw0tE#nw^f|1qb?4% zE|pl!AJBG^c5twvv_ZsBv?zL$U_}x=u+wo|L5rL z^5VePkD1f@d-}7*u@OK30R#|0009ILK;Y&Am;e0d2>}EUKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R(O( Fa0 UpdateVramImage(); + _refreshTimer.Start(); + } + + private void UpdateVramImage() + { + if (_vdp == null) return; + + // Loop through all 512 tiles in VRAM + for (int tile = 0; tile < 512; tile++) + { + // Calculate grid position (32 columns, 16 rows) + int tileX = (tile % 32) * 8; + int tileY = (tile / 32) * 8; + int tileAddress = tile * 32; + + // Decode the 8x8 pixels + for (int y = 0; y < 8; y++) + { + byte bp0 = _vdp.VRAM[tileAddress + (y * 4) + 0]; + byte bp1 = _vdp.VRAM[tileAddress + (y * 4) + 1]; + byte bp2 = _vdp.VRAM[tileAddress + (y * 4) + 2]; + byte bp3 = _vdp.VRAM[tileAddress + (y * 4) + 3]; + + for (int x = 0; x < 8; x++) + { + int shift = 7 - x; + int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) | + (((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3); + + // Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly + byte smsColor = _vdp.CRAM[16 + colorIndex]; + int r = (smsColor & 0x03) * 85; + int g = ((smsColor >> 2) & 0x03) * 85; + int b = ((smsColor >> 4) & 0x03) * 85; + + // Background color (Index 0) is technically transparent for sprites, so we render it as magenta + if (colorIndex == 0) { r = 255; g = 0; b = 255; } + + _pixelData[((tileY + y) * 256) + (tileX + x)] = (255 << 24) | (r << 16) | (g << 8) | b; + } + } + } + + // Blast the array into the Bitmap + var data = _vramBitmap.LockBits(new Rectangle(0, 0, 256, 128), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + Marshal.Copy(_pixelData, 0, data.Scan0, _pixelData.Length); + _vramBitmap.UnlockBits(data); + + this.Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + // Draw it chunky and pixel-perfect + e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; + e.Graphics.DrawImage(_vramBitmap, new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height)); + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + _refreshTimer.Stop(); + base.OnFormClosing(e); + } + } +} \ No newline at end of file