diff --git a/Core/Io/IO_Bus.cs b/Core/Io/IO_Bus.cs index 1361e84..4ccd033 100644 --- a/Core/Io/IO_Bus.cs +++ b/Core/Io/IO_Bus.cs @@ -1,13 +1,13 @@ -using System.Diagnostics; -using Core.Interfaces; +using Core.Interfaces; +using System.Diagnostics; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Core.Io { public class IO_Bus { - public byte BorderColorIndex { get; private set; } = 7; // 7 is White - - // 8 rows representing the Spectrum keyboard matrix. Default to 0xFF (unpressed). + public byte BorderColorIndex { get; private set; } = 7; + public bool BeeperState { get; private set; } = false; public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; public byte ReadPort(ushort portAddress) @@ -42,11 +42,13 @@ namespace Core.Io // The ULA intercepts any write to an even port address if ((portAddress & 0x01) == 0) { - // The bottom 3 bits (0-2) define the border color! + // The bottom 3 bits (0-2) define the border color BorderColorIndex = (byte)(portValue & 0x07); - // (Bits 3 and 4 handle the cassette MIC output and the internal speaker, - // which we will need when we start playing audio!) + // Bit 4 controls the speaker + BeeperState = (portValue & 0x10) != 0; + + // Bit 3 handles the cassette MIC output } } } diff --git a/Desktop/BeeperDevice.cs b/Desktop/BeeperDevice.cs new file mode 100644 index 0000000..fe54642 --- /dev/null +++ b/Desktop/BeeperDevice.cs @@ -0,0 +1,41 @@ +using NAudio.Wave; +using System; + +namespace Desktop +{ + public class BeeperDevice + { + private WaveOutEvent _waveOut; + private BufferedWaveProvider _buffer; + + public BeeperDevice() + { + _waveOut = new WaveOutEvent(); + _waveOut.DesiredLatency = 50; // 100ms latency to prevent buffer stutter + + // 44.1kHz, 1 channel (Mono), Float format + _buffer = new BufferedWaveProvider(WaveFormat.CreateIeeeFloatWaveFormat(44100, 1)); + _buffer.BufferDuration = TimeSpan.FromSeconds(1); + _buffer.DiscardOnBufferOverflow = true; + + _waveOut.Init(_buffer); + _waveOut.Play(); + } + + public void AddSample(bool isHigh) + { + //Buffer overrun check and dump + if (_buffer.BufferedDuration.TotalMilliseconds > 100) + { + _buffer.ClearBuffer(); + } + + // Convert the boolean into a physical sound wave (-0.2 or +0.2) + float sampleValue = isHigh ? 0.2f : -0.2f; + + // Convert the float to bytes and drop it in the pipe + byte[] bytes = BitConverter.GetBytes(sampleValue); + _buffer.AddSamples(bytes, 0, 4); + } + } +} \ No newline at end of file diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj index ca6f524..132ab54 100644 --- a/Desktop/Desktop.csproj +++ b/Desktop/Desktop.csproj @@ -8,6 +8,10 @@ enable + + + + diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 2393f0e..dfbf00f 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -17,6 +17,7 @@ namespace Desktop private ULA _ula = null!; private TapManager _tapManager = null!; private DebuggerForm? _debugger = null; + private BeeperDevice _beeper = null!; private string _baseTitle = ""; private bool _isRunning = false; private bool _isPaused = false; @@ -41,6 +42,7 @@ namespace Desktop _memoryBus = new MemoryBus(); _simpleIoBus = new IO_Bus(); _ula = new ULA(_memoryBus, _simpleIoBus); + _beeper = new BeeperDevice(); _tapManager = new TapManager(); _memoryBus.CrapRAMData(); byte[] romData = RomLoader.Load("48.rom"); @@ -70,6 +72,7 @@ namespace Desktop var stopwatch = Stopwatch.StartNew(); var fpsStopwatch = Stopwatch.StartNew(); long scanlineCount = 0; + long audioSampleCount = 0; while (_isRunning) { @@ -90,6 +93,13 @@ namespace Desktop // --- Execute Instruction --- _cpu.Step(); + //Process audio at the correct time + while (_cpu.TotalTStates >= (long)(audioSampleCount * 79.365)) + { + _beeper.AddSample(_simpleIoBus.BeeperState); + audioSampleCount++; + } + // --- Check for End of Frame --- if (_cpu.TotalTStates >= nextScanlineTarget) {