From 8382195feb30bc46f990bd4d144b8692bb5de628 Mon Sep 17 00:00:00 2001 From: Sean Fausett Date: Sun, 26 Jul 2009 23:22:00 +0000 Subject: [PATCH] Initial sound emulation for WPF and XNA+Windows platforms via COM interop to DirectSound. Added rudimentary UI to Silverlight and WPF platforms to enable disk selection at runtime. --HG-- extra : convert_revision : svn%3Affd33b8c-2492-42e0-bdc5-587b920b7d6d/trunk%4024459 --- Library/DirectSound.cs | 189 ++++++++++++++++++ Library/DirectSoundInterop.cs | 150 ++++++++++++++ Library/FileHelpers.cs | 19 -- Library/GCHandleHelpers.cs | 27 +++ Library/GeneralSecurity.cs | 22 +- Library/Lazy.cs | 2 +- Library/MarshalHelpers.cs | 31 +++ Library/Silverlight/ApplicationBase.cs | 5 - Library/Silverlight/FrameRateCounter.xaml.cs | 2 +- .../Jellyfish.Library.Silverlight.csproj | 3 - Library/Silverlight/StringFormatConverter.cs | 2 +- Library/Wpf/ApplicationBase.cs | 7 +- Library/Wpf/FrameRateCounter.xaml.cs | 2 +- Library/Wpf/Jellyfish.Library.Wpf.csproj | 14 +- Library/Wpf/WindowExtensions.cs | 14 ++ Library/Xna/FrameRateCounter.cs | 6 +- Library/Xna/GameBase.cs | 5 - Library/Xna/Jellyfish.Library.Xna.Xbox.csproj | 3 - Library/Xna/Jellyfish.Library.Xna.Zune.csproj | 3 - Library/Xna/Jellyfish.Library.Xna.csproj | 13 +- Virtu/Cpu.cs | 3 +- Virtu/CpuData.cs | 2 +- Virtu/CustomDictionary.xml | 1 + Virtu/DiskDsk.cs | 34 ++-- Virtu/DiskII.cs | 20 +- Virtu/Drive525.cs | 14 +- Virtu/GamePort.cs | 1 - Virtu/Keyboard.cs | 1 - Virtu/Machine.cs | 65 +++++- Virtu/MachineEvents.cs | 24 ++- Virtu/MachineSettings.cs | 24 +-- Virtu/Properties/SR.Designer.cs | 2 +- Virtu/Roms/AppleIIe.rom | Bin 0 -> 16384 bytes Virtu/Roms/DiskII.rom | Bin 0 -> 256 bytes Virtu/Services/AudioService.cs | 81 +++++++- Virtu/Services/GamePortService.cs | 5 +- Virtu/Services/KeyboardService.cs | 13 +- Virtu/Services/MachineService.cs | 29 +++ Virtu/Services/MachineServices.cs | 6 +- Virtu/Services/StorageService.cs | 10 +- Virtu/Services/VideoService.cs | 11 +- .../Jellyfish.Virtu.Silverlight.csproj | 5 + Virtu/Silverlight/MainApp.xaml.cs | 2 +- Virtu/Silverlight/MainPage.xaml | 19 +- Virtu/Silverlight/MainPage.xaml.cs | 52 +++-- .../Services/SilverlightAudioService.cs | 19 ++ .../Services/SilverlightKeyboardService.cs | 24 +-- .../Services/SilverlightStorageService.cs | 37 +--- .../Services/SilverlightVideoService.cs | 13 +- Virtu/Speaker.cs | 2 +- Virtu/Video.cs | 1 - Virtu/Wpf/Jellyfish.Virtu.Wpf.csproj | 9 + Virtu/Wpf/MainApp.xaml.cs | 6 +- Virtu/Wpf/MainWindow.xaml | 15 +- Virtu/Wpf/MainWindow.xaml.cs | 52 +++-- Virtu/Wpf/Services/WpfAudioService.cs | 41 ++++ Virtu/Wpf/Services/WpfKeyboardService.cs | 10 +- Virtu/Wpf/Services/WpfStorageService.cs | 19 +- Virtu/Wpf/Services/WpfVideoService.cs | 14 +- Virtu/Xna/Jellyfish.Virtu.Xna.Xbox.csproj | 4 + Virtu/Xna/Jellyfish.Virtu.Xna.csproj | 4 + Virtu/Xna/MainApp.cs | 2 - Virtu/Xna/MainGame.cs | 34 +++- Virtu/Xna/Services/XnaAudioService.cs | 42 ++++ Virtu/Xna/Services/XnaGamePortService.cs | 8 +- Virtu/Xna/Services/XnaKeyboardService.cs | 18 +- Virtu/Xna/Services/XnaStorageService.cs | 11 +- Virtu/Xna/Services/XnaVideoService.cs | 21 +- 68 files changed, 1010 insertions(+), 339 deletions(-) create mode 100644 Library/DirectSound.cs create mode 100644 Library/DirectSoundInterop.cs delete mode 100644 Library/FileHelpers.cs create mode 100644 Library/GCHandleHelpers.cs create mode 100644 Library/MarshalHelpers.cs create mode 100644 Library/Wpf/WindowExtensions.cs create mode 100644 Virtu/Roms/AppleIIe.rom create mode 100644 Virtu/Roms/DiskII.rom create mode 100644 Virtu/Services/MachineService.cs create mode 100644 Virtu/Silverlight/Services/SilverlightAudioService.cs create mode 100644 Virtu/Wpf/Services/WpfAudioService.cs create mode 100644 Virtu/Xna/Services/XnaAudioService.cs diff --git a/Library/DirectSound.cs b/Library/DirectSound.cs new file mode 100644 index 0000000..50f378c --- /dev/null +++ b/Library/DirectSound.cs @@ -0,0 +1,189 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Jellyfish.Library +{ + public sealed class DirectSoundUpdateEventArgs : EventArgs + { + private DirectSoundUpdateEventArgs() + { + } + + public static DirectSoundUpdateEventArgs Create(IntPtr buffer, int bufferSize) + { + _instance.Buffer = buffer; + _instance.BufferSize = bufferSize; + + return _instance; // use singleton; avoids garbage + } + + public IntPtr Buffer { get; private set; } + public int BufferSize { get; private set; } + + private static readonly DirectSoundUpdateEventArgs _instance = new DirectSoundUpdateEventArgs(); + } + + public sealed partial class DirectSound : IDisposable + { + public DirectSound(int sampleRate, int sampleChannels, int sampleBits, int sampleSize) + { + _sampleRate = sampleRate; + _sampleChannels = sampleChannels; + _sampleBits = sampleBits; + _sampleSize = sampleSize; + + _thread = new Thread(Run) { Name = "DirectSound" }; + } + + public void Dispose() + { + _position1Event.Close(); + _position2Event.Close(); + _stopEvent.Close(); + } + + public void Start(IntPtr window) + { + _window = window; + _thread.Start(); + } + + public void Stop() + { + _stopEvent.Set(); + _thread.Join(); + } + + [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")] + private void Initialize() + { + int hresult = NativeMethods.DirectSoundCreate(IntPtr.Zero, out _device, IntPtr.Zero); + if (hresult < 0) + { + Marshal.ThrowExceptionForHR(hresult); + } + + _device.SetCooperativeLevel(_window, CooperativeLevel.Normal); + + GCHandleHelpers.Pin(new WaveFormat(_sampleRate, _sampleChannels, _sampleBits), waveFormat => + { + BufferDescription description = new BufferDescription(BufferCapabilities.CtrlPositionNotify, BlockCount * _sampleSize, waveFormat); + _device.CreateSoundBuffer(description, out _buffer, IntPtr.Zero); + }); + ClearBuffer(); + + BufferPositionNotify[] positionEvents = new BufferPositionNotify[BlockCount] + { + new BufferPositionNotify(0 * _sampleSize, _position1Event.SafeWaitHandle.DangerousGetHandle()), + new BufferPositionNotify(1 * _sampleSize, _position2Event.SafeWaitHandle.DangerousGetHandle()) + }; + ((IDirectSoundNotify)_buffer).SetNotificationPositions(positionEvents.Length, positionEvents); + + _buffer.Play(0, 0, BufferPlay.Looping); + } + + private void ClearBuffer() + { + UpdateBuffer(0, 0, BufferLock.EntireBuffer, (buffer, bufferSize) => + { + MarshalHelpers.ZeroMemory(buffer, bufferSize); + }); + } + + private void RestoreBuffer() + { + BufferStatus status; + _buffer.GetStatus(out status); + if ((status & BufferStatus.BufferLost) != 0) + { + _buffer.Restore(); + } + } + + private void UpdateBuffer(int block) + { + EventHandler handler = Update; + if (handler != null) + { + UpdateBuffer(block * _sampleSize, _sampleSize, BufferLock.None, (buffer, bufferSize) => + { + handler(this, DirectSoundUpdateEventArgs.Create(buffer, bufferSize)); + }); + } + } + + private void UpdateBuffer(int offset, int count, BufferLock flags, Action updateBuffer) + { + RestoreBuffer(); + + IntPtr buffer1, buffer2; + int buffer1Size, buffer2Size; + _buffer.Lock(offset, count, out buffer1, out buffer1Size, out buffer2, out buffer2Size, flags); + try + { + if (buffer1 != IntPtr.Zero) + { + updateBuffer(buffer1, buffer1Size); + } + if (buffer2 != IntPtr.Zero) + { + updateBuffer(buffer2, buffer2Size); + } + } + finally + { + _buffer.Unlock(buffer1, buffer1Size, buffer2, buffer2Size); + } + } + + private void Uninitialize() + { + if (_buffer != null) + { + _buffer.Stop(); + Marshal.ReleaseComObject(_buffer); + } + if (_device != null) + { + Marshal.ReleaseComObject(_device); + } + } + + private void Run() // com mta thread + { + Initialize(); + + EventWaitHandle[] eventHandles = new EventWaitHandle[] { _position1Event, _position2Event, _stopEvent }; + int index = WaitHandle.WaitAny(eventHandles); + + while (index < BlockCount) + { + UpdateBuffer((index + 1) % BlockCount); // update next block in circular buffer + index = WaitHandle.WaitAny(eventHandles); + } + + Uninitialize(); + } + + public event EventHandler Update; + + private const int BlockCount = 2; + private const int WaveFormatPcm = 1; + + private int _sampleRate; + private int _sampleChannels; + private int _sampleBits; + private int _sampleSize; + + private Thread _thread; + private IntPtr _window; + private IDirectSound _device; + private IDirectSoundBuffer _buffer; + + private AutoResetEvent _position1Event = new AutoResetEvent(false); + private AutoResetEvent _position2Event = new AutoResetEvent(false); + private ManualResetEvent _stopEvent = new ManualResetEvent(false); + } +} diff --git a/Library/DirectSoundInterop.cs b/Library/DirectSoundInterop.cs new file mode 100644 index 0000000..8391208 --- /dev/null +++ b/Library/DirectSoundInterop.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Jellyfish.Library +{ + public sealed partial class DirectSound + { + [Flags] + private enum BufferCapabilities + { + PrimaryBuffer = 0x00000001, + CtrlPositionNotify = 0x00000100, + StickyFocus = 0x00004000, + GlobalFocus = 0x00008000 + } + + [Flags] + private enum BufferLock + { + None = 0x00000000, + FromWriteCursor = 0x00000001, + EntireBuffer = 0x00000002 + } + + [Flags] + private enum BufferPlay + { + Looping = 0x00000001 + } + + [Flags] + private enum BufferStatus + { + Playing = 0x00000001, + BufferLost = 0x00000002, + Looping = 0x00000004, + Terminated = 0x00000020 + } + + private enum CooperativeLevel + { + Normal = 1, + Priority = 2 + } + + [StructLayout(LayoutKind.Sequential)] + private sealed class BufferDescription + { + public BufferDescription(BufferCapabilities capabilities, int size, IntPtr format) + { + dwSize = Marshal.SizeOf(typeof(BufferDescription)); + dwFlags = capabilities; + dwBufferBytes = size; + lpwfxFormat = format; + } + + public int dwSize; + public BufferCapabilities dwFlags; + public int dwBufferBytes; + public int dwReserved; + public IntPtr lpwfxFormat; + public Guid guid3DAlgorithm; + } + + [StructLayout(LayoutKind.Sequential)] + private struct BufferPositionNotify + { + public BufferPositionNotify(int offset, IntPtr notifyEvent) + { + dwOffset = offset; + hEventNotify = notifyEvent; + } + + public int dwOffset; + public IntPtr hEventNotify; + } + + [StructLayout(LayoutKind.Sequential)] + private sealed class WaveFormat + { + public WaveFormat(int sampleRate, int sampleChannels, int sampleBits) + { + wFormatTag = WaveFormatPcm; + nSamplesPerSec = sampleRate; + nChannels = (short)sampleChannels; + wBitsPerSample = (short)sampleBits; + nBlockAlign = (short)(sampleChannels * sampleBits / 8); + nAvgBytesPerSec = sampleRate * nBlockAlign; + } + + public short wFormatTag; + public short nChannels; + public int nSamplesPerSec; + public int nAvgBytesPerSec; + public short nBlockAlign; + public short wBitsPerSample; + public short cbSize; + } + + [ComImport, Guid("279AFA83-4981-11CE-A521-0020AF0BE560"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IDirectSound + { + void CreateSoundBuffer(BufferDescription pcDSBufferDesc, [MarshalAs(UnmanagedType.Interface)] out IDirectSoundBuffer pDSBuffer, IntPtr pUnkOuter); + void GetCaps(IntPtr pDSCaps); + void DuplicateSoundBuffer([MarshalAs(UnmanagedType.Interface)] IDirectSoundBuffer pDSBufferOriginal, [MarshalAs(UnmanagedType.Interface)] out IDirectSoundBuffer pDSBufferDuplicate); + void SetCooperativeLevel(IntPtr hwnd, CooperativeLevel dwLevel); + void Compact(); + void GetSpeakerConfig(out int dwSpeakerConfig); + void SetSpeakerConfig(int dwSpeakerConfig); + void Initialize([MarshalAs(UnmanagedType.LPStruct)] Guid pcGuidDevice); + } + + [ComImport, Guid("279AFA85-4981-11CE-A521-0020AF0BE560"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IDirectSoundBuffer + { + void GetCaps(IntPtr pDSBufferCaps); + void GetCurrentPosition(out int dwCurrentPlayCursor, out int dwCurrentWriteCursor); + void GetFormat(IntPtr pwfxFormat, int dwSizeAllocated, out int dwSizeWritten); + void GetVolume(out int lVolume); + void GetPan(out int lPan); + void GetFrequency(out int dwFrequency); + void GetStatus(out BufferStatus dwStatus); + void Initialize([MarshalAs(UnmanagedType.Interface)] IDirectSound pDirectSound, BufferDescription pcDSBufferDesc); + void Lock(int dwOffset, int dwBytes, out IntPtr pvAudioPtr1, out int dwAudioBytes1, out IntPtr pvAudioPtr2, out int dwAudioBytes2, BufferLock dwFlags); + void Play(int dwReserved1, int dwPriority, BufferPlay dwFlags); + void SetCurrentPosition(int dwNewPosition); + void SetFormat(WaveFormat pcfxFormat); + void SetVolume(int lVolume); + void SetPan(int lPan); + void SetFrequency(int dwFrequency); + void Stop(); + void Unlock(IntPtr pvAudioPtr1, int dwAudioBytes1, IntPtr pvAudioPtr2, int dwAudioBytes2); + void Restore(); + } + + [ComImport, Guid("B0210783-89CD-11D0-AF08-00A0C925CD16"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IDirectSoundNotify + { + void SetNotificationPositions(int dwPositionNotifies, [MarshalAs(UnmanagedType.LPArray)] BufferPositionNotify[] pcPositionNotifies); + } + + [SuppressUnmanagedCodeSecurity] + private static class NativeMethods + { + [DllImport("dsound.dll")] + public static extern int DirectSoundCreate(IntPtr pcGuidDevice, [MarshalAs(UnmanagedType.Interface)] out IDirectSound pDS, IntPtr pUnkOuter); + } + } +} diff --git a/Library/FileHelpers.cs b/Library/FileHelpers.cs deleted file mode 100644 index 758d3c1..0000000 --- a/Library/FileHelpers.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.IO; - -namespace Jellyfish.Library -{ - public static class FileHelpers - { - public static byte[] ReadAllBytes(string path) - { -#if SILVERLIGHT || XBOX || ZUNE - using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - return stream.ReadAllBytes(); - } -#else - return File.ReadAllBytes(path); -#endif - } - } -} diff --git a/Library/GCHandleHelpers.cs b/Library/GCHandleHelpers.cs new file mode 100644 index 0000000..91d3993 --- /dev/null +++ b/Library/GCHandleHelpers.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; +using System.Security.Permissions; + +namespace Jellyfish.Library +{ + public static class GCHandleHelpers + { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public static void Pin(object value, Action action) + { + GCHandle gcHandle = new GCHandle(); + try + { + gcHandle = GCHandle.Alloc(value, GCHandleType.Pinned); + action(gcHandle.AddrOfPinnedObject()); + } + finally + { + if (gcHandle.IsAllocated) + { + gcHandle.Free(); + } + } + } + } +} diff --git a/Library/GeneralSecurity.cs b/Library/GeneralSecurity.cs index e53b52c..603c189 100644 --- a/Library/GeneralSecurity.cs +++ b/Library/GeneralSecurity.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Security.AccessControl; +using System.Security.Permissions; using System.Security.Principal; namespace Jellyfish.Library @@ -9,6 +10,7 @@ namespace Jellyfish.Library [StructLayout(LayoutKind.Sequential)] public sealed class SecurityAttributes { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public SecurityAttributes() { _length = Marshal.SizeOf(typeof(SecurityAttributes)); @@ -116,31 +118,21 @@ namespace Jellyfish.Library base.AddAuditRule(rule); } + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public void GetSecurityAttributes(bool inheritable, Action action) { GetSecurityAttributes(this, inheritable, action); } + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public static void GetSecurityAttributes(ObjectSecurity security, bool inheritable, Action action) { if (security != null) { - GCHandle gcHandle = new GCHandle(); - try + GCHandleHelpers.Pin(security.GetSecurityDescriptorBinaryForm(), securityDescriptor => { - gcHandle = GCHandle.Alloc(security.GetSecurityDescriptorBinaryForm(), GCHandleType.Pinned); - SecurityAttributes securityAttributes = new SecurityAttributes(); - securityAttributes.SecurityDescriptor = gcHandle.AddrOfPinnedObject(); - securityAttributes.InheritHandle = inheritable; - action(securityAttributes); - } - finally - { - if (gcHandle.IsAllocated) - { - gcHandle.Free(); - } - } + action(new SecurityAttributes() { SecurityDescriptor = securityDescriptor, InheritHandle = inheritable }); + }); } else if (inheritable) { diff --git a/Library/Lazy.cs b/Library/Lazy.cs index 6440d90..492e875 100644 --- a/Library/Lazy.cs +++ b/Library/Lazy.cs @@ -3,7 +3,7 @@ using System.Threading; namespace Jellyfish.Library { - public class Lazy where T : class + public sealed class Lazy where T : class { public Lazy(Func initializer) { diff --git a/Library/MarshalHelpers.cs b/Library/MarshalHelpers.cs new file mode 100644 index 0000000..5f03978 --- /dev/null +++ b/Library/MarshalHelpers.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; + +namespace Jellyfish.Library +{ + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public static class MarshalHelpers + { + public static void FillMemory(IntPtr buffer, int bufferSize, byte value) + { + NativeMethods.FillMemory(buffer, (IntPtr)bufferSize, value); + } + + public static void ZeroMemory(IntPtr buffer, int bufferSize) + { + NativeMethods.ZeroMemory(buffer, (IntPtr)bufferSize); + } + + [SuppressUnmanagedCodeSecurity] + private static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + public static extern void FillMemory(IntPtr destination, IntPtr length, byte fill); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern void ZeroMemory(IntPtr destination, IntPtr length); + } + } +} diff --git a/Library/Silverlight/ApplicationBase.cs b/Library/Silverlight/ApplicationBase.cs index a98c75d..f090b83 100644 --- a/Library/Silverlight/ApplicationBase.cs +++ b/Library/Silverlight/ApplicationBase.cs @@ -6,11 +6,6 @@ namespace Jellyfish.Library { public class ApplicationBase : Application { - public ApplicationBase() : - this(null) - { - } - public ApplicationBase(string name) { Name = name; diff --git a/Library/Silverlight/FrameRateCounter.xaml.cs b/Library/Silverlight/FrameRateCounter.xaml.cs index 5ac6961..bbc650c 100644 --- a/Library/Silverlight/FrameRateCounter.xaml.cs +++ b/Library/Silverlight/FrameRateCounter.xaml.cs @@ -5,7 +5,7 @@ using System.Windows.Media; namespace Jellyfish.Library { - public partial class FrameRateCounter : UserControl + public sealed partial class FrameRateCounter : UserControl { public FrameRateCounter() { diff --git a/Library/Silverlight/Jellyfish.Library.Silverlight.csproj b/Library/Silverlight/Jellyfish.Library.Silverlight.csproj index 2517581..393aa64 100644 --- a/Library/Silverlight/Jellyfish.Library.Silverlight.csproj +++ b/Library/Silverlight/Jellyfish.Library.Silverlight.csproj @@ -54,9 +54,6 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs - GlobalSuppressions.cs diff --git a/Library/Silverlight/StringFormatConverter.cs b/Library/Silverlight/StringFormatConverter.cs index 7c3b096..ecb203a 100644 --- a/Library/Silverlight/StringFormatConverter.cs +++ b/Library/Silverlight/StringFormatConverter.cs @@ -5,7 +5,7 @@ using System.Windows.Data; namespace Jellyfish.Library { - public class StringFormatConverter : IValueConverter // SL is missing Binding.StringFormat + public sealed class StringFormatConverter : IValueConverter // SL is missing Binding.StringFormat { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/Library/Wpf/ApplicationBase.cs b/Library/Wpf/ApplicationBase.cs index 2284e7d..e3ddfb5 100644 --- a/Library/Wpf/ApplicationBase.cs +++ b/Library/Wpf/ApplicationBase.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Security.Permissions; using System.Text; using System.Windows; using System.Windows.Threading; @@ -8,11 +9,7 @@ namespace Jellyfish.Library { public class ApplicationBase : Application { - public ApplicationBase() : - this(null) - { - } - + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public ApplicationBase(string name) { Name = name; diff --git a/Library/Wpf/FrameRateCounter.xaml.cs b/Library/Wpf/FrameRateCounter.xaml.cs index 5ac6961..bbc650c 100644 --- a/Library/Wpf/FrameRateCounter.xaml.cs +++ b/Library/Wpf/FrameRateCounter.xaml.cs @@ -5,7 +5,7 @@ using System.Windows.Media; namespace Jellyfish.Library { - public partial class FrameRateCounter : UserControl + public sealed partial class FrameRateCounter : UserControl { public FrameRateCounter() { diff --git a/Library/Wpf/Jellyfish.Library.Wpf.csproj b/Library/Wpf/Jellyfish.Library.Wpf.csproj index 2b4cc4b..fd486a2 100644 --- a/Library/Wpf/Jellyfish.Library.Wpf.csproj +++ b/Library/Wpf/Jellyfish.Library.Wpf.csproj @@ -62,8 +62,14 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs + + DirectSound.cs + + + DirectSoundInterop.cs + + + GCHandleHelpers.cs GeneralSecurity.cs @@ -77,6 +83,9 @@ Lazy.cs + + MarshalHelpers.cs + MathHelpers.cs @@ -100,6 +109,7 @@ FrameRateCounter.xaml + diff --git a/Library/Wpf/WindowExtensions.cs b/Library/Wpf/WindowExtensions.cs new file mode 100644 index 0000000..34b0fec --- /dev/null +++ b/Library/Wpf/WindowExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Windows; +using System.Windows.Interop; + +namespace Jellyfish.Library +{ + public static class WindowExtensions + { + public static IntPtr GetHandle(this Window window) + { + return new WindowInteropHelper(window).Handle; + } + } +} diff --git a/Library/Xna/FrameRateCounter.cs b/Library/Xna/FrameRateCounter.cs index aae716f..f288dac 100644 --- a/Library/Xna/FrameRateCounter.cs +++ b/Library/Xna/FrameRateCounter.cs @@ -5,13 +5,11 @@ using Microsoft.Xna.Framework.Graphics; namespace Jellyfish.Library { - public class FrameRateCounter : DrawableGameComponent + public sealed class FrameRateCounter : DrawableGameComponent { public FrameRateCounter(GameBase game) : base(game) { - _frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage - FontColor = Color.White; FontName = "Default"; @@ -68,6 +66,6 @@ namespace Jellyfish.Library private long _elapsedTime; private int _frameCount; private int _frameRate; - private StringBuilder _frameRateBuilder; + private StringBuilder _frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage } } diff --git a/Library/Xna/GameBase.cs b/Library/Xna/GameBase.cs index 068d760..7a57708 100644 --- a/Library/Xna/GameBase.cs +++ b/Library/Xna/GameBase.cs @@ -7,11 +7,6 @@ namespace Jellyfish.Library { public class GameBase : Game { - public GameBase() : - this(null) - { - } - public GameBase(string name) { Name = name; diff --git a/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj b/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj index a0752ef..28dba83 100644 --- a/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj +++ b/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj @@ -62,9 +62,6 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs - GlobalSuppressions.cs diff --git a/Library/Xna/Jellyfish.Library.Xna.Zune.csproj b/Library/Xna/Jellyfish.Library.Xna.Zune.csproj index 490ce35..8dc5d3e 100644 --- a/Library/Xna/Jellyfish.Library.Xna.Zune.csproj +++ b/Library/Xna/Jellyfish.Library.Xna.Zune.csproj @@ -60,9 +60,6 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs - GlobalSuppressions.cs diff --git a/Library/Xna/Jellyfish.Library.Xna.csproj b/Library/Xna/Jellyfish.Library.Xna.csproj index b2cc6e8..f7acfb6 100644 --- a/Library/Xna/Jellyfish.Library.Xna.csproj +++ b/Library/Xna/Jellyfish.Library.Xna.csproj @@ -84,8 +84,14 @@ AssemblyCommentAttribute.cs - - FileHelpers.cs + + DirectSound.cs + + + DirectSoundInterop.cs + + + GCHandleHelpers.cs GeneralSecurity.cs @@ -99,6 +105,9 @@ Lazy.cs + + MarshalHelpers.cs + MathHelpers.cs diff --git a/Virtu/Cpu.cs b/Virtu/Cpu.cs index 29f0b36..e834c97 100644 --- a/Virtu/Cpu.cs +++ b/Virtu/Cpu.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Threading; -using Jellyfish.Virtu.Properties; namespace Jellyfish.Virtu { @@ -187,6 +186,7 @@ namespace Jellyfish.Virtu Opcode = _memory.Read(RPC); RPC = (RPC + 1) & 0xFFFF; _executeOpcode[Opcode](); + Cycles += CC; // System.Diagnostics.Debug.WriteLine(" " + ToString()); @@ -3243,6 +3243,7 @@ namespace Jellyfish.Virtu public int EA { get; private set; } public int CC { get; private set; } public int Opcode { get; private set; } + public long Cycles { get; private set; } private Action _updateEvent; diff --git a/Virtu/CpuData.cs b/Virtu/CpuData.cs index e227cb4..ef70640 100644 --- a/Virtu/CpuData.cs +++ b/Virtu/CpuData.cs @@ -7,7 +7,7 @@ namespace Jellyfish.Virtu private const int CyclesPerUpdate = 17030; private const int CyclesPerVSync = 17030; private const int CyclesPerSecond = 1022730; - private static readonly long TicksPerVSync = TimeSpan.FromSeconds((double)CyclesPerVSync / (double)CyclesPerSecond).Ticks; + private static readonly long TicksPerVSync = TimeSpan.FromSeconds((double)CyclesPerVSync / CyclesPerSecond).Ticks; private const int OpcodeCount = 256; diff --git a/Virtu/CustomDictionary.xml b/Virtu/CustomDictionary.xml index 1baed9f..928be94 100644 --- a/Virtu/CustomDictionary.xml +++ b/Virtu/CustomDictionary.xml @@ -11,6 +11,7 @@ Annunciator Dsk Opcode + Unpause Virtu Xna x diff --git a/Virtu/DiskDsk.cs b/Virtu/DiskDsk.cs index 27b157e..7ced3b0 100644 --- a/Virtu/DiskDsk.cs +++ b/Virtu/DiskDsk.cs @@ -266,25 +266,25 @@ namespace Jellyfish.Virtu private static readonly byte[] NibbleToByte = new byte[] { // padding for offset (not used) - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, // nibble translate table - 0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06, - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - 0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E, - 0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, - 0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F + 0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E, + 0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F }; } } diff --git a/Virtu/DiskII.cs b/Virtu/DiskII.cs index 9d3fcb3..c6c4a31 100644 --- a/Virtu/DiskII.cs +++ b/Virtu/DiskII.cs @@ -1,25 +1,19 @@ using System.Diagnostics.CodeAnalysis; -using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Settings; namespace Jellyfish.Virtu { public sealed class DiskII : MachineComponent { - public DiskII(Machine machine) : + public DiskII(Machine machine) : base(machine) { } public override void Initialize() { - _storageService = Machine.Services.GetService(); - +#if WINDOWS DiskIISettings settings = Machine.Settings.DiskII; - if (settings.Disk1.Name.Length == 0) - { - settings.Disk1.Name = _storageService.GetDiskFile(); - } if (settings.Disk1.Name.Length > 0) { _drives[0].InsertDisk(settings.Disk1.Name, settings.Disk1.IsWriteProtected); @@ -28,6 +22,7 @@ namespace Jellyfish.Virtu { _drives[1].InsertDisk(settings.Disk2.Name, settings.Disk2.IsWriteProtected); } +#endif } public override void Reset() @@ -42,10 +37,6 @@ namespace Jellyfish.Virtu public override void Uninitialize() { Flush(); - - DiskIISettings settings = Machine.Settings.DiskII; // TODO remove; reset filename to prompt on next start - settings.Disk1.Name = string.Empty; - settings.Disk2.Name = string.Empty; } [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] @@ -227,13 +218,14 @@ namespace Jellyfish.Virtu } } + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] + public Drive525[] Drives { get { return _drives; } } + private const int Phase0On = 1 << 0; private const int Phase1On = 1 << 1; private const int Phase2On = 1 << 2; private const int Phase3On = 1 << 3; - private StorageService _storageService; - private Drive525[] _drives = new Drive525[] { new Drive525(), new Drive525() }; private int _latch; private int _phaseStates; diff --git a/Virtu/Drive525.cs b/Virtu/Drive525.cs index d07142a..e036ec9 100644 --- a/Virtu/Drive525.cs +++ b/Virtu/Drive525.cs @@ -1,9 +1,10 @@ using System; +using System.IO; using Jellyfish.Library; namespace Jellyfish.Virtu { - public class Drive525 + public sealed class Drive525 { public Drive525() { @@ -14,13 +15,20 @@ namespace Jellyfish.Virtu } public void InsertDisk(string fileName, bool isWriteProtected) + { + using (FileStream stream = File.OpenRead(fileName)) + { + InsertDisk(fileName, stream, isWriteProtected); + } + } + + public void InsertDisk(string name, Stream stream, bool isWriteProtected) { FlushTrack(); // TODO handle null param/empty string for eject, or add Eject() - byte[] fileData = FileHelpers.ReadAllBytes(fileName); - _disk = Disk525.CreateDisk(fileName, fileData, isWriteProtected); + _disk = Disk525.CreateDisk(name, stream.ReadAllBytes(), isWriteProtected); _trackLoaded = false; } diff --git a/Virtu/GamePort.cs b/Virtu/GamePort.cs index 5712b63..1b009cc 100644 --- a/Virtu/GamePort.cs +++ b/Virtu/GamePort.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using Jellyfish.Library; -using Jellyfish.Virtu.Properties; using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Settings; diff --git a/Virtu/Keyboard.cs b/Virtu/Keyboard.cs index 8a697f2..51be1e9 100644 --- a/Virtu/Keyboard.cs +++ b/Virtu/Keyboard.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using Jellyfish.Virtu.Properties; using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Settings; diff --git a/Virtu/Machine.cs b/Virtu/Machine.cs index 6327050..5a99f7f 100644 --- a/Virtu/Machine.cs +++ b/Virtu/Machine.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System; +using System.Collections.ObjectModel; using System.Threading; using Jellyfish.Library; using Jellyfish.Virtu.Services; @@ -6,11 +7,12 @@ using Jellyfish.Virtu.Settings; namespace Jellyfish.Virtu { - public class Machine + public enum MachineState { Stopped = 0, Starting, Running, Pausing, Paused, Stopping } + + public sealed class Machine : IDisposable { public Machine() { - Thread = new Thread(Run) { Name = "Machine" }; Events = new MachineEvents(); Services = new MachineServices(); Settings = new MachineSettings(); @@ -23,8 +25,15 @@ namespace Jellyfish.Virtu Cassette = new Cassette(this); Speaker = new Speaker(this); Video = new Video(this); - Components = new Collection { Cpu, Memory, DiskII, Keyboard, GamePort, Cassette, Speaker, Video }; + + Thread = new Thread(Run) { Name = "Machine" }; + } + + public void Dispose() + { + _pausedEvent.Close(); + _unpauseEvent.Close(); } public void Reset() @@ -36,35 +45,64 @@ namespace Jellyfish.Virtu { _storageService = Services.GetService(); _storageService.Load(MachineSettings.FileName, stream => Settings.Deserialize(stream)); + + State = MachineState.Starting; Thread.Start(); } + public void Pause() + { + State = MachineState.Pausing; + _pausedEvent.WaitOne(); + State = MachineState.Paused; + } + + public void Unpause() + { + State = MachineState.Running; + _unpauseEvent.Set(); + } + public void Stop() { - _stopPending = true; + State = MachineState.Stopping; + _unpauseEvent.Set(); Thread.Join(); + State = MachineState.Stopped; + _storageService.Save(MachineSettings.FileName, stream => Settings.Serialize(stream)); } - private void Run() + private void Run() // machine thread { Components.ForEach(component => component.Initialize()); Reset(); + State = MachineState.Running; do { - Events.RaiseEvents(Cpu.Execute()); + do + { + Events.RaiseEvents(Cpu.Execute()); + } + while (State == MachineState.Running); + + if (State == MachineState.Pausing) + { + _pausedEvent.Set(); + _unpauseEvent.WaitOne(); + } } - while (!_stopPending); + while (State != MachineState.Stopping); Components.ForEach(component => component.Uninitialize()); } - public Thread Thread { get; private set; } public MachineEvents Events { get; private set; } public MachineServices Services { get; private set; } public MachineSettings Settings { get; private set; } - public Collection Components { get; private set; } + public MachineState State { get; private set; } + public Cpu Cpu { get; private set; } public Memory Memory { get; private set; } public DiskII DiskII { get; private set; } @@ -73,8 +111,13 @@ namespace Jellyfish.Virtu public Cassette Cassette { get; private set; } public Speaker Speaker { get; private set; } public Video Video { get; private set; } + public Collection Components { get; private set; } + + public Thread Thread { get; private set; } + + private AutoResetEvent _pausedEvent = new AutoResetEvent(false); + private AutoResetEvent _unpauseEvent = new AutoResetEvent(false); private StorageService _storageService; - private bool _stopPending; } } diff --git a/Virtu/MachineEvents.cs b/Virtu/MachineEvents.cs index 1a6d283..388d9c7 100644 --- a/Virtu/MachineEvents.cs +++ b/Virtu/MachineEvents.cs @@ -5,7 +5,7 @@ using System.Globalization; namespace Jellyfish.Virtu { - public class MachineEvent + public sealed class MachineEvent { public MachineEvent(int delta, Action action) { @@ -22,7 +22,7 @@ namespace Jellyfish.Virtu public Action Action { get; set; } } - public class MachineEvents + public sealed class MachineEvents { public void AddEvent(int delta, Action action) { @@ -75,7 +75,7 @@ namespace Jellyfish.Virtu } } - return delta; + return 0; } [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] @@ -86,17 +86,23 @@ namespace Jellyfish.Virtu while (node.Value.Delta <= 0) { - delta = node.Value.Delta; node.Value.Action(); - - _used.Remove(node); - _free.AddFirst(node); // cache node; avoids garbage - + RemoveEvent(node); node = _used.First; - node.Value.Delta += delta; } } + private void RemoveEvent(LinkedListNode node) + { + if (node.Next != null) + { + node.Next.Value.Delta += node.Value.Delta; + } + + _used.Remove(node); + _free.AddFirst(node); // cache node; avoids garbage + } + private LinkedList _used = new LinkedList(); private LinkedList _free = new LinkedList(); } diff --git a/Virtu/MachineSettings.cs b/Virtu/MachineSettings.cs index 351d228..100d2ce 100644 --- a/Virtu/MachineSettings.cs +++ b/Virtu/MachineSettings.cs @@ -6,7 +6,7 @@ using System.Xml.Linq; namespace Jellyfish.Virtu.Settings { - public class MachineSettings + public sealed class MachineSettings { public MachineSettings() { @@ -21,8 +21,8 @@ namespace Jellyfish.Virtu.Settings UseGamePort = false, Key = new KeySettings { - Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', DownRight = 0 }, - Joystick1 = new JoystickSettings { UpLeft = 0, Up = 'I', UpRight = 0, Left = 'J', Right = 'L', DownLeft = 0, Down = 'K', DownRight = 0 }, + Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'I', UpRight = 0, Left = 'J', Right = 'L', DownLeft = 0, Down = 'K', DownRight = 0 }, + Joystick1 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', DownRight = 0 }, Button0 = 0, Button1 = 0, Button2 = 0 } }; @@ -305,25 +305,25 @@ namespace Jellyfish.Virtu.Settings public const string Namespace = "http://schemas.jellyfish.co.nz/virtu/settings"; } - public class CpuSettings + public sealed class CpuSettings { public bool Is65C02 { get; set; } public bool IsThrottled { get; set; } } - public class DiskSettings + public sealed class DiskSettings { public string Name { get; set; } public bool IsWriteProtected { get; set; } }; - public class DiskIISettings + public sealed class DiskIISettings { public DiskSettings Disk1 { get; set; } public DiskSettings Disk2 { get; set; } }; - public class JoystickSettings + public sealed class JoystickSettings { public int UpLeft { get; set; } public int Up { get; set; } @@ -335,7 +335,7 @@ namespace Jellyfish.Virtu.Settings public int DownRight { get; set; } }; - public class KeySettings + public sealed class KeySettings { public JoystickSettings Joystick0 { get; set; } public JoystickSettings Joystick1 { get; set; } @@ -344,19 +344,19 @@ namespace Jellyfish.Virtu.Settings public int Button2 { get; set; } }; - public class KeyboardSettings + public sealed class KeyboardSettings { public bool UseGamePort { get; set; } public KeySettings Key { get; set; } } - public class GamePortSettings + public sealed class GamePortSettings { public bool UseKeyboard { get; set; } public KeySettings Key { get; set; } } - public class ColorSettings + public sealed class ColorSettings { public uint Black { get; set; } public uint DarkBlue { get; set; } @@ -377,7 +377,7 @@ namespace Jellyfish.Virtu.Settings public uint Monochrome { get; set; } } - public class VideoSettings + public sealed class VideoSettings { public bool IsFullScreen { get; set; } public bool IsMonochrome { get; set; } diff --git a/Virtu/Properties/SR.Designer.cs b/Virtu/Properties/SR.Designer.cs index c1702e6..13a2298 100644 --- a/Virtu/Properties/SR.Designer.cs +++ b/Virtu/Properties/SR.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:2.0.50727.3074 +// Runtime Version:2.0.50727.4016 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. diff --git a/Virtu/Roms/AppleIIe.rom b/Virtu/Roms/AppleIIe.rom new file mode 100644 index 0000000000000000000000000000000000000000..65bb648567f66b37874b1a417f564b296280c5f7 GIT binary patch literal 16384 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 I|Nid(1v1KeNdN!< literal 0 HcmV?d00001 diff --git a/Virtu/Roms/DiskII.rom b/Virtu/Roms/DiskII.rom new file mode 100644 index 0000000000000000000000000000000000000000..56698b0f8879c56af152fc0cbff4230ba53592b0 GIT binary patch literal 256 zcmZ3auz+C^bDK>Umx|2?0ahE&|C)6hIJ_?Wo-sF!`NRu>rwS4OckN|hn4!tc#R?Q}Jg_oMVa1;gf!D2St+uU>D>+)#B{+a;1%7*8 zz3?5(IJxS==T{Kcv=5vb6YhJi{h)f`<^twc2e7DCs|}b|wduI updateBuffer) // audio thread + { + lock (_lock) + { + long cycles = Machine.Cpu.Cycles; + int updateDelta = (int)(cycles - _updateCycles); + _updateCycles = _toggleCycles = cycles; // reset audio frame + + if (updateDelta > 0) + { + double bytesPerCycle = (double)bufferSize / (Machine.Settings.Cpu.IsThrottled ? CyclesPerSample : updateDelta); + + while (_readIndex != _writeIndex) + { + int deltaSize = (int)(_deltaBuffer[_readIndex] * bytesPerCycle); + if (deltaSize > bufferSize) + { + _deltaBuffer[_readIndex] -= (int)((double)bufferSize / bytesPerCycle); + break; + } + + updateBuffer(_isOutputHigh ? SampleHigh : SampleZero, deltaSize); + _isOutputHigh ^= true; + + bufferSize -= deltaSize; + _readIndex = (_readIndex + 1) % DeltaBufferSize; + } + + updateBuffer(_isOutputHigh ? SampleHigh : SampleZero, bufferSize); + } + else + { + updateBuffer(SampleZero, bufferSize); + } + } + } + + public const int SampleRate = 44100; // hz + public const int SampleChannels = 1; + public const int SampleBits = 8; + public const int SampleSize = (int)(SampleRate * Latency / 1000f) * SampleChannels * SampleBits / 8; + + private const int CyclesPerSecond = 1022730; + private const int CyclesPerSample = (int)(CyclesPerSecond * Latency / 1000f); + + private const int Latency = 40; // ms + + private static readonly byte[] SampleHigh = Enumerable.Repeat((byte)0xFF, SampleSize).ToArray(); + private static readonly byte[] SampleZero = new byte[SampleSize]; + + private const int DeltaBufferSize = 8192; + + private int[] _deltaBuffer = new int[DeltaBufferSize]; + private uint _readIndex; + private uint _writeIndex; + private bool _isOutputHigh; + private long _toggleCycles; + private long _updateCycles; + private object _lock = new object(); } } diff --git a/Virtu/Services/GamePortService.cs b/Virtu/Services/GamePortService.cs index 189165f..08b34f4 100644 --- a/Virtu/Services/GamePortService.cs +++ b/Virtu/Services/GamePortService.cs @@ -48,9 +48,10 @@ private bool _isDown; } - public class GamePortService + public class GamePortService : MachineService { - public GamePortService() + public GamePortService(Machine machine) : + base(machine) { Paddle0 = Paddle1 = Paddle2 = Paddle3 = 255; // not connected } diff --git a/Virtu/Services/KeyboardService.cs b/Virtu/Services/KeyboardService.cs index a39e3b8..f124e56 100644 --- a/Virtu/Services/KeyboardService.cs +++ b/Virtu/Services/KeyboardService.cs @@ -4,7 +4,7 @@ using System.Threading; namespace Jellyfish.Virtu.Services { - public class AsciiKeyEventArgs : EventArgs + public sealed class AsciiKeyEventArgs : EventArgs { private AsciiKeyEventArgs() { @@ -22,8 +22,13 @@ namespace Jellyfish.Virtu.Services private static readonly AsciiKeyEventArgs _instance = new AsciiKeyEventArgs(); } - public abstract class KeyboardService + public abstract class KeyboardService : MachineService { + protected KeyboardService(Machine machine) : + base(machine) + { + } + public abstract bool IsKeyDown(int key); [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")] @@ -42,7 +47,7 @@ namespace Jellyfish.Virtu.Services { while (IsAnyKeyDown) { - Thread.Sleep(100); + Thread.Sleep(10); } } @@ -50,7 +55,7 @@ namespace Jellyfish.Virtu.Services { while (IsResetKeyDown) { - Thread.Sleep(100); + Thread.Sleep(10); } } diff --git a/Virtu/Services/MachineService.cs b/Virtu/Services/MachineService.cs new file mode 100644 index 0000000..fe74e02 --- /dev/null +++ b/Virtu/Services/MachineService.cs @@ -0,0 +1,29 @@ +using System; + +namespace Jellyfish.Virtu.Services +{ + public abstract class MachineService : IDisposable + { + protected MachineService(Machine machine) + { + Machine = machine; + } + + ~MachineService() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + + protected Machine Machine { get; private set; } + } +} diff --git a/Virtu/Services/MachineServices.cs b/Virtu/Services/MachineServices.cs index b243205..ac8bd9c 100644 --- a/Virtu/Services/MachineServices.cs +++ b/Virtu/Services/MachineServices.cs @@ -5,9 +5,9 @@ using Jellyfish.Virtu.Properties; namespace Jellyfish.Virtu.Services { - public class MachineServices : IServiceProvider + public sealed class MachineServices : IServiceProvider { - public void AddService(Type serviceType, object serviceProvider) + public void AddService(Type serviceType, MachineService serviceProvider) { if (_serviceProviders.ContainsKey(serviceType)) { @@ -37,6 +37,6 @@ namespace Jellyfish.Virtu.Services _serviceProviders.Remove(serviceType); } - private Dictionary _serviceProviders = new Dictionary(); + private Dictionary _serviceProviders = new Dictionary(); } } diff --git a/Virtu/Services/StorageService.cs b/Virtu/Services/StorageService.cs index cc4d0af..667ae53 100644 --- a/Virtu/Services/StorageService.cs +++ b/Virtu/Services/StorageService.cs @@ -1,13 +1,15 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.IO; namespace Jellyfish.Virtu.Services { - public abstract class StorageService + public abstract class StorageService : MachineService { - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - public abstract string GetDiskFile(); + protected StorageService(Machine machine) : + base(machine) + { + } + public abstract void Load(string path, Action reader); public abstract void Save(string path, Action writer); } diff --git a/Virtu/Services/VideoService.cs b/Virtu/Services/VideoService.cs index 06b9bcd..3e47ac1 100644 --- a/Virtu/Services/VideoService.cs +++ b/Virtu/Services/VideoService.cs @@ -1,9 +1,12 @@ -using System; - -namespace Jellyfish.Virtu.Services +namespace Jellyfish.Virtu.Services { - public abstract class VideoService + public abstract class VideoService : MachineService { + protected VideoService(Machine machine) : + base(machine) + { + } + public abstract void SetPixel(int x, int y, uint color); public abstract void Update(); diff --git a/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj b/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj index 0717b64..f3e2a6c 100644 --- a/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj +++ b/Virtu/Silverlight/Jellyfish.Virtu.Silverlight.csproj @@ -62,6 +62,7 @@ + @@ -130,6 +131,9 @@ Services\KeyboardService.cs + + Services\MachineService.cs + Services\MachineServices.cs @@ -155,6 +159,7 @@ MainPage.xaml + diff --git a/Virtu/Silverlight/MainApp.xaml.cs b/Virtu/Silverlight/MainApp.xaml.cs index 91b7615..1cd9c9d 100644 --- a/Virtu/Silverlight/MainApp.xaml.cs +++ b/Virtu/Silverlight/MainApp.xaml.cs @@ -2,7 +2,7 @@ namespace Jellyfish.Virtu { - public partial class MainApp : ApplicationBase + public sealed partial class MainApp : ApplicationBase { public MainApp() : base("Virtu") diff --git a/Virtu/Silverlight/MainPage.xaml b/Virtu/Silverlight/MainPage.xaml index d9c33d8..2aa6080 100644 --- a/Virtu/Silverlight/MainPage.xaml +++ b/Virtu/Silverlight/MainPage.xaml @@ -1,11 +1,18 @@  - - - - - + + +