From 780f8ee7904322e7d41b62f19c0dfff72a59e1ca Mon Sep 17 00:00:00 2001 From: parsons Date: Thu, 14 May 2026 22:31:23 +0100 Subject: [PATCH] APU fully implemented and working but approx 0.25s latency to fix --- Desktop/DebuggerForm.Designer.cs | 91 +++++++++++++++++++++++++++----- Desktop/DebuggerForm.cs | 18 ++++++- Desktop/DebuggerForm.resx | 3 ++ Desktop/Desktop.csproj | 1 + Desktop/Form1.Designer.cs | 1 + Desktop/Form1.cs | 15 +++++- Desktop/NAudioPlayer.cs | 49 +++++++++++++++++ 7 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 Desktop/NAudioPlayer.cs diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs index ecf17d3..47266da 100644 --- a/Desktop/DebuggerForm.Designer.cs +++ b/Desktop/DebuggerForm.Designer.cs @@ -58,6 +58,12 @@ richTextBox1 = new RichTextBox(); button1 = new Button(); CpuRun = new Button(); + groupBox1 = new GroupBox(); + lblTone0 = new Label(); + lblNoise = new Label(); + lblTone2 = new Label(); + lblTone1 = new Label(); + groupBox1.SuspendLayout(); SuspendLayout(); // // lblAF @@ -164,10 +170,10 @@ // // txtMemoryView // - txtMemoryView.Location = new Point(110, 100); + txtMemoryView.Location = new Point(110, 101); txtMemoryView.Margin = new Padding(2); txtMemoryView.Name = "txtMemoryView"; - txtMemoryView.Size = new Size(553, 1118); + txtMemoryView.Size = new Size(553, 543); txtMemoryView.TabIndex = 15; txtMemoryView.Text = "Memory View Window"; // @@ -242,7 +248,7 @@ // lblIff1 // lblIff1.AutoSize = true; - lblIff1.Location = new Point(709, 514); + lblIff1.Location = new Point(11, 533); lblIff1.Margin = new Padding(4, 0, 4, 0); lblIff1.Name = "lblIff1"; lblIff1.Size = new Size(45, 25); @@ -252,7 +258,7 @@ // lblIff2 // lblIff2.AutoSize = true; - lblIff2.Location = new Point(709, 572); + lblIff2.Location = new Point(11, 586); lblIff2.Margin = new Padding(4, 0, 4, 0); lblIff2.Name = "lblIff2"; lblIff2.Size = new Size(45, 25); @@ -262,12 +268,12 @@ // lblIE // lblIE.AutoSize = true; - lblIE.Location = new Point(710, 462); + lblIE.Location = new Point(11, 486); lblIE.Margin = new Padding(4, 0, 4, 0); lblIE.Name = "lblIE"; - lblIE.Size = new Size(133, 25); + lblIE.Size = new Size(33, 25); lblIE.TabIndex = 26; - lblIE.Text = "Interrupt Mode"; + lblIE.Text = "IM"; // // btnReset // @@ -289,7 +295,7 @@ // lblFrames // lblFrames.AutoSize = true; - lblFrames.Location = new Point(709, 852); + lblFrames.Location = new Point(14, 660); lblFrames.Margin = new Padding(2, 0, 2, 0); lblFrames.Name = "lblFrames"; lblFrames.Size = new Size(149, 25); @@ -299,7 +305,7 @@ // lblFPS // lblFPS.AutoSize = true; - lblFPS.Location = new Point(824, 949); + lblFPS.Location = new Point(129, 757); lblFPS.Margin = new Padding(2, 0, 2, 0); lblFPS.Name = "lblFPS"; lblFPS.Size = new Size(41, 25); @@ -309,7 +315,7 @@ // lblFrameTime // lblFrameTime.AutoSize = true; - lblFrameTime.Location = new Point(755, 898); + lblFrameTime.Location = new Point(60, 706); lblFrameTime.Margin = new Padding(2, 0, 2, 0); lblFrameTime.Name = "lblFrameTime"; lblFrameTime.Size = new Size(104, 25); @@ -319,7 +325,7 @@ // richTextBox1 // richTextBox1.Enabled = false; - richTextBox1.Location = new Point(709, 636); + richTextBox1.Location = new Point(682, 474); richTextBox1.Margin = new Padding(4); richTextBox1.Name = "richTextBox1"; richTextBox1.ReadOnly = true; @@ -329,7 +335,7 @@ // // button1 // - button1.Location = new Point(694, 1030); + button1.Location = new Point(1075, 756); button1.Margin = new Padding(4); button1.Name = "button1"; button1.Size = new Size(118, 36); @@ -340,7 +346,7 @@ // // CpuRun // - CpuRun.Location = new Point(694, 1104); + CpuRun.Location = new Point(943, 757); CpuRun.Margin = new Padding(4); CpuRun.Name = "CpuRun"; CpuRun.Size = new Size(118, 36); @@ -349,11 +355,61 @@ CpuRun.UseVisualStyleBackColor = true; CpuRun.Click += CpuRun_Click; // + // groupBox1 + // + groupBox1.Controls.Add(lblTone1); + groupBox1.Controls.Add(lblTone2); + groupBox1.Controls.Add(lblNoise); + groupBox1.Controls.Add(lblTone0); + groupBox1.Location = new Point(266, 658); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new Size(397, 222); + groupBox1.TabIndex = 35; + groupBox1.TabStop = false; + groupBox1.Text = "Audio (SN76489)"; + // + // lblTone0 + // + lblTone0.AutoSize = true; + lblTone0.Location = new Point(25, 48); + lblTone0.Name = "lblTone0"; + lblTone0.Size = new Size(59, 25); + lblTone0.TabIndex = 0; + lblTone0.Text = "Tone0"; + // + // lblNoise + // + lblNoise.AutoSize = true; + lblNoise.Location = new Point(25, 160); + lblNoise.Name = "lblNoise"; + lblNoise.Size = new Size(57, 25); + lblNoise.TabIndex = 1; + lblNoise.Text = "Noise"; + // + // lblTone2 + // + lblTone2.AutoSize = true; + lblTone2.Location = new Point(25, 122); + lblTone2.Name = "lblTone2"; + lblTone2.Size = new Size(59, 25); + lblTone2.TabIndex = 2; + lblTone2.Text = "Tone2"; + // + // lblTone1 + // + lblTone1.AutoSize = true; + lblTone1.Location = new Point(25, 83); + lblTone1.Name = "lblTone1"; + lblTone1.Size = new Size(59, 25); + lblTone1.TabIndex = 3; + lblTone1.Text = "Tone1"; + // // DebuggerForm // AutoScaleDimensions = new SizeF(10F, 25F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1206, 1238); + ClientSize = new Size(1206, 892); + Controls.Add(groupBox1); Controls.Add(CpuRun); Controls.Add(button1); Controls.Add(richTextBox1); @@ -385,6 +441,8 @@ Margin = new Padding(2); Name = "DebuggerForm"; Text = "Debugger"; + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); ResumeLayout(false); PerformLayout(); } @@ -420,6 +478,11 @@ private Button button1; private Button CpuRun; public System.Windows.Forms.Timer uiUpdateTimer; + private GroupBox groupBox1; + private Label lblTone1; + private Label lblTone2; + private Label lblNoise; + private Label lblTone0; //private TextBox textBox4; } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index e7b9123..2e934d7 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -1,6 +1,8 @@ using System; +using System.Reflection.PortableExecutable; using System.Text; using System.Windows.Forms; +using Core.Audio; using Core.Cpu; using Core.Memory; @@ -100,7 +102,7 @@ namespace Desktop lblIY.Text = $"IY: {_cpu.IY.Word:X4}"; lblIff1.Text = $"IFF1: {_cpu.IFF1}"; lblIff2.Text = $"IFF2: {_cpu.IFF2}"; - lblIE.Text = $"Interrupt Mode: {_cpu.InterruptMode}"; + lblIE.Text = $"IM: {_cpu.InterruptMode}"; lblFlags.Text = $"Flags: {_cpu.GetFlagsString()}"; lblTStates.Text = $"T-States: {_cpu.TotalTStates}"; lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}"; @@ -109,11 +111,23 @@ namespace Desktop UpdateMemoryView(); UpdateStackView(); UpdateDisassemblyView(); + // --- AUDIO REGISTERS --- + // Use _mainForm to access the Machine, and then grab the AudioProcessor! + if (_mainForm._machine != null && _mainForm._machine.AudioProcessor != null) + { + ushort[] apuRegs = _mainForm._machine.AudioProcessor.Registers; + + lblTone0.Text = $"Tone 0: 0x{apuRegs[0]:X4} (Vol: 0x{apuRegs[1]:X1})"; + lblTone1.Text = $"Tone 1: 0x{apuRegs[2]:X4} (Vol: 0x{apuRegs[3]:X1})"; + lblTone2.Text = $"Tone 2: 0x{apuRegs[4]:X4} (Vol: 0x{apuRegs[5]:X1})"; + + lblNoise.Text = $"Noise : 0x{apuRegs[6]:X1} (Vol: 0x{apuRegs[7]:X1})"; + } } private void UpdateMemoryView() { - int count = 40; + int count = 20; // Try to parse the hex string the user typed in if (!ushort.TryParse(txtMemoryStart.Text, System.Globalization.NumberStyles.HexNumber, null, out ushort startAddress)) { diff --git a/Desktop/DebuggerForm.resx b/Desktop/DebuggerForm.resx index 7ac8c1e..cd73f8b 100644 --- a/Desktop/DebuggerForm.resx +++ b/Desktop/DebuggerForm.resx @@ -120,4 +120,7 @@ 17, 17 + + 47 + \ No newline at end of file diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj index 999caf6..9b6c28d 100644 --- a/Desktop/Desktop.csproj +++ b/Desktop/Desktop.csproj @@ -343,6 +343,7 @@ + diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index 9e9b85d..fa9b97c 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -139,6 +139,7 @@ Margin = new Padding(4); Name = "ParsonsForm1"; Text = "Form1"; + FormClosing += ParsonsForm1_FormClosing; menuStrip1.ResumeLayout(false); menuStrip1.PerformLayout(); ResumeLayout(false); diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 36b15ba..d6923f1 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -6,14 +6,16 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; using Core; +using Desktop.Audio; namespace Desktop { public partial class ParsonsForm1 : Form { - private SmsMachine _machine = null!; + public SmsMachine _machine = null!; private DebuggerForm _debugger; private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb); + private NAudioPlayer _audioPlayer; private Task _emulatorTask; private double TargetFrameTime = 16.667f; public int TotalFrameCount = 0; @@ -34,6 +36,8 @@ namespace Desktop InitializeComponent(); this.Text = $"Parsons Master System 2026 - {_currentRomName}"; _machine = new SmsMachine(); + _audioPlayer = new NAudioPlayer(); + _machine.AudioProcessor.AudioDevice = _audioPlayer; PopulateIncludedRomsMenu(); @@ -157,7 +161,8 @@ namespace Desktop framesThisSecond = 0; lastFpsUpdate = currentTime; - BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { + BeginInvoke((System.Windows.Forms.MethodInvoker)delegate + { this.Text = $"Parsons Master System - {_currentRomName} [FPS/FT: {FramesPerSecond:F0}/{FrameTime:F1}]"; }); } @@ -299,5 +304,11 @@ namespace Desktop _machine.IoBus.Joypad1Keyboard |= bitMask; // Target Keyboard! } } + + private void ParsonsForm1_FormClosing(object sender, FormClosingEventArgs e) + { + IsRunning = false; + _audioPlayer?.Stop(); + } } } diff --git a/Desktop/NAudioPlayer.cs b/Desktop/NAudioPlayer.cs new file mode 100644 index 0000000..aaf4c2e --- /dev/null +++ b/Desktop/NAudioPlayer.cs @@ -0,0 +1,49 @@ +using System; +using Core.Interfaces; +using Microsoft.VisualBasic; +using NAudio.Wave; + +namespace Desktop.Audio // Change this to match your project's namespace +{ + public class NAudioPlayer : IAudioDevice + { + private WaveOutEvent _waveOut; + private BufferedWaveProvider _buffer; + + public NAudioPlayer() + { + // Set up the audio format: 44100 Hz, 32-bit IEEE float, 1 channel (Mono) + WaveFormat format = WaveFormat.CreateIeeeFloatWaveFormat(44100, 1); + + _buffer = new BufferedWaveProvider(format); + + // CRITICAL FOR EMULATORS: + // If the emulator runs slightly too fast, the audio buffer will fill up + // and the sound will lag behind the gameplay. Discarding overflow keeps it synced! + _buffer.DiscardOnBufferOverflow = true; + + _waveOut = new WaveOutEvent(); + + // 100ms latency is a great sweet spot for WinForms emulators. + // Too low = crackling audio. Too high = delayed sound effects. + _waveOut.DesiredLatency = 100; + _waveOut.Init(_buffer); + _waveOut.Play(); + } + + public void AddSample(float sample) + { + // Convert the float (-1.0f to 1.0f) into 4 raw bytes + byte[] sampleBytes = BitConverter.GetBytes(sample); + + // Push the 4 bytes to the sound card buffer! + _buffer.AddSamples(sampleBytes, 0, 4); + } + + public void Stop() + { + _waveOut?.Stop(); + _waveOut?.Dispose(); + } + } +} \ No newline at end of file