diff --git a/D/Conversion.cs b/D/Conversion.cs index 0019d5d..ef464b7 100644 --- a/D/Conversion.cs +++ b/D/Conversion.cs @@ -45,5 +45,10 @@ public static class Conversion /// Conversion from microseconds to nanoseconds /// public static readonly ulong UsecToNsec = 1000; + + /// + /// Conversion from microseconds to seconds + /// + public static readonly double UsecToSec = 0.000001; } } diff --git a/D/Darkstar.csproj b/D/Darkstar.csproj index 4289c25..1862fab 100644 --- a/D/Darkstar.csproj +++ b/D/Darkstar.csproj @@ -122,7 +122,7 @@ - + True True diff --git a/D/IOP/Beeper.cs b/D/IOP/Beeper.cs new file mode 100644 index 0000000..e47a053 --- /dev/null +++ b/D/IOP/Beeper.cs @@ -0,0 +1,109 @@ +using D.Logging; +using SDL2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace D.IOP +{ + /// + /// Implements the tone generator used to generate simple beeps. + /// This is driven by an i8253 programmable interval timer. + /// + public class Beeper + { + public Beeper() + { + Reset(); + } + + public void Reset() + { + _lsb = 0; + _loadLSB = true; + _frequency = 0.0; + _enabled = false; + _sampleOn = false; + _periodInSamples = 0; + + _sampleBuffer = new byte[0x10000]; + } + + public void LoadPeriod(byte value) + { + if (_loadLSB) + { + _lsb = value; + } + else + { + // "The period (in usec*1.8432) will be in the range ~29..65535." + double period = ((value << 8) | _lsb) / 1.8432; + + // The above is in usec; convert to seconds: + period = period * Conversion.UsecToSec; + + // Invert to get the frequency in Hz: + _frequency = 1.0 / period; + + if (Log.Enabled) Log.Write(LogComponent.Beeper, "Tone frequency set to {0}", _frequency); + + // And find out the length in samples at 44.1Khz. + _periodInSamples = 44100.0 / _frequency; + } + + _loadLSB = !_loadLSB; + } + + public void EnableTone() + { + if (Log.Enabled) Log.Write(LogComponent.Beeper, "Tone enabled.", _frequency); + _enabled = true; + } + + public void DisableTone() + { + if (Log.Enabled && _enabled) Log.Write(LogComponent.Beeper, "Tone disabled.", _frequency); + _enabled = false; + } + + public void AudioCallback(IntPtr userData, IntPtr stream, int length) + { + if (_enabled) + { + for (int i = 0; i < length; i++) + { + _position++; + + if (_position > _periodInSamples) + { + _position -= _periodInSamples; + _sampleOn = !_sampleOn; + } + + _sampleBuffer[i] = (byte)(_enabled ? (_sampleOn ? 0x3f : 0x00) : 0x00); + } + } + else + { + Array.Clear(_sampleBuffer, 0, length); + } + + Marshal.Copy(_sampleBuffer, 0, stream, length); + } + + private byte[] _sampleBuffer; + + private bool _loadLSB; + private byte _lsb; + + private double _frequency; + private bool _enabled; + private double _position; + private double _periodInSamples; + private bool _sampleOn; + } +} diff --git a/D/IOP/IOProcessor.cs b/D/IOP/IOProcessor.cs index 64314ff..adb9cad 100644 --- a/D/IOP/IOProcessor.cs +++ b/D/IOP/IOProcessor.cs @@ -54,7 +54,7 @@ public IOProcessor(DSystem system) _floppyController = new FloppyController(_floppyDrive, _system); _dma = new DMAController(this); _tty = new Printer(); - _tone = new Tone(); + _beeper = new Beeper(); // // Register DMA devices with controller @@ -143,9 +143,9 @@ public Printer Printer get { return _tty; } } - public Tone Tone + public Beeper Beeper { - get { return _tone; } + get { return _beeper; } } private i8085 _cpu; @@ -161,7 +161,7 @@ public Tone Tone private Keyboard _keyboard; private Mouse _mouse; private Printer _tty; - private Tone _tone; + private Beeper _beeper; private DSystem _system; // diff --git a/D/IOP/MiscIO.cs b/D/IOP/MiscIO.cs index dd6c275..a5937b0 100644 --- a/D/IOP/MiscIO.cs +++ b/D/IOP/MiscIO.cs @@ -120,7 +120,7 @@ public void WritePort(int port, byte value) // i8253 Timer channel #1 - used to set the Keyboard bell (tone) frequency. // This is a 16-bit value loaded one byte at a time, LSB first. // Send the word off to the tone generator. - _iop.Tone.LoadInterval(value); + _iop.Beeper.LoadPeriod(value); break; case 0x8f: @@ -180,11 +180,11 @@ public void WritePort(int port, byte value) if ((value & 0x20) != 0) { - _iop.Tone.EnableTone(); + _iop.Beeper.EnableTone(); } else { - _iop.Tone.DisableTone(); + _iop.Beeper.DisableTone(); } if ((value & 0x10) != 0) diff --git a/D/IOP/Tone.cs b/D/IOP/Tone.cs deleted file mode 100644 index 4e1fdd0..0000000 --- a/D/IOP/Tone.cs +++ /dev/null @@ -1,91 +0,0 @@ -using D.Logging; -using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; - -namespace D.IOP -{ - /// - /// Implements the tone generator used to generate simple beeps. - /// This is driven by an i8253 programmable interval timer. - /// - public class Tone - { - public Tone() - { - Reset(); - } - - public void Reset() - { - _lsb = 0; - _loadLSB = true; - _frequency = 0.0; - _enabled = false; - _sampleOn = false; - _periodInSamples = 0; - } - - public void LoadInterval(byte value) - { - if (_loadLSB) - { - _lsb = value; - } - else - { - //;Frequency constant (1843.2/f, f in kHz) - _frequency = ((value << 8) | value) / 1.8432; - if (Log.Enabled) Log.Write(LogComponent.Tone, "Tone frequency set to {0}", _frequency); - _periodInSamples = (44100.0 / _frequency) / 2; - } - - _loadLSB = false; - } - - public void EnableTone() - { - if (Log.Enabled) Log.Write(LogComponent.Tone, "Tone enabled.", _frequency); - _enabled = true; - } - - public void DisableTone() - { - // if (Log.Enabled) Log.Write(LogComponent.Tone, "Tone disabled.", _frequency); - _enabled = false; - } - - public void AudioCallback(IntPtr userData, IntPtr stream, int length) - { - byte[] samples = new byte[length]; - - for (int i = 0; i < length; i++) - { - _position++; - - if (_position > _periodInSamples) - { - _position -= _periodInSamples; - _sampleOn = !_sampleOn; - } - - samples[i] = (byte)(_enabled ? (_sampleOn ? 0xff : 0x00) : 0x00); - } - - // Marshal.Copy(samples, 0, stream, length); - } - - private bool _loadLSB; - private byte _lsb; - - private double _frequency; - private bool _enabled; - private double _position; - private double _periodInSamples; - private bool _sampleOn; - } -} diff --git a/D/Logging/Log.cs b/D/Logging/Log.cs index fc85083..bbde329 100644 --- a/D/Logging/Log.cs +++ b/D/Logging/Log.cs @@ -82,7 +82,7 @@ public enum LogComponent EthernetPacket = 0x10000000, // Keyboard tone - Tone = 0x20000000, + Beeper = 0x20000000, // Configuration Configuration = 0x40000000, @@ -111,9 +111,9 @@ public static class Log { static Log() { - Enabled = true; - _components = LogComponent.Tone | LogComponent.IOPPrinter; - _type = LogType.All; + Enabled = false; + _components = LogComponent.None; + _type = LogType.None; _logIndex = 0; } diff --git a/D/UI/DWindow-IO.cs b/D/UI/DWindow-IO.cs index 09fdffe..9a5a875 100644 --- a/D/UI/DWindow-IO.cs +++ b/D/UI/DWindow-IO.cs @@ -974,6 +974,8 @@ private void InitializeSDL() { int retVal; + SDL.SDL_SetHint("SDL_WINDOWS_DISABLE_THREAD_NAMING", "1"); + // Get SDL humming if ((retVal = SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING)) < 0) { @@ -1030,25 +1032,28 @@ private void InitializeSDL() _renderEvent = new SDL.SDL_Event(); _renderEvent.type = (SDL.SDL_EventType)_renderEventType; - - + // + // Initialize SDL Audio: + // I'd prefer to do this in the Beeper class but there's some undocumented + // dependency on having an active SDL window, so we do it here to keep + // things simple. + // SDL.SDL_AudioSpec desired = new SDL.SDL_AudioSpec(); SDL.SDL_AudioSpec obtained = new SDL.SDL_AudioSpec(); + + _audioCallback = _system.IOP.Beeper.AudioCallback; desired.freq = 44100; desired.format = SDL.AUDIO_U8; desired.channels = 1; - desired.callback = _system.IOP.Tone.AudioCallback; - desired.samples = 1; + desired.callback = _audioCallback; + desired.samples = 1024; uint deviceId = SDL.SDL_OpenAudioDevice(null, 0, ref desired, out obtained, 0); - SDL.SDL_PauseAudioDevice(deviceId, 0); - if (Log.Enabled) Log.Write(LogComponent.Tone, "SDL Audio initialized, device id {0}", deviceId); - - + if (Log.Enabled) Log.Write(LogComponent.Beeper, "SDL Audio initialized, device id {0}", deviceId); } private void CreateDisplayTexture(bool filter) @@ -1206,5 +1211,13 @@ private void ToggleFullScreen(bool fullScreen) // Rendering textures private IntPtr _displayTexture = IntPtr.Zero; private ReaderWriterLockSlim _textureLock; + + // + // Local reference for the SDL Audio callback: + // SDL-CS doesn't hold this reference which causes + // problems when the GC runs. We keep this reference for as long + // as the Display Window is active. + // + private SDL.SDL_AudioCallback _audioCallback; } }