mirror of
https://github.com/digital-jellyfish/Virtu.git
synced 2024-11-23 03:33:44 +00:00
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
This commit is contained in:
parent
7c2048f0c3
commit
8382195feb
189
Library/DirectSound.cs
Normal file
189
Library/DirectSound.cs
Normal file
@ -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<DirectSoundUpdateEventArgs> 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<IntPtr, int> 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<DirectSoundUpdateEventArgs> 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);
|
||||
}
|
||||
}
|
150
Library/DirectSoundInterop.cs
Normal file
150
Library/DirectSoundInterop.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
27
Library/GCHandleHelpers.cs
Normal file
27
Library/GCHandleHelpers.cs
Normal file
@ -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<IntPtr> action)
|
||||
{
|
||||
GCHandle gcHandle = new GCHandle();
|
||||
try
|
||||
{
|
||||
gcHandle = GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
action(gcHandle.AddrOfPinnedObject());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (gcHandle.IsAllocated)
|
||||
{
|
||||
gcHandle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 @@ public void AddAuditRule(GeneralAuditRule rule)
|
||||
base.AddAuditRule(rule);
|
||||
}
|
||||
|
||||
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||
public void GetSecurityAttributes(bool inheritable, Action<SecurityAttributes> action)
|
||||
{
|
||||
GetSecurityAttributes(this, inheritable, action);
|
||||
}
|
||||
|
||||
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||
public static void GetSecurityAttributes(ObjectSecurity security, bool inheritable, Action<SecurityAttributes> 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)
|
||||
{
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public class Lazy<T> where T : class
|
||||
public sealed class Lazy<T> where T : class
|
||||
{
|
||||
public Lazy(Func<T> initializer)
|
||||
{
|
||||
|
31
Library/MarshalHelpers.cs
Normal file
31
Library/MarshalHelpers.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,11 +6,6 @@ namespace Jellyfish.Library
|
||||
{
|
||||
public class ApplicationBase : Application
|
||||
{
|
||||
public ApplicationBase() :
|
||||
this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public ApplicationBase(string name)
|
||||
{
|
||||
Name = name;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public partial class FrameRateCounter : UserControl
|
||||
public sealed partial class FrameRateCounter : UserControl
|
||||
{
|
||||
public FrameRateCounter()
|
||||
{
|
||||
|
@ -54,9 +54,6 @@
|
||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||
<Link>AssemblyCommentAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\FileHelpers.cs">
|
||||
<Link>FileHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GlobalSuppressions.cs">
|
||||
<Link>GlobalSuppressions.cs</Link>
|
||||
</Compile>
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public partial class FrameRateCounter : UserControl
|
||||
public sealed partial class FrameRateCounter : UserControl
|
||||
{
|
||||
public FrameRateCounter()
|
||||
{
|
||||
|
@ -62,8 +62,14 @@
|
||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||
<Link>AssemblyCommentAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\FileHelpers.cs">
|
||||
<Link>FileHelpers.cs</Link>
|
||||
<Compile Include="..\DirectSound.cs">
|
||||
<Link>DirectSound.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\DirectSoundInterop.cs">
|
||||
<Link>DirectSoundInterop.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GCHandleHelpers.cs">
|
||||
<Link>GCHandleHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GeneralSecurity.cs">
|
||||
<Link>GeneralSecurity.cs</Link>
|
||||
@ -77,6 +83,9 @@
|
||||
<Compile Include="..\Lazy.cs">
|
||||
<Link>Lazy.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MarshalHelpers.cs">
|
||||
<Link>MarshalHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MathHelpers.cs">
|
||||
<Link>MathHelpers.cs</Link>
|
||||
</Compile>
|
||||
@ -100,6 +109,7 @@
|
||||
<DependentUpon>FrameRateCounter.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WindowExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="FrameRateCounter.xaml">
|
||||
|
14
Library/Wpf/WindowExtensions.cs
Normal file
14
Library/Wpf/WindowExtensions.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,13 +5,11 @@
|
||||
|
||||
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 @@ public override void Update(GameTime gameTime)
|
||||
private long _elapsedTime;
|
||||
private int _frameCount;
|
||||
private int _frameRate;
|
||||
private StringBuilder _frameRateBuilder;
|
||||
private StringBuilder _frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,6 @@ namespace Jellyfish.Library
|
||||
{
|
||||
public class GameBase : Game
|
||||
{
|
||||
public GameBase() :
|
||||
this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public GameBase(string name)
|
||||
{
|
||||
Name = name;
|
||||
|
@ -62,9 +62,6 @@
|
||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||
<Link>AssemblyCommentAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\FileHelpers.cs">
|
||||
<Link>FileHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GlobalSuppressions.cs">
|
||||
<Link>GlobalSuppressions.cs</Link>
|
||||
</Compile>
|
||||
|
@ -60,9 +60,6 @@
|
||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||
<Link>AssemblyCommentAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\FileHelpers.cs">
|
||||
<Link>FileHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GlobalSuppressions.cs">
|
||||
<Link>GlobalSuppressions.cs</Link>
|
||||
</Compile>
|
||||
|
@ -84,8 +84,14 @@
|
||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||
<Link>AssemblyCommentAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\FileHelpers.cs">
|
||||
<Link>FileHelpers.cs</Link>
|
||||
<Compile Include="..\DirectSound.cs">
|
||||
<Link>DirectSound.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\DirectSoundInterop.cs">
|
||||
<Link>DirectSoundInterop.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GCHandleHelpers.cs">
|
||||
<Link>GCHandleHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\GeneralSecurity.cs">
|
||||
<Link>GeneralSecurity.cs</Link>
|
||||
@ -99,6 +105,9 @@
|
||||
<Compile Include="..\Lazy.cs">
|
||||
<Link>Lazy.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MarshalHelpers.cs">
|
||||
<Link>MarshalHelpers.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MathHelpers.cs">
|
||||
<Link>MathHelpers.cs</Link>
|
||||
</Compile>
|
||||
|
@ -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 @@ public int Execute()
|
||||
Opcode = _memory.Read(RPC);
|
||||
RPC = (RPC + 1) & 0xFFFF;
|
||||
_executeOpcode[Opcode]();
|
||||
Cycles += CC;
|
||||
|
||||
// System.Diagnostics.Debug.WriteLine(" " + ToString());
|
||||
|
||||
@ -3243,6 +3243,7 @@ private void UpdateSettings()
|
||||
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;
|
||||
|
||||
|
@ -7,7 +7,7 @@ public partial class Cpu
|
||||
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;
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
<Word>Annunciator</Word>
|
||||
<Word>Dsk</Word>
|
||||
<Word>Opcode</Word>
|
||||
<Word>Unpause</Word>
|
||||
<Word>Virtu</Word>
|
||||
<Word>Xna</Word>
|
||||
<Word>x</Word>
|
||||
|
@ -266,25 +266,25 @@ private void WriteDataNibbles(int sectorOffset)
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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<StorageService>();
|
||||
|
||||
#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 @@ public override void Initialize()
|
||||
{
|
||||
_drives[1].InsertDisk(settings.Disk2.Name, settings.Disk2.IsWriteProtected);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
@ -42,10 +37,6 @@ public override void Reset()
|
||||
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 @@ private void SetPhase(int address)
|
||||
}
|
||||
}
|
||||
|
||||
[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;
|
||||
|
@ -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 @@ public Drive525()
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Jellyfish.Virtu.Properties;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using Jellyfish.Virtu.Settings;
|
||||
|
||||
|
@ -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 @@
|
||||
|
||||
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 @@ public Machine()
|
||||
Cassette = new Cassette(this);
|
||||
Speaker = new Speaker(this);
|
||||
Video = new Video(this);
|
||||
|
||||
Components = new Collection<MachineComponent> { 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 @@ public void Start()
|
||||
{
|
||||
_storageService = Services.GetService<StorageService>();
|
||||
_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<MachineComponent> 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 @@ private void Run()
|
||||
public Cassette Cassette { get; private set; }
|
||||
public Speaker Speaker { get; private set; }
|
||||
public Video Video { get; private set; }
|
||||
public Collection<MachineComponent> 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;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public class MachineEvent
|
||||
public sealed class MachineEvent
|
||||
{
|
||||
public MachineEvent(int delta, Action action)
|
||||
{
|
||||
@ -22,7 +22,7 @@ public override string ToString()
|
||||
public Action Action { get; set; }
|
||||
}
|
||||
|
||||
public class MachineEvents
|
||||
public sealed class MachineEvents
|
||||
{
|
||||
public void AddEvent(int delta, Action action)
|
||||
{
|
||||
@ -75,7 +75,7 @@ public int FindEvent(Action action)
|
||||
}
|
||||
}
|
||||
|
||||
return delta;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
|
||||
@ -86,17 +86,23 @@ public void RaiseEvents(int delta)
|
||||
|
||||
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<MachineEvent> node)
|
||||
{
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Value.Delta += node.Value.Delta;
|
||||
}
|
||||
|
||||
_used.Remove(node);
|
||||
_free.AddFirst(node); // cache node; avoids garbage
|
||||
}
|
||||
|
||||
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
|
||||
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
namespace Jellyfish.Virtu.Settings
|
||||
{
|
||||
public class MachineSettings
|
||||
public sealed class MachineSettings
|
||||
{
|
||||
public MachineSettings()
|
||||
{
|
||||
@ -21,8 +21,8 @@ public MachineSettings()
|
||||
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 @@ public void Serialize(Stream stream)
|
||||
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 @@ public class JoystickSettings
|
||||
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 @@ public class KeySettings
|
||||
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 @@ public class ColorSettings
|
||||
public uint Monochrome { get; set; }
|
||||
}
|
||||
|
||||
public class VideoSettings
|
||||
public sealed class VideoSettings
|
||||
{
|
||||
public bool IsFullScreen { get; set; }
|
||||
public bool IsMonochrome { get; set; }
|
||||
|
2
Virtu/Properties/SR.Designer.cs
generated
2
Virtu/Properties/SR.Designer.cs
generated
@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// 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.
|
||||
|
BIN
Virtu/Roms/AppleIIe.rom
Normal file
BIN
Virtu/Roms/AppleIIe.rom
Normal file
Binary file not shown.
BIN
Virtu/Roms/DiskII.rom
Normal file
BIN
Virtu/Roms/DiskII.rom
Normal file
Binary file not shown.
@ -1,9 +1,86 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public class AudioService
|
||||
public class AudioService : MachineService
|
||||
{
|
||||
public virtual void Update() { }
|
||||
public AudioService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public void ToggleOutput() // machine thread
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
long cycles = Machine.Cpu.Cycles;
|
||||
int toggleDelta = (int)(cycles - _toggleCycles);
|
||||
_toggleCycles = cycles;
|
||||
|
||||
_deltaBuffer[_writeIndex] = toggleDelta;
|
||||
_writeIndex = (_writeIndex + 1) % DeltaBufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Update(int bufferSize, Action<byte[], int> 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();
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,10 @@ public override string ToString()
|
||||
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
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public class AsciiKeyEventArgs : EventArgs
|
||||
public sealed class AsciiKeyEventArgs : EventArgs
|
||||
{
|
||||
private AsciiKeyEventArgs()
|
||||
{
|
||||
@ -22,8 +22,13 @@ public static AsciiKeyEventArgs Create(int asciiKey)
|
||||
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 @@ public void WaitForKeyUp()
|
||||
{
|
||||
while (IsAnyKeyDown)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +55,7 @@ public void WaitForResetKeyUp()
|
||||
{
|
||||
while (IsResetKeyDown)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
|
29
Virtu/Services/MachineService.cs
Normal file
29
Virtu/Services/MachineService.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
@ -5,9 +5,9 @@
|
||||
|
||||
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 @@ public void RemoveService(Type serviceType)
|
||||
_serviceProviders.Remove(serviceType);
|
||||
}
|
||||
|
||||
private Dictionary<Type, object> _serviceProviders = new Dictionary<Type, object>();
|
||||
private Dictionary<Type, MachineService> _serviceProviders = new Dictionary<Type, MachineService>();
|
||||
}
|
||||
}
|
||||
|
@ -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<Stream> reader);
|
||||
public abstract void Save(string path, Action<Stream> writer);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -62,6 +62,7 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Windows.Controls.Toolkit, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Windows.Browser" />
|
||||
<Reference Include="System.Xml.Linq, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
|
||||
@ -130,6 +131,9 @@
|
||||
<Compile Include="..\Services\KeyboardService.cs">
|
||||
<Link>Services\KeyboardService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineService.cs">
|
||||
<Link>Services\MachineService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineServices.cs">
|
||||
<Link>Services\MachineServices.cs</Link>
|
||||
</Compile>
|
||||
@ -155,6 +159,7 @@
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\SilverlightAudioService.cs" />
|
||||
<Compile Include="Services\SilverlightKeyboardService.cs" />
|
||||
<Compile Include="Services\SilverlightStorageService.cs" />
|
||||
<Compile Include="Services\SilverlightVideoService.cs" />
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class MainApp : ApplicationBase
|
||||
public sealed partial class MainApp : ApplicationBase
|
||||
{
|
||||
public MainApp() :
|
||||
base("Virtu")
|
||||
|
@ -1,11 +1,18 @@
|
||||
<UserControl x:Class="Jellyfish.Virtu.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:tk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
|
||||
xmlns:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
|
||||
xmlns:jv="clr-namespace:Jellyfish.Virtu;assembly=Jellyfish.Virtu">
|
||||
<Grid Background="Black" Cursor="None">
|
||||
<Image Name="_image" MinWidth="560" MinHeight="384"/>
|
||||
|
||||
<jl:FrameRateCounter/>
|
||||
</Grid>
|
||||
<tk:DockPanel Background="Black">
|
||||
<StackPanel Orientation="Horizontal" tk:DockPanel.Dock="Top">
|
||||
<Button x:Name="_disk1Button" Content="Disk 1" IsTabStop="false" Margin="4 4 0 4"/>
|
||||
<Button x:Name="_disk2Button" Content="Disk 2" IsTabStop="false" Margin="4 4 0 4"/>
|
||||
<jl:FrameRateCounter Margin="4 4 0 4" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Grid Cursor="None">
|
||||
<Image x:Name="_image" MinWidth="560" MinHeight="384"/>
|
||||
<MediaElement x:Name="_media"/>
|
||||
</Grid>
|
||||
</tk:DockPanel>
|
||||
</UserControl>
|
||||
|
@ -1,60 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class MainPage : UserControl
|
||||
public sealed partial class MainPage : UserControl, IDisposable
|
||||
{
|
||||
public MainPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_storageService = new SilverlightStorageService(this);
|
||||
_keyboardService = new SilverlightKeyboardService(this);
|
||||
_gamePortService = new GamePortService(); // not connected
|
||||
_audioService = new AudioService(); // not connected
|
||||
_videoService = new SilverlightVideoService(_image);
|
||||
_storageService = new SilverlightStorageService(_machine);
|
||||
_keyboardService = new SilverlightKeyboardService(_machine, this);
|
||||
_gamePortService = new GamePortService(_machine); // not connected
|
||||
_audioService = new SilverlightAudioService(_machine, this, _media);
|
||||
_videoService = new SilverlightVideoService(_machine, _image);
|
||||
|
||||
_machine = new Machine();
|
||||
_machine.Services.AddService(typeof(StorageService), _storageService);
|
||||
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
||||
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
||||
_machine.Services.AddService(typeof(AudioService), _audioService);
|
||||
_machine.Services.AddService(typeof(VideoService), _videoService);
|
||||
|
||||
Loaded += MainPage_Loaded;
|
||||
Loaded += (sender, e) => _machine.Start();
|
||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
||||
Application.Current.Exit += MainApp_Exit;
|
||||
Application.Current.Exit += (sender, e) => _machine.Stop();
|
||||
|
||||
_disk1Button.Click += (sender, e) => DiskButton_Click(0);
|
||||
_disk2Button.Click += (sender, e) => DiskButton_Click(1);
|
||||
}
|
||||
|
||||
private void MainPage_Loaded(object sender, RoutedEventArgs e)
|
||||
public void Dispose()
|
||||
{
|
||||
_machine.Start();
|
||||
_machine.Dispose();
|
||||
_storageService.Dispose();
|
||||
_keyboardService.Dispose();
|
||||
_gamePortService.Dispose();
|
||||
_audioService.Dispose();
|
||||
_videoService.Dispose();
|
||||
}
|
||||
|
||||
private void CompositionTarget_Rendering(object sender, EventArgs e)
|
||||
{
|
||||
_keyboardService.Update();
|
||||
_gamePortService.Update();
|
||||
_audioService.Update();
|
||||
_videoService.Update();
|
||||
}
|
||||
|
||||
private void MainApp_Exit(object sender, EventArgs e)
|
||||
private void DiskButton_Click(int drive)
|
||||
{
|
||||
_machine.Stop();
|
||||
OpenFileDialog dialog = new OpenFileDialog();
|
||||
dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
|
||||
|
||||
bool? result = dialog.ShowDialog();
|
||||
if (result.HasValue && result.Value)
|
||||
{
|
||||
using (FileStream stream = dialog.File.OpenRead())
|
||||
{
|
||||
_machine.Pause();
|
||||
_machine.DiskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false);
|
||||
_machine.Unpause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Machine _machine = new Machine();
|
||||
|
||||
private StorageService _storageService;
|
||||
private KeyboardService _keyboardService;
|
||||
private GamePortService _gamePortService;
|
||||
private AudioService _audioService;
|
||||
private VideoService _videoService;
|
||||
|
||||
private Machine _machine;
|
||||
}
|
||||
}
|
||||
|
19
Virtu/Silverlight/Services/SilverlightAudioService.cs
Normal file
19
Virtu/Silverlight/Services/SilverlightAudioService.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class SilverlightAudioService : AudioService
|
||||
{
|
||||
public SilverlightAudioService(Machine machine, UserControl page, MediaElement media) :
|
||||
base(machine)
|
||||
{
|
||||
_page = page;
|
||||
_media = media;
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
private UserControl _page;
|
||||
private MediaElement _media;
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Browser;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
@ -10,13 +9,14 @@ namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class SilverlightKeyboardService : KeyboardService
|
||||
{
|
||||
public SilverlightKeyboardService(UserControl page)
|
||||
public SilverlightKeyboardService(Machine machine, UserControl page) :
|
||||
base(machine)
|
||||
{
|
||||
_page = page;
|
||||
|
||||
_page.LostFocus += Page_LostFocus;
|
||||
_page.KeyDown += Page_KeyDown;
|
||||
_page.KeyUp += Page_KeyUp;
|
||||
_page.LostFocus += Page_LostFocus;
|
||||
}
|
||||
|
||||
public override bool IsKeyDown(int key)
|
||||
@ -57,15 +57,6 @@ private bool IsKeyDown(Key key)
|
||||
return _states[(int)key];
|
||||
}
|
||||
|
||||
private void Page_LostFocus(object sender, RoutedEventArgs e) // reset keyboard state on lost focus; can't access keyboard state on got focus
|
||||
{
|
||||
IsAnyKeyDown = false;
|
||||
foreach (Key key in KeyValues)
|
||||
{
|
||||
_states[(int)key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Page_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
_states[(int)e.Key] = true;
|
||||
@ -86,6 +77,15 @@ private void Page_KeyUp(object sender, KeyEventArgs e)
|
||||
_updateAnyKeyDown = true;
|
||||
}
|
||||
|
||||
private void Page_LostFocus(object sender, RoutedEventArgs e) // reset keyboard state on lost focus; can't access keyboard state on got focus
|
||||
{
|
||||
IsAnyKeyDown = false;
|
||||
foreach (Key key in KeyValues)
|
||||
{
|
||||
_states[(int)key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")]
|
||||
private int GetAsciiKey(Key key, int platformKeyCode)
|
||||
|
@ -1,45 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.IsolatedStorage;
|
||||
using System.Threading;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class SilverlightStorageService : StorageService
|
||||
{
|
||||
public SilverlightStorageService(UserControl page)
|
||||
public SilverlightStorageService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
_dispatcher = page.Dispatcher;
|
||||
}
|
||||
|
||||
public override string GetDiskFile()
|
||||
{
|
||||
string fileName = string.Empty;
|
||||
|
||||
// TODO
|
||||
//ManualResetEvent syncEvent = new ManualResetEvent(false);
|
||||
//DispatcherOperation operation = _dispatcher.BeginInvoke(() =>
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// OpenFileDialog dialog = new OpenFileDialog(); // SL expects all dialogs to be user initiated, ie from within an event handler.
|
||||
// dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
|
||||
// bool? result = dialog.ShowDialog();
|
||||
// if (result.HasValue && result.Value)
|
||||
// {
|
||||
// fileName = dialog.File.FullName;
|
||||
// }
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// syncEvent.Set();
|
||||
// }
|
||||
//});
|
||||
//syncEvent.WaitOne();
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public override void Load(string path, Action<Stream> reader)
|
||||
@ -78,7 +47,5 @@ public override void Save(string path, Action<Stream> writer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private Dispatcher _dispatcher;
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,18 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class SilverlightVideoService : VideoService
|
||||
{
|
||||
public SilverlightVideoService(Image image)
|
||||
public SilverlightVideoService(Machine machine, Image image) :
|
||||
base(machine)
|
||||
{
|
||||
_image = image;
|
||||
SetImageSize();
|
||||
|
||||
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight);
|
||||
_pixels = new int[BitmapWidth * BitmapHeight];
|
||||
_image.Source = _bitmap;
|
||||
SetImageSize();
|
||||
|
||||
Application.Current.Host.Content.Resized += (sender, e) => SetImageSize();
|
||||
}
|
||||
@ -73,8 +70,8 @@ private void SetImageSize()
|
||||
private const int BitmapHeight = 384;
|
||||
|
||||
private Image _image;
|
||||
private WriteableBitmap _bitmap;
|
||||
private int[] _pixels;
|
||||
private WriteableBitmap _bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight);
|
||||
private int[] _pixels = new int[BitmapWidth * BitmapHeight];
|
||||
private bool _pixelsDirty;
|
||||
private bool _isFullScreen;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ public override void Initialize()
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
// TODO
|
||||
_audioService.ToggleOutput();
|
||||
}
|
||||
|
||||
private AudioService _audioService;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Jellyfish.Virtu.Properties;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using Jellyfish.Virtu.Settings;
|
||||
|
||||
|
@ -42,10 +42,12 @@
|
||||
</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Xna.Framework, Version=3.1.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=x86" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Xml.Linq">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
@ -128,6 +130,9 @@
|
||||
<Compile Include="..\MachineEvents.cs">
|
||||
<Link>Core\MachineEvents.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineService.cs">
|
||||
<Link>Services\MachineService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineServices.cs">
|
||||
<Link>Services\MachineServices.cs</Link>
|
||||
</Compile>
|
||||
@ -161,9 +166,13 @@
|
||||
<Compile Include="..\Services\VideoService.cs">
|
||||
<Link>Services\VideoService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Xna\Services\XnaGamePortService.cs">
|
||||
<Link>Services\XnaGamePortService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Services\WpfAudioService.cs" />
|
||||
<Compile Include="Services\WpfKeyboardService.cs" />
|
||||
<Compile Include="Services\WpfStorageService.cs" />
|
||||
<Compile Include="Services\WpfVideoService.cs" />
|
||||
|
@ -1,9 +1,11 @@
|
||||
using Jellyfish.Library;
|
||||
using System.Security.Permissions;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class MainApp : ApplicationBase
|
||||
public sealed partial class MainApp : ApplicationBase
|
||||
{
|
||||
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||
public MainApp() :
|
||||
base("Virtu")
|
||||
{
|
||||
|
@ -3,9 +3,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
|
||||
Title="Virtu" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
|
||||
<Grid Background="Black" Cursor="None">
|
||||
<Image Name="_image" MinWidth="560" MinHeight="384" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
|
||||
|
||||
<jl:FrameRateCounter/>
|
||||
</Grid>
|
||||
<DockPanel Background="Black">
|
||||
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
|
||||
<Button x:Name="_disk1Button" Content="Disk 1" Focusable="false" IsTabStop="false" Margin="4 4 0 4"/>
|
||||
<Button x:Name="_disk2Button" Content="Disk 2" Focusable="false" IsTabStop="false" Margin="4 4 0 4"/>
|
||||
<jl:FrameRateCounter Margin="4 4 0 4" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Grid Cursor="None">
|
||||
<Image x:Name="_image" MinWidth="560" MinHeight="384" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
@ -1,58 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
public sealed partial class MainWindow : Window, IDisposable
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_storageService = new WpfStorageService();
|
||||
_keyboardService = new WpfKeyboardService(this);
|
||||
_gamePortService = new GamePortService(); // not connected
|
||||
_audioService = new AudioService(); // not connected
|
||||
_videoService = new WpfVideoService(this, _image);
|
||||
_storageService = new WpfStorageService(_machine);
|
||||
_keyboardService = new WpfKeyboardService(_machine, this);
|
||||
_gamePortService = new XnaGamePortService(_machine);
|
||||
_audioService = new WpfAudioService(_machine, this);
|
||||
_videoService = new WpfVideoService(_machine, this, _image);
|
||||
|
||||
_machine = new Machine();
|
||||
_machine.Services.AddService(typeof(StorageService), _storageService);
|
||||
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
||||
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
||||
_machine.Services.AddService(typeof(AudioService), _audioService);
|
||||
_machine.Services.AddService(typeof(VideoService), _videoService);
|
||||
|
||||
Loaded += MainWindow_Loaded;
|
||||
Loaded += (sender, e) => _machine.Start();
|
||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
||||
Application.Current.Exit += MainApp_Exit;
|
||||
Application.Current.Exit += (sender, e) => _machine.Stop();
|
||||
|
||||
_disk1Button.Click += (sender, e) => DiskButton_Click(0);
|
||||
_disk2Button.Click += (sender, e) => DiskButton_Click(1);
|
||||
}
|
||||
|
||||
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
public void Dispose()
|
||||
{
|
||||
_machine.Start();
|
||||
_machine.Dispose();
|
||||
_storageService.Dispose();
|
||||
_keyboardService.Dispose();
|
||||
_gamePortService.Dispose();
|
||||
_audioService.Dispose();
|
||||
_videoService.Dispose();
|
||||
}
|
||||
|
||||
private void CompositionTarget_Rendering(object sender, EventArgs e)
|
||||
{
|
||||
_keyboardService.Update();
|
||||
_gamePortService.Update();
|
||||
_audioService.Update();
|
||||
_videoService.Update();
|
||||
}
|
||||
|
||||
private void MainApp_Exit(object sender, ExitEventArgs e)
|
||||
private void DiskButton_Click(int drive)
|
||||
{
|
||||
_machine.Stop();
|
||||
OpenFileDialog dialog = new OpenFileDialog();
|
||||
dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
|
||||
|
||||
bool? result = dialog.ShowDialog();
|
||||
if (result.HasValue && result.Value)
|
||||
{
|
||||
using (FileStream stream = File.OpenRead(dialog.FileName))
|
||||
{
|
||||
_machine.Pause();
|
||||
_machine.DiskII.Drives[drive].InsertDisk(dialog.FileName, stream, false);
|
||||
_machine.Unpause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Machine _machine = new Machine();
|
||||
|
||||
private StorageService _storageService;
|
||||
private KeyboardService _keyboardService;
|
||||
private GamePortService _gamePortService;
|
||||
private AudioService _audioService;
|
||||
private VideoService _videoService;
|
||||
|
||||
private Machine _machine;
|
||||
}
|
||||
}
|
||||
|
41
Virtu/Wpf/Services/WpfAudioService.cs
Normal file
41
Virtu/Wpf/Services/WpfAudioService.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class WpfAudioService : AudioService
|
||||
{
|
||||
public WpfAudioService(Machine machine, Window window) :
|
||||
base(machine)
|
||||
{
|
||||
_window = window;
|
||||
|
||||
_window.SourceInitialized += (sender, e) => _directSound.Start(_window.GetHandle());
|
||||
_directSound.Update += DirectSound_Update;
|
||||
_window.Closed += (sender, e) => _directSound.Stop();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_directSound.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void DirectSound_Update(object sender, DirectSoundUpdateEventArgs e)
|
||||
{
|
||||
IntPtr buffer = e.Buffer;
|
||||
Update(e.BufferSize, (source, count) =>
|
||||
{
|
||||
Marshal.Copy(source, 0, buffer, count);
|
||||
buffer = (IntPtr)((long)buffer + count);
|
||||
});
|
||||
}
|
||||
|
||||
private Window _window;
|
||||
private DirectSound _directSound = new DirectSound(SampleRate, SampleChannels, SampleBits, SampleSize);
|
||||
}
|
||||
}
|
@ -8,13 +8,14 @@ namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class WpfKeyboardService : KeyboardService
|
||||
{
|
||||
public WpfKeyboardService(Window window)
|
||||
public WpfKeyboardService(Machine machine, Window window) :
|
||||
base(machine)
|
||||
{
|
||||
_window = window;
|
||||
|
||||
_window.GotKeyboardFocus += Window_GotKeyboardFocus;
|
||||
_window.KeyDown += Window_KeyDown;
|
||||
_window.KeyUp += Window_KeyUp;
|
||||
_window.GotKeyboardFocus += (sender, e) => _updateAnyKeyDown = true;
|
||||
}
|
||||
|
||||
public override bool IsKeyDown(int key)
|
||||
@ -55,11 +56,6 @@ private bool IsKeyDown(Key key)
|
||||
return _states[(int)key];
|
||||
}
|
||||
|
||||
private void Window_GotKeyboardFocus(object sender, EventArgs e)
|
||||
{
|
||||
_updateAnyKeyDown = true;
|
||||
}
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
_states[(int)((e.Key == Key.System) ? e.SystemKey : e.Key)] = true;
|
||||
|
@ -2,29 +2,14 @@
|
||||
using System.Deployment.Application;
|
||||
using System.IO;
|
||||
using System.IO.IsolatedStorage;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class WpfStorageService : StorageService
|
||||
{
|
||||
public override string GetDiskFile()
|
||||
public WpfStorageService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
string fileName = string.Empty;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(new Action(() =>
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog();
|
||||
dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
|
||||
bool? result = dialog.ShowDialog();
|
||||
if (result.HasValue && result.Value)
|
||||
{
|
||||
fileName = dialog.FileName;
|
||||
}
|
||||
}));
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public override void Load(string path, Action<Stream> reader)
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Permissions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
@ -10,15 +11,14 @@ namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class WpfVideoService : VideoService
|
||||
{
|
||||
public WpfVideoService(Window window, Image image)
|
||||
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||
public WpfVideoService(Machine machine, Window window, Image image) :
|
||||
base(machine)
|
||||
{
|
||||
_window = window;
|
||||
_image = image;
|
||||
SetImageSize();
|
||||
|
||||
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapDpi, BitmapDpi, BitmapPixelFormat, null);
|
||||
_pixels = new uint[BitmapWidth * BitmapHeight];
|
||||
_image.Source = _bitmap;
|
||||
SetImageSize();
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += (sender, e) => SetImageSize();
|
||||
}
|
||||
@ -74,8 +74,8 @@ private void SetImageSize()
|
||||
|
||||
private Window _window;
|
||||
private Image _image;
|
||||
private WriteableBitmap _bitmap;
|
||||
private uint[] _pixels;
|
||||
private WriteableBitmap _bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapDpi, BitmapDpi, BitmapPixelFormat, null);
|
||||
private uint[] _pixels = new uint[BitmapWidth * BitmapHeight];
|
||||
private bool _pixelsDirty;
|
||||
private bool _isFullScreen;
|
||||
}
|
||||
|
@ -128,6 +128,9 @@
|
||||
<Compile Include="..\Services\KeyboardService.cs">
|
||||
<Link>Services\KeyboardService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineService.cs">
|
||||
<Link>Services\MachineService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineServices.cs">
|
||||
<Link>Services\MachineServices.cs</Link>
|
||||
</Compile>
|
||||
@ -149,6 +152,7 @@
|
||||
<Compile Include="MainApp.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="MainGame.cs" />
|
||||
<Compile Include="Services\XnaAudioService.cs" />
|
||||
<Compile Include="Services\XnaGamePortService.cs" />
|
||||
<Compile Include="Services\XnaKeyboardService.cs" />
|
||||
<Compile Include="Services\XnaStorageService.cs" />
|
||||
|
@ -149,6 +149,9 @@
|
||||
<Compile Include="..\Services\KeyboardService.cs">
|
||||
<Link>Services\KeyboardService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineService.cs">
|
||||
<Link>Services\MachineService.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Services\MachineServices.cs">
|
||||
<Link>Services\MachineServices.cs</Link>
|
||||
</Compile>
|
||||
@ -170,6 +173,7 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="MainApp.cs" />
|
||||
<Compile Include="MainGame.cs" />
|
||||
<Compile Include="Services\XnaAudioService.cs" />
|
||||
<Compile Include="Services\XnaGamePortService.cs" />
|
||||
<Compile Include="Services\XnaKeyboardService.cs" />
|
||||
<Compile Include="Services\XnaStorageService.cs" />
|
||||
|
@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
static class MainApp
|
||||
|
@ -18,13 +18,16 @@ public MainGame() :
|
||||
GraphicsDeviceManager.PreferredBackBufferWidth = 560;
|
||||
GraphicsDeviceManager.PreferredBackBufferHeight = 384;
|
||||
#endif
|
||||
_storageService = new XnaStorageService(this);
|
||||
_keyboardService = new XnaKeyboardService();
|
||||
_gamePortService = new XnaGamePortService();
|
||||
_audioService = new AudioService(); // not connected
|
||||
_videoService = new XnaVideoService(this);
|
||||
_storageService = new XnaStorageService(_machine, this);
|
||||
_keyboardService = new XnaKeyboardService(_machine);
|
||||
_gamePortService = new XnaGamePortService(_machine);
|
||||
#if XBOX
|
||||
_audioService = new AudioService(_machine); // not connected
|
||||
#else
|
||||
_audioService = new XnaAudioService(_machine, this);
|
||||
#endif
|
||||
_videoService = new XnaVideoService(_machine, this);
|
||||
|
||||
_machine = new Machine();
|
||||
_machine.Services.AddService(typeof(StorageService), _storageService);
|
||||
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
||||
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
||||
@ -32,6 +35,20 @@ public MainGame() :
|
||||
_machine.Services.AddService(typeof(VideoService), _videoService);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_machine.Dispose();
|
||||
_storageService.Dispose();
|
||||
_keyboardService.Dispose();
|
||||
_gamePortService.Dispose();
|
||||
_audioService.Dispose();
|
||||
_videoService.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void BeginRun()
|
||||
{
|
||||
_machine.Start();
|
||||
@ -41,7 +58,6 @@ protected override void Update(GameTime gameTime)
|
||||
{
|
||||
_keyboardService.Update();
|
||||
_gamePortService.Update();
|
||||
_audioService.Update();
|
||||
base.Update(gameTime);
|
||||
}
|
||||
|
||||
@ -57,12 +73,12 @@ protected override void EndRun()
|
||||
_machine.Stop();
|
||||
}
|
||||
|
||||
private Machine _machine = new Machine();
|
||||
|
||||
private StorageService _storageService;
|
||||
private KeyboardService _keyboardService;
|
||||
private GamePortService _gamePortService;
|
||||
private AudioService _audioService;
|
||||
private VideoService _videoService;
|
||||
|
||||
private Machine _machine;
|
||||
}
|
||||
}
|
||||
|
42
Virtu/Xna/Services/XnaAudioService.cs
Normal file
42
Virtu/Xna/Services/XnaAudioService.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
#if WINDOWS
|
||||
public sealed class XnaAudioService : AudioService
|
||||
{
|
||||
public XnaAudioService(Machine machine, GameBase game) :
|
||||
base(machine)
|
||||
{
|
||||
_game = game;
|
||||
|
||||
_directSound.Start(_game.Window.Handle);
|
||||
_directSound.Update += DirectSound_Update;
|
||||
_game.Exiting += (sender, e) => _directSound.Stop();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_directSound.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void DirectSound_Update(object sender, DirectSoundUpdateEventArgs e)
|
||||
{
|
||||
IntPtr buffer = e.Buffer;
|
||||
Update(e.BufferSize, (source, count) =>
|
||||
{
|
||||
Marshal.Copy(source, 0, buffer, count);
|
||||
buffer = (IntPtr)((long)buffer + count);
|
||||
});
|
||||
}
|
||||
|
||||
private GameBase _game;
|
||||
private DirectSound _directSound = new DirectSound(SampleRate, SampleChannels, SampleBits, SampleSize);
|
||||
}
|
||||
#endif
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class XnaGamePortService : GamePortService
|
||||
{
|
||||
public XnaGamePortService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
_lastState = _state;
|
||||
|
@ -8,17 +8,11 @@ namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class XnaKeyboardService : KeyboardService
|
||||
{
|
||||
public XnaKeyboardService() :
|
||||
this(TimeSpan.FromMilliseconds(500).Ticks, TimeSpan.FromMilliseconds(32).Ticks)
|
||||
public XnaKeyboardService(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public XnaKeyboardService(long repeatDelay, long repeatSpeed)
|
||||
{
|
||||
_repeatDelay = repeatDelay;
|
||||
_repeatSpeed = repeatSpeed;
|
||||
}
|
||||
|
||||
public override bool IsKeyDown(int key)
|
||||
{
|
||||
return IsKeyDown((Keys)key);
|
||||
@ -46,7 +40,7 @@ public override void Update()
|
||||
|
||||
_lastKey = key;
|
||||
_lastTime = DateTime.UtcNow.Ticks;
|
||||
_repeatTime = _repeatDelay;
|
||||
_repeatTime = RepeatDelay;
|
||||
#if XBOX
|
||||
int asciiKey = GetAsciiKey(key, ref gamePadState);
|
||||
#else
|
||||
@ -71,7 +65,7 @@ public override void Update()
|
||||
if (time - _lastTime >= _repeatTime)
|
||||
{
|
||||
_lastTime = time;
|
||||
_repeatTime = _repeatSpeed;
|
||||
_repeatTime = RepeatSpeed;
|
||||
#if XBOX
|
||||
int asciiKey = GetAsciiKey(_lastKey, ref gamePadState);
|
||||
#else
|
||||
@ -356,6 +350,8 @@ where field.IsLiteral
|
||||
where (key != Keys.None) // filter Keys.None
|
||||
select key).ToArray();
|
||||
#endif
|
||||
private static readonly long RepeatDelay = TimeSpan.FromMilliseconds(500).Ticks;
|
||||
private static readonly long RepeatSpeed = TimeSpan.FromMilliseconds(32).Ticks;
|
||||
|
||||
private KeyboardState _state;
|
||||
private KeyboardState _lastState;
|
||||
@ -363,7 +359,5 @@ where field.IsLiteral
|
||||
private Keys _lastKey;
|
||||
private long _lastTime;
|
||||
private long _repeatTime;
|
||||
private long _repeatDelay;
|
||||
private long _repeatSpeed;
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,10 @@ namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class XnaStorageService : StorageService
|
||||
{
|
||||
public XnaStorageService(GameBase game)
|
||||
public XnaStorageService(Machine machine, GameBase game) :
|
||||
base(machine)
|
||||
{
|
||||
_game = game;
|
||||
_storageDevice = new Lazy<StorageDevice>(() => Guide.EndShowStorageDeviceSelector(Guide.BeginShowStorageDeviceSelector(null, null)));
|
||||
}
|
||||
|
||||
public override string GetDiskFile()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override void Load(string path, Action<Stream> reader)
|
||||
@ -48,6 +43,6 @@ public override void Save(string path, Action<Stream> writer)
|
||||
}
|
||||
|
||||
private GameBase _game;
|
||||
private Lazy<StorageDevice> _storageDevice;
|
||||
private Lazy<StorageDevice> _storageDevice = new Lazy<StorageDevice>(() => Guide.EndShowStorageDeviceSelector(Guide.BeginShowStorageDeviceSelector(null, null)));
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,16 @@
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
public sealed class XnaVideoService : VideoService, IDisposable
|
||||
public sealed class XnaVideoService : VideoService
|
||||
{
|
||||
public XnaVideoService(GameBase game)
|
||||
public XnaVideoService(Machine machine, GameBase game) :
|
||||
base(machine)
|
||||
{
|
||||
_game = game;
|
||||
|
||||
_game.GraphicsDeviceManager.PreparingDeviceSettings += GraphicsDeviceManager_PreparingDeviceSettings;
|
||||
_game.GraphicsDeviceService.DeviceCreated += GraphicsDeviceService_DeviceCreated;
|
||||
_game.GraphicsDeviceService.DeviceReset += GraphicsDeviceService_DeviceReset;
|
||||
_game.GraphicsDeviceService.DeviceReset += (sender, e) => SetTexturePosition();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "y*560")]
|
||||
@ -53,10 +54,13 @@ public override void Update()
|
||||
_spriteBatch.End();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_spriteBatch.Dispose();
|
||||
_texture.Dispose();
|
||||
if (disposing)
|
||||
{
|
||||
_spriteBatch.Dispose();
|
||||
_texture.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void GraphicsDeviceManager_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
|
||||
@ -83,11 +87,6 @@ private void GraphicsDeviceService_DeviceCreated(object sender, EventArgs e)
|
||||
SetTexturePosition();
|
||||
}
|
||||
|
||||
private void GraphicsDeviceService_DeviceReset(object sender, EventArgs e)
|
||||
{
|
||||
SetTexturePosition();
|
||||
}
|
||||
|
||||
private void SetTexturePosition()
|
||||
{
|
||||
_texturePosition.X = (_graphicsDevice.PresentationParameters.BackBufferWidth - TextureWidth * _textureScale) / 2f; // centered
|
||||
|
Loading…
Reference in New Issue
Block a user