mirror of
https://github.com/digital-jellyfish/Virtu.git
synced 2024-06-02 12:41:31 +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.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
|
using System.Security.Permissions;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
|
||||||
namespace Jellyfish.Library
|
namespace Jellyfish.Library
|
||||||
|
@ -9,6 +10,7 @@ namespace Jellyfish.Library
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public sealed class SecurityAttributes
|
public sealed class SecurityAttributes
|
||||||
{
|
{
|
||||||
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||||
public SecurityAttributes()
|
public SecurityAttributes()
|
||||||
{
|
{
|
||||||
_length = Marshal.SizeOf(typeof(SecurityAttributes));
|
_length = Marshal.SizeOf(typeof(SecurityAttributes));
|
||||||
|
@ -116,31 +118,21 @@ public void AddAuditRule(GeneralAuditRule rule)
|
||||||
base.AddAuditRule(rule);
|
base.AddAuditRule(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||||
public void GetSecurityAttributes(bool inheritable, Action<SecurityAttributes> action)
|
public void GetSecurityAttributes(bool inheritable, Action<SecurityAttributes> action)
|
||||||
{
|
{
|
||||||
GetSecurityAttributes(this, inheritable, action);
|
GetSecurityAttributes(this, inheritable, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||||
public static void GetSecurityAttributes(ObjectSecurity security, bool inheritable, Action<SecurityAttributes> action)
|
public static void GetSecurityAttributes(ObjectSecurity security, bool inheritable, Action<SecurityAttributes> action)
|
||||||
{
|
{
|
||||||
if (security != null)
|
if (security != null)
|
||||||
{
|
{
|
||||||
GCHandle gcHandle = new GCHandle();
|
GCHandleHelpers.Pin(security.GetSecurityDescriptorBinaryForm(), securityDescriptor =>
|
||||||
try
|
|
||||||
{
|
{
|
||||||
gcHandle = GCHandle.Alloc(security.GetSecurityDescriptorBinaryForm(), GCHandleType.Pinned);
|
action(new SecurityAttributes() { SecurityDescriptor = securityDescriptor, InheritHandle = inheritable });
|
||||||
SecurityAttributes securityAttributes = new SecurityAttributes();
|
});
|
||||||
securityAttributes.SecurityDescriptor = gcHandle.AddrOfPinnedObject();
|
|
||||||
securityAttributes.InheritHandle = inheritable;
|
|
||||||
action(securityAttributes);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (gcHandle.IsAllocated)
|
|
||||||
{
|
|
||||||
gcHandle.Free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (inheritable)
|
else if (inheritable)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Library
|
namespace Jellyfish.Library
|
||||||
{
|
{
|
||||||
public class Lazy<T> where T : class
|
public sealed class Lazy<T> where T : class
|
||||||
{
|
{
|
||||||
public Lazy(Func<T> initializer)
|
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 class ApplicationBase : Application
|
||||||
{
|
{
|
||||||
public ApplicationBase() :
|
|
||||||
this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApplicationBase(string name)
|
public ApplicationBase(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Library
|
namespace Jellyfish.Library
|
||||||
{
|
{
|
||||||
public partial class FrameRateCounter : UserControl
|
public sealed partial class FrameRateCounter : UserControl
|
||||||
{
|
{
|
||||||
public FrameRateCounter()
|
public FrameRateCounter()
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,9 +54,6 @@
|
||||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||||
<Link>AssemblyCommentAttribute.cs</Link>
|
<Link>AssemblyCommentAttribute.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\FileHelpers.cs">
|
|
||||||
<Link>FileHelpers.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\GlobalSuppressions.cs">
|
<Compile Include="..\GlobalSuppressions.cs">
|
||||||
<Link>GlobalSuppressions.cs</Link>
|
<Link>GlobalSuppressions.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Library
|
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)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Security.Permissions;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
@ -8,11 +9,7 @@ namespace Jellyfish.Library
|
||||||
{
|
{
|
||||||
public class ApplicationBase : Application
|
public class ApplicationBase : Application
|
||||||
{
|
{
|
||||||
public ApplicationBase() :
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||||
this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApplicationBase(string name)
|
public ApplicationBase(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Library
|
namespace Jellyfish.Library
|
||||||
{
|
{
|
||||||
public partial class FrameRateCounter : UserControl
|
public sealed partial class FrameRateCounter : UserControl
|
||||||
{
|
{
|
||||||
public FrameRateCounter()
|
public FrameRateCounter()
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,8 +62,14 @@
|
||||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||||
<Link>AssemblyCommentAttribute.cs</Link>
|
<Link>AssemblyCommentAttribute.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\FileHelpers.cs">
|
<Compile Include="..\DirectSound.cs">
|
||||||
<Link>FileHelpers.cs</Link>
|
<Link>DirectSound.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\DirectSoundInterop.cs">
|
||||||
|
<Link>DirectSoundInterop.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\GCHandleHelpers.cs">
|
||||||
|
<Link>GCHandleHelpers.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\GeneralSecurity.cs">
|
<Compile Include="..\GeneralSecurity.cs">
|
||||||
<Link>GeneralSecurity.cs</Link>
|
<Link>GeneralSecurity.cs</Link>
|
||||||
|
@ -77,6 +83,9 @@
|
||||||
<Compile Include="..\Lazy.cs">
|
<Compile Include="..\Lazy.cs">
|
||||||
<Link>Lazy.cs</Link>
|
<Link>Lazy.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MarshalHelpers.cs">
|
||||||
|
<Link>MarshalHelpers.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MathHelpers.cs">
|
<Compile Include="..\MathHelpers.cs">
|
||||||
<Link>MathHelpers.cs</Link>
|
<Link>MathHelpers.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -100,6 +109,7 @@
|
||||||
<DependentUpon>FrameRateCounter.xaml</DependentUpon>
|
<DependentUpon>FrameRateCounter.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="WindowExtensions.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Include="FrameRateCounter.xaml">
|
<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
|
namespace Jellyfish.Library
|
||||||
{
|
{
|
||||||
public class FrameRateCounter : DrawableGameComponent
|
public sealed class FrameRateCounter : DrawableGameComponent
|
||||||
{
|
{
|
||||||
public FrameRateCounter(GameBase game) :
|
public FrameRateCounter(GameBase game) :
|
||||||
base(game)
|
base(game)
|
||||||
{
|
{
|
||||||
_frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage
|
|
||||||
|
|
||||||
FontColor = Color.White;
|
FontColor = Color.White;
|
||||||
FontName = "Default";
|
FontName = "Default";
|
||||||
|
|
||||||
|
@ -68,6 +66,6 @@ public override void Update(GameTime gameTime)
|
||||||
private long _elapsedTime;
|
private long _elapsedTime;
|
||||||
private int _frameCount;
|
private int _frameCount;
|
||||||
private int _frameRate;
|
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 class GameBase : Game
|
||||||
{
|
{
|
||||||
public GameBase() :
|
|
||||||
this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public GameBase(string name)
|
public GameBase(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
|
@ -62,9 +62,6 @@
|
||||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||||
<Link>AssemblyCommentAttribute.cs</Link>
|
<Link>AssemblyCommentAttribute.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\FileHelpers.cs">
|
|
||||||
<Link>FileHelpers.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\GlobalSuppressions.cs">
|
<Compile Include="..\GlobalSuppressions.cs">
|
||||||
<Link>GlobalSuppressions.cs</Link>
|
<Link>GlobalSuppressions.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -60,9 +60,6 @@
|
||||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||||
<Link>AssemblyCommentAttribute.cs</Link>
|
<Link>AssemblyCommentAttribute.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\FileHelpers.cs">
|
|
||||||
<Link>FileHelpers.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\GlobalSuppressions.cs">
|
<Compile Include="..\GlobalSuppressions.cs">
|
||||||
<Link>GlobalSuppressions.cs</Link>
|
<Link>GlobalSuppressions.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -84,8 +84,14 @@
|
||||||
<Compile Include="..\AssemblyCommentAttribute.cs">
|
<Compile Include="..\AssemblyCommentAttribute.cs">
|
||||||
<Link>AssemblyCommentAttribute.cs</Link>
|
<Link>AssemblyCommentAttribute.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\FileHelpers.cs">
|
<Compile Include="..\DirectSound.cs">
|
||||||
<Link>FileHelpers.cs</Link>
|
<Link>DirectSound.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\DirectSoundInterop.cs">
|
||||||
|
<Link>DirectSoundInterop.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\GCHandleHelpers.cs">
|
||||||
|
<Link>GCHandleHelpers.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\GeneralSecurity.cs">
|
<Compile Include="..\GeneralSecurity.cs">
|
||||||
<Link>GeneralSecurity.cs</Link>
|
<Link>GeneralSecurity.cs</Link>
|
||||||
|
@ -99,6 +105,9 @@
|
||||||
<Compile Include="..\Lazy.cs">
|
<Compile Include="..\Lazy.cs">
|
||||||
<Link>Lazy.cs</Link>
|
<Link>Lazy.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MarshalHelpers.cs">
|
||||||
|
<Link>MarshalHelpers.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MathHelpers.cs">
|
<Compile Include="..\MathHelpers.cs">
|
||||||
<Link>MathHelpers.cs</Link>
|
<Link>MathHelpers.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Jellyfish.Virtu.Properties;
|
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
|
@ -187,6 +186,7 @@ public int Execute()
|
||||||
Opcode = _memory.Read(RPC);
|
Opcode = _memory.Read(RPC);
|
||||||
RPC = (RPC + 1) & 0xFFFF;
|
RPC = (RPC + 1) & 0xFFFF;
|
||||||
_executeOpcode[Opcode]();
|
_executeOpcode[Opcode]();
|
||||||
|
Cycles += CC;
|
||||||
|
|
||||||
// System.Diagnostics.Debug.WriteLine(" " + ToString());
|
// System.Diagnostics.Debug.WriteLine(" " + ToString());
|
||||||
|
|
||||||
|
@ -3243,6 +3243,7 @@ private void UpdateSettings()
|
||||||
public int EA { get; private set; }
|
public int EA { get; private set; }
|
||||||
public int CC { get; private set; }
|
public int CC { get; private set; }
|
||||||
public int Opcode { get; private set; }
|
public int Opcode { get; private set; }
|
||||||
|
public long Cycles { get; private set; }
|
||||||
|
|
||||||
private Action _updateEvent;
|
private Action _updateEvent;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ public partial class Cpu
|
||||||
private const int CyclesPerUpdate = 17030;
|
private const int CyclesPerUpdate = 17030;
|
||||||
private const int CyclesPerVSync = 17030;
|
private const int CyclesPerVSync = 17030;
|
||||||
private const int CyclesPerSecond = 1022730;
|
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;
|
private const int OpcodeCount = 256;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<Word>Annunciator</Word>
|
<Word>Annunciator</Word>
|
||||||
<Word>Dsk</Word>
|
<Word>Dsk</Word>
|
||||||
<Word>Opcode</Word>
|
<Word>Opcode</Word>
|
||||||
|
<Word>Unpause</Word>
|
||||||
<Word>Virtu</Word>
|
<Word>Virtu</Word>
|
||||||
<Word>Xna</Word>
|
<Word>Xna</Word>
|
||||||
<Word>x</Word>
|
<Word>x</Word>
|
||||||
|
|
|
@ -266,25 +266,25 @@ private void WriteDataNibbles(int sectorOffset)
|
||||||
private static readonly byte[] NibbleToByte = new byte[]
|
private static readonly byte[] NibbleToByte = new byte[]
|
||||||
{
|
{
|
||||||
// padding for offset (not used)
|
// padding for offset (not used)
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
|
||||||
|
|
||||||
// nibble translate table
|
// nibble translate table
|
||||||
0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
|
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 System.Diagnostics.CodeAnalysis;
|
||||||
using Jellyfish.Virtu.Services;
|
|
||||||
using Jellyfish.Virtu.Settings;
|
using Jellyfish.Virtu.Settings;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public sealed class DiskII : MachineComponent
|
public sealed class DiskII : MachineComponent
|
||||||
{
|
{
|
||||||
public DiskII(Machine machine) :
|
public DiskII(Machine machine) :
|
||||||
base(machine)
|
base(machine)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
_storageService = Machine.Services.GetService<StorageService>();
|
#if WINDOWS
|
||||||
|
|
||||||
DiskIISettings settings = Machine.Settings.DiskII;
|
DiskIISettings settings = Machine.Settings.DiskII;
|
||||||
if (settings.Disk1.Name.Length == 0)
|
|
||||||
{
|
|
||||||
settings.Disk1.Name = _storageService.GetDiskFile();
|
|
||||||
}
|
|
||||||
if (settings.Disk1.Name.Length > 0)
|
if (settings.Disk1.Name.Length > 0)
|
||||||
{
|
{
|
||||||
_drives[0].InsertDisk(settings.Disk1.Name, settings.Disk1.IsWriteProtected);
|
_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);
|
_drives[1].InsertDisk(settings.Disk2.Name, settings.Disk2.IsWriteProtected);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Reset()
|
public override void Reset()
|
||||||
|
@ -42,10 +37,6 @@ public override void Reset()
|
||||||
public override void Uninitialize()
|
public override void Uninitialize()
|
||||||
{
|
{
|
||||||
Flush();
|
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")]
|
[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 Phase0On = 1 << 0;
|
||||||
private const int Phase1On = 1 << 1;
|
private const int Phase1On = 1 << 1;
|
||||||
private const int Phase2On = 1 << 2;
|
private const int Phase2On = 1 << 2;
|
||||||
private const int Phase3On = 1 << 3;
|
private const int Phase3On = 1 << 3;
|
||||||
|
|
||||||
private StorageService _storageService;
|
|
||||||
|
|
||||||
private Drive525[] _drives = new Drive525[] { new Drive525(), new Drive525() };
|
private Drive525[] _drives = new Drive525[] { new Drive525(), new Drive525() };
|
||||||
private int _latch;
|
private int _latch;
|
||||||
private int _phaseStates;
|
private int _phaseStates;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using Jellyfish.Library;
|
using Jellyfish.Library;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public class Drive525
|
public sealed class Drive525
|
||||||
{
|
{
|
||||||
public Drive525()
|
public Drive525()
|
||||||
{
|
{
|
||||||
|
@ -14,13 +15,20 @@ public Drive525()
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InsertDisk(string fileName, bool isWriteProtected)
|
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();
|
FlushTrack();
|
||||||
|
|
||||||
// TODO handle null param/empty string for eject, or add Eject()
|
// TODO handle null param/empty string for eject, or add Eject()
|
||||||
|
|
||||||
byte[] fileData = FileHelpers.ReadAllBytes(fileName);
|
_disk = Disk525.CreateDisk(name, stream.ReadAllBytes(), isWriteProtected);
|
||||||
_disk = Disk525.CreateDisk(fileName, fileData, isWriteProtected);
|
|
||||||
_trackLoaded = false;
|
_trackLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Jellyfish.Library;
|
using Jellyfish.Library;
|
||||||
using Jellyfish.Virtu.Properties;
|
|
||||||
using Jellyfish.Virtu.Services;
|
using Jellyfish.Virtu.Services;
|
||||||
using Jellyfish.Virtu.Settings;
|
using Jellyfish.Virtu.Settings;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Jellyfish.Virtu.Properties;
|
|
||||||
using Jellyfish.Virtu.Services;
|
using Jellyfish.Virtu.Services;
|
||||||
using Jellyfish.Virtu.Settings;
|
using Jellyfish.Virtu.Settings;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.ObjectModel;
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Jellyfish.Library;
|
using Jellyfish.Library;
|
||||||
using Jellyfish.Virtu.Services;
|
using Jellyfish.Virtu.Services;
|
||||||
|
@ -6,11 +7,12 @@
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public class Machine
|
public enum MachineState { Stopped = 0, Starting, Running, Pausing, Paused, Stopping }
|
||||||
|
|
||||||
|
public sealed class Machine : IDisposable
|
||||||
{
|
{
|
||||||
public Machine()
|
public Machine()
|
||||||
{
|
{
|
||||||
Thread = new Thread(Run) { Name = "Machine" };
|
|
||||||
Events = new MachineEvents();
|
Events = new MachineEvents();
|
||||||
Services = new MachineServices();
|
Services = new MachineServices();
|
||||||
Settings = new MachineSettings();
|
Settings = new MachineSettings();
|
||||||
|
@ -23,8 +25,15 @@ public Machine()
|
||||||
Cassette = new Cassette(this);
|
Cassette = new Cassette(this);
|
||||||
Speaker = new Speaker(this);
|
Speaker = new Speaker(this);
|
||||||
Video = new Video(this);
|
Video = new Video(this);
|
||||||
|
|
||||||
Components = new Collection<MachineComponent> { Cpu, Memory, DiskII, Keyboard, GamePort, Cassette, Speaker, Video };
|
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()
|
public void Reset()
|
||||||
|
@ -36,35 +45,64 @@ public void Start()
|
||||||
{
|
{
|
||||||
_storageService = Services.GetService<StorageService>();
|
_storageService = Services.GetService<StorageService>();
|
||||||
_storageService.Load(MachineSettings.FileName, stream => Settings.Deserialize(stream));
|
_storageService.Load(MachineSettings.FileName, stream => Settings.Deserialize(stream));
|
||||||
|
|
||||||
|
State = MachineState.Starting;
|
||||||
Thread.Start();
|
Thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Pause()
|
||||||
|
{
|
||||||
|
State = MachineState.Pausing;
|
||||||
|
_pausedEvent.WaitOne();
|
||||||
|
State = MachineState.Paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unpause()
|
||||||
|
{
|
||||||
|
State = MachineState.Running;
|
||||||
|
_unpauseEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_stopPending = true;
|
State = MachineState.Stopping;
|
||||||
|
_unpauseEvent.Set();
|
||||||
Thread.Join();
|
Thread.Join();
|
||||||
|
State = MachineState.Stopped;
|
||||||
|
|
||||||
_storageService.Save(MachineSettings.FileName, stream => Settings.Serialize(stream));
|
_storageService.Save(MachineSettings.FileName, stream => Settings.Serialize(stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Run()
|
private void Run() // machine thread
|
||||||
{
|
{
|
||||||
Components.ForEach(component => component.Initialize());
|
Components.ForEach(component => component.Initialize());
|
||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
|
State = MachineState.Running;
|
||||||
do
|
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());
|
Components.ForEach(component => component.Uninitialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Thread Thread { get; private set; }
|
|
||||||
public MachineEvents Events { get; private set; }
|
public MachineEvents Events { get; private set; }
|
||||||
public MachineServices Services { get; private set; }
|
public MachineServices Services { get; private set; }
|
||||||
public MachineSettings Settings { 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 Cpu Cpu { get; private set; }
|
||||||
public Memory Memory { get; private set; }
|
public Memory Memory { get; private set; }
|
||||||
public DiskII DiskII { get; private set; }
|
public DiskII DiskII { get; private set; }
|
||||||
|
@ -73,8 +111,13 @@ private void Run()
|
||||||
public Cassette Cassette { get; private set; }
|
public Cassette Cassette { get; private set; }
|
||||||
public Speaker Speaker { get; private set; }
|
public Speaker Speaker { get; private set; }
|
||||||
public Video Video { 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 StorageService _storageService;
|
||||||
private bool _stopPending;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public class MachineEvent
|
public sealed class MachineEvent
|
||||||
{
|
{
|
||||||
public MachineEvent(int delta, Action action)
|
public MachineEvent(int delta, Action action)
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,7 @@ public override string ToString()
|
||||||
public Action Action { get; set; }
|
public Action Action { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MachineEvents
|
public sealed class MachineEvents
|
||||||
{
|
{
|
||||||
public void AddEvent(int delta, Action action)
|
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")]
|
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
|
||||||
|
@ -86,17 +86,23 @@ public void RaiseEvents(int delta)
|
||||||
|
|
||||||
while (node.Value.Delta <= 0)
|
while (node.Value.Delta <= 0)
|
||||||
{
|
{
|
||||||
delta = node.Value.Delta;
|
|
||||||
node.Value.Action();
|
node.Value.Action();
|
||||||
|
RemoveEvent(node);
|
||||||
_used.Remove(node);
|
|
||||||
_free.AddFirst(node); // cache node; avoids garbage
|
|
||||||
|
|
||||||
node = _used.First;
|
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> _used = new LinkedList<MachineEvent>();
|
||||||
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Settings
|
namespace Jellyfish.Virtu.Settings
|
||||||
{
|
{
|
||||||
public class MachineSettings
|
public sealed class MachineSettings
|
||||||
{
|
{
|
||||||
public MachineSettings()
|
public MachineSettings()
|
||||||
{
|
{
|
||||||
|
@ -21,8 +21,8 @@ public MachineSettings()
|
||||||
UseGamePort = false,
|
UseGamePort = false,
|
||||||
Key = new KeySettings
|
Key = new KeySettings
|
||||||
{
|
{
|
||||||
Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', 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 = '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
|
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 const string Namespace = "http://schemas.jellyfish.co.nz/virtu/settings";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CpuSettings
|
public sealed class CpuSettings
|
||||||
{
|
{
|
||||||
public bool Is65C02 { get; set; }
|
public bool Is65C02 { get; set; }
|
||||||
public bool IsThrottled { get; set; }
|
public bool IsThrottled { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DiskSettings
|
public sealed class DiskSettings
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public bool IsWriteProtected { get; set; }
|
public bool IsWriteProtected { get; set; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public class DiskIISettings
|
public sealed class DiskIISettings
|
||||||
{
|
{
|
||||||
public DiskSettings Disk1 { get; set; }
|
public DiskSettings Disk1 { get; set; }
|
||||||
public DiskSettings Disk2 { get; set; }
|
public DiskSettings Disk2 { get; set; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public class JoystickSettings
|
public sealed class JoystickSettings
|
||||||
{
|
{
|
||||||
public int UpLeft { get; set; }
|
public int UpLeft { get; set; }
|
||||||
public int Up { get; set; }
|
public int Up { get; set; }
|
||||||
|
@ -335,7 +335,7 @@ public class JoystickSettings
|
||||||
public int DownRight { get; set; }
|
public int DownRight { get; set; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public class KeySettings
|
public sealed class KeySettings
|
||||||
{
|
{
|
||||||
public JoystickSettings Joystick0 { get; set; }
|
public JoystickSettings Joystick0 { get; set; }
|
||||||
public JoystickSettings Joystick1 { get; set; }
|
public JoystickSettings Joystick1 { get; set; }
|
||||||
|
@ -344,19 +344,19 @@ public class KeySettings
|
||||||
public int Button2 { get; set; }
|
public int Button2 { get; set; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public class KeyboardSettings
|
public sealed class KeyboardSettings
|
||||||
{
|
{
|
||||||
public bool UseGamePort { get; set; }
|
public bool UseGamePort { get; set; }
|
||||||
public KeySettings Key { get; set; }
|
public KeySettings Key { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GamePortSettings
|
public sealed class GamePortSettings
|
||||||
{
|
{
|
||||||
public bool UseKeyboard { get; set; }
|
public bool UseKeyboard { get; set; }
|
||||||
public KeySettings Key { get; set; }
|
public KeySettings Key { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ColorSettings
|
public sealed class ColorSettings
|
||||||
{
|
{
|
||||||
public uint Black { get; set; }
|
public uint Black { get; set; }
|
||||||
public uint DarkBlue { get; set; }
|
public uint DarkBlue { get; set; }
|
||||||
|
@ -377,7 +377,7 @@ public class ColorSettings
|
||||||
public uint Monochrome { get; set; }
|
public uint Monochrome { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VideoSettings
|
public sealed class VideoSettings
|
||||||
{
|
{
|
||||||
public bool IsFullScreen { get; set; }
|
public bool IsFullScreen { get; set; }
|
||||||
public bool IsMonochrome { 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>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// 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
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// the code is regenerated.
|
// 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;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
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;
|
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
|
Paddle0 = Paddle1 = Paddle2 = Paddle3 = 255; // not connected
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public class AsciiKeyEventArgs : EventArgs
|
public sealed class AsciiKeyEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
private AsciiKeyEventArgs()
|
private AsciiKeyEventArgs()
|
||||||
{
|
{
|
||||||
|
@ -22,8 +22,13 @@ public static AsciiKeyEventArgs Create(int asciiKey)
|
||||||
private static readonly AsciiKeyEventArgs _instance = new AsciiKeyEventArgs();
|
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);
|
public abstract bool IsKeyDown(int key);
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
|
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
|
||||||
|
@ -42,7 +47,7 @@ public void WaitForKeyUp()
|
||||||
{
|
{
|
||||||
while (IsAnyKeyDown)
|
while (IsAnyKeyDown)
|
||||||
{
|
{
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +55,7 @@ public void WaitForResetKeyUp()
|
||||||
{
|
{
|
||||||
while (IsResetKeyDown)
|
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
|
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))
|
if (_serviceProviders.ContainsKey(serviceType))
|
||||||
{
|
{
|
||||||
|
@ -37,6 +37,6 @@ public void RemoveService(Type serviceType)
|
||||||
_serviceProviders.Remove(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;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public abstract class StorageService
|
public abstract class StorageService : MachineService
|
||||||
{
|
{
|
||||||
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
|
protected StorageService(Machine machine) :
|
||||||
public abstract string GetDiskFile();
|
base(machine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void Load(string path, Action<Stream> reader);
|
public abstract void Load(string path, Action<Stream> reader);
|
||||||
public abstract void Save(string path, Action<Stream> writer);
|
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 SetPixel(int x, int y, uint color);
|
||||||
public abstract void Update();
|
public abstract void Update();
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Net" />
|
<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.Xml" />
|
||||||
<Reference Include="System.Windows.Browser" />
|
<Reference Include="System.Windows.Browser" />
|
||||||
<Reference Include="System.Xml.Linq, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
|
<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">
|
<Compile Include="..\Services\KeyboardService.cs">
|
||||||
<Link>Services\KeyboardService.cs</Link>
|
<Link>Services\KeyboardService.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Services\MachineService.cs">
|
||||||
|
<Link>Services\MachineService.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Services\MachineServices.cs">
|
<Compile Include="..\Services\MachineServices.cs">
|
||||||
<Link>Services\MachineServices.cs</Link>
|
<Link>Services\MachineServices.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -155,6 +159,7 @@
|
||||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Services\SilverlightAudioService.cs" />
|
||||||
<Compile Include="Services\SilverlightKeyboardService.cs" />
|
<Compile Include="Services\SilverlightKeyboardService.cs" />
|
||||||
<Compile Include="Services\SilverlightStorageService.cs" />
|
<Compile Include="Services\SilverlightStorageService.cs" />
|
||||||
<Compile Include="Services\SilverlightVideoService.cs" />
|
<Compile Include="Services\SilverlightVideoService.cs" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public partial class MainApp : ApplicationBase
|
public sealed partial class MainApp : ApplicationBase
|
||||||
{
|
{
|
||||||
public MainApp() :
|
public MainApp() :
|
||||||
base("Virtu")
|
base("Virtu")
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
<UserControl x:Class="Jellyfish.Virtu.MainPage"
|
<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: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:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
|
||||||
xmlns:jv="clr-namespace:Jellyfish.Virtu;assembly=Jellyfish.Virtu">
|
xmlns:jv="clr-namespace:Jellyfish.Virtu;assembly=Jellyfish.Virtu">
|
||||||
<Grid Background="Black" Cursor="None">
|
<tk:DockPanel Background="Black">
|
||||||
<Image Name="_image" MinWidth="560" MinHeight="384"/>
|
<StackPanel Orientation="Horizontal" tk:DockPanel.Dock="Top">
|
||||||
|
<Button x:Name="_disk1Button" Content="Disk 1" IsTabStop="false" Margin="4 4 0 4"/>
|
||||||
<jl:FrameRateCounter/>
|
<Button x:Name="_disk2Button" Content="Disk 2" IsTabStop="false" Margin="4 4 0 4"/>
|
||||||
</Grid>
|
<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>
|
</UserControl>
|
||||||
|
|
|
@ -1,60 +1,78 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Jellyfish.Virtu.Services;
|
using Jellyfish.Virtu.Services;
|
||||||
using System.Windows.Input;
|
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public partial class MainPage : UserControl
|
public sealed partial class MainPage : UserControl, IDisposable
|
||||||
{
|
{
|
||||||
public MainPage()
|
public MainPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_storageService = new SilverlightStorageService(this);
|
_storageService = new SilverlightStorageService(_machine);
|
||||||
_keyboardService = new SilverlightKeyboardService(this);
|
_keyboardService = new SilverlightKeyboardService(_machine, this);
|
||||||
_gamePortService = new GamePortService(); // not connected
|
_gamePortService = new GamePortService(_machine); // not connected
|
||||||
_audioService = new AudioService(); // not connected
|
_audioService = new SilverlightAudioService(_machine, this, _media);
|
||||||
_videoService = new SilverlightVideoService(_image);
|
_videoService = new SilverlightVideoService(_machine, _image);
|
||||||
|
|
||||||
_machine = new Machine();
|
|
||||||
_machine.Services.AddService(typeof(StorageService), _storageService);
|
_machine.Services.AddService(typeof(StorageService), _storageService);
|
||||||
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
||||||
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
||||||
_machine.Services.AddService(typeof(AudioService), _audioService);
|
_machine.Services.AddService(typeof(AudioService), _audioService);
|
||||||
_machine.Services.AddService(typeof(VideoService), _videoService);
|
_machine.Services.AddService(typeof(VideoService), _videoService);
|
||||||
|
|
||||||
Loaded += MainPage_Loaded;
|
Loaded += (sender, e) => _machine.Start();
|
||||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
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)
|
private void CompositionTarget_Rendering(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_keyboardService.Update();
|
_keyboardService.Update();
|
||||||
_gamePortService.Update();
|
_gamePortService.Update();
|
||||||
_audioService.Update();
|
|
||||||
_videoService.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 StorageService _storageService;
|
||||||
private KeyboardService _keyboardService;
|
private KeyboardService _keyboardService;
|
||||||
private GamePortService _gamePortService;
|
private GamePortService _gamePortService;
|
||||||
private AudioService _audioService;
|
private AudioService _audioService;
|
||||||
private VideoService _videoService;
|
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.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Browser;
|
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
@ -10,13 +9,14 @@ namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class SilverlightKeyboardService : KeyboardService
|
public sealed class SilverlightKeyboardService : KeyboardService
|
||||||
{
|
{
|
||||||
public SilverlightKeyboardService(UserControl page)
|
public SilverlightKeyboardService(Machine machine, UserControl page) :
|
||||||
|
base(machine)
|
||||||
{
|
{
|
||||||
_page = page;
|
_page = page;
|
||||||
|
|
||||||
_page.LostFocus += Page_LostFocus;
|
|
||||||
_page.KeyDown += Page_KeyDown;
|
_page.KeyDown += Page_KeyDown;
|
||||||
_page.KeyUp += Page_KeyUp;
|
_page.KeyUp += Page_KeyUp;
|
||||||
|
_page.LostFocus += Page_LostFocus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsKeyDown(int key)
|
public override bool IsKeyDown(int key)
|
||||||
|
@ -57,15 +57,6 @@ private bool IsKeyDown(Key key)
|
||||||
return _states[(int)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)
|
private void Page_KeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
_states[(int)e.Key] = true;
|
_states[(int)e.Key] = true;
|
||||||
|
@ -86,6 +77,15 @@ private void Page_KeyUp(object sender, KeyEventArgs e)
|
||||||
_updateAnyKeyDown = true;
|
_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", "CA1502:AvoidExcessiveComplexity")]
|
||||||
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")]
|
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")]
|
||||||
private int GetAsciiKey(Key key, int platformKeyCode)
|
private int GetAsciiKey(Key key, int platformKeyCode)
|
||||||
|
|
|
@ -1,45 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.IsolatedStorage;
|
using System.IO.IsolatedStorage;
|
||||||
using System.Threading;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class SilverlightStorageService : StorageService
|
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)
|
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;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class SilverlightVideoService : VideoService
|
public sealed class SilverlightVideoService : VideoService
|
||||||
{
|
{
|
||||||
public SilverlightVideoService(Image image)
|
public SilverlightVideoService(Machine machine, Image image) :
|
||||||
|
base(machine)
|
||||||
{
|
{
|
||||||
_image = image;
|
_image = image;
|
||||||
SetImageSize();
|
|
||||||
|
|
||||||
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight);
|
|
||||||
_pixels = new int[BitmapWidth * BitmapHeight];
|
|
||||||
_image.Source = _bitmap;
|
_image.Source = _bitmap;
|
||||||
|
SetImageSize();
|
||||||
|
|
||||||
Application.Current.Host.Content.Resized += (sender, e) => SetImageSize();
|
Application.Current.Host.Content.Resized += (sender, e) => SetImageSize();
|
||||||
}
|
}
|
||||||
|
@ -73,8 +70,8 @@ private void SetImageSize()
|
||||||
private const int BitmapHeight = 384;
|
private const int BitmapHeight = 384;
|
||||||
|
|
||||||
private Image _image;
|
private Image _image;
|
||||||
private WriteableBitmap _bitmap;
|
private WriteableBitmap _bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight);
|
||||||
private int[] _pixels;
|
private int[] _pixels = new int[BitmapWidth * BitmapHeight];
|
||||||
private bool _pixelsDirty;
|
private bool _pixelsDirty;
|
||||||
private bool _isFullScreen;
|
private bool _isFullScreen;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ public override void Initialize()
|
||||||
|
|
||||||
public void ToggleOutput()
|
public void ToggleOutput()
|
||||||
{
|
{
|
||||||
// TODO
|
_audioService.ToggleOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AudioService _audioService;
|
private AudioService _audioService;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using Jellyfish.Virtu.Properties;
|
|
||||||
using Jellyfish.Virtu.Services;
|
using Jellyfish.Virtu.Services;
|
||||||
using Jellyfish.Virtu.Settings;
|
using Jellyfish.Virtu.Settings;
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,12 @@
|
||||||
</NoWarn>
|
</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="Microsoft.Xna.Framework, Version=3.1.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=x86" />
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core">
|
<Reference Include="System.Core">
|
||||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="System.Data" />
|
||||||
<Reference Include="System.Deployment" />
|
<Reference Include="System.Deployment" />
|
||||||
<Reference Include="System.Xml.Linq">
|
<Reference Include="System.Xml.Linq">
|
||||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||||
|
@ -128,6 +130,9 @@
|
||||||
<Compile Include="..\MachineEvents.cs">
|
<Compile Include="..\MachineEvents.cs">
|
||||||
<Link>Core\MachineEvents.cs</Link>
|
<Link>Core\MachineEvents.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Services\MachineService.cs">
|
||||||
|
<Link>Services\MachineService.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Services\MachineServices.cs">
|
<Compile Include="..\Services\MachineServices.cs">
|
||||||
<Link>Services\MachineServices.cs</Link>
|
<Link>Services\MachineServices.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -161,9 +166,13 @@
|
||||||
<Compile Include="..\Services\VideoService.cs">
|
<Compile Include="..\Services\VideoService.cs">
|
||||||
<Link>Services\VideoService.cs</Link>
|
<Link>Services\VideoService.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Xna\Services\XnaGamePortService.cs">
|
||||||
|
<Link>Services\XnaGamePortService.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs">
|
<Compile Include="Properties\AssemblyInfo.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Services\WpfAudioService.cs" />
|
||||||
<Compile Include="Services\WpfKeyboardService.cs" />
|
<Compile Include="Services\WpfKeyboardService.cs" />
|
||||||
<Compile Include="Services\WpfStorageService.cs" />
|
<Compile Include="Services\WpfStorageService.cs" />
|
||||||
<Compile Include="Services\WpfVideoService.cs" />
|
<Compile Include="Services\WpfVideoService.cs" />
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using Jellyfish.Library;
|
using System.Security.Permissions;
|
||||||
|
using Jellyfish.Library;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public partial class MainApp : ApplicationBase
|
public sealed partial class MainApp : ApplicationBase
|
||||||
{
|
{
|
||||||
|
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
|
||||||
public MainApp() :
|
public MainApp() :
|
||||||
base("Virtu")
|
base("Virtu")
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,9 +3,14 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
|
xmlns:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
|
||||||
Title="Virtu" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
|
Title="Virtu" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
|
||||||
<Grid Background="Black" Cursor="None">
|
<DockPanel Background="Black">
|
||||||
<Image Name="_image" MinWidth="560" MinHeight="384" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
|
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
|
||||||
|
<Button x:Name="_disk1Button" Content="Disk 1" Focusable="false" IsTabStop="false" Margin="4 4 0 4"/>
|
||||||
<jl:FrameRateCounter/>
|
<Button x:Name="_disk2Button" Content="Disk 2" Focusable="false" IsTabStop="false" Margin="4 4 0 4"/>
|
||||||
</Grid>
|
<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>
|
</Window>
|
||||||
|
|
|
@ -1,58 +1,78 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Jellyfish.Virtu.Services;
|
using Jellyfish.Virtu.Services;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
public partial class MainWindow : Window
|
public sealed partial class MainWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_storageService = new WpfStorageService();
|
_storageService = new WpfStorageService(_machine);
|
||||||
_keyboardService = new WpfKeyboardService(this);
|
_keyboardService = new WpfKeyboardService(_machine, this);
|
||||||
_gamePortService = new GamePortService(); // not connected
|
_gamePortService = new XnaGamePortService(_machine);
|
||||||
_audioService = new AudioService(); // not connected
|
_audioService = new WpfAudioService(_machine, this);
|
||||||
_videoService = new WpfVideoService(this, _image);
|
_videoService = new WpfVideoService(_machine, this, _image);
|
||||||
|
|
||||||
_machine = new Machine();
|
|
||||||
_machine.Services.AddService(typeof(StorageService), _storageService);
|
_machine.Services.AddService(typeof(StorageService), _storageService);
|
||||||
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
||||||
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
||||||
_machine.Services.AddService(typeof(AudioService), _audioService);
|
_machine.Services.AddService(typeof(AudioService), _audioService);
|
||||||
_machine.Services.AddService(typeof(VideoService), _videoService);
|
_machine.Services.AddService(typeof(VideoService), _videoService);
|
||||||
|
|
||||||
Loaded += MainWindow_Loaded;
|
Loaded += (sender, e) => _machine.Start();
|
||||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
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)
|
private void CompositionTarget_Rendering(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
_keyboardService.Update();
|
_keyboardService.Update();
|
||||||
_gamePortService.Update();
|
_gamePortService.Update();
|
||||||
_audioService.Update();
|
|
||||||
_videoService.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 StorageService _storageService;
|
||||||
private KeyboardService _keyboardService;
|
private KeyboardService _keyboardService;
|
||||||
private GamePortService _gamePortService;
|
private GamePortService _gamePortService;
|
||||||
private AudioService _audioService;
|
private AudioService _audioService;
|
||||||
private VideoService _videoService;
|
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 sealed class WpfKeyboardService : KeyboardService
|
||||||
{
|
{
|
||||||
public WpfKeyboardService(Window window)
|
public WpfKeyboardService(Machine machine, Window window) :
|
||||||
|
base(machine)
|
||||||
{
|
{
|
||||||
_window = window;
|
_window = window;
|
||||||
|
|
||||||
_window.GotKeyboardFocus += Window_GotKeyboardFocus;
|
|
||||||
_window.KeyDown += Window_KeyDown;
|
_window.KeyDown += Window_KeyDown;
|
||||||
_window.KeyUp += Window_KeyUp;
|
_window.KeyUp += Window_KeyUp;
|
||||||
|
_window.GotKeyboardFocus += (sender, e) => _updateAnyKeyDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsKeyDown(int key)
|
public override bool IsKeyDown(int key)
|
||||||
|
@ -55,11 +56,6 @@ private bool IsKeyDown(Key key)
|
||||||
return _states[(int)key];
|
return _states[(int)key];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Window_GotKeyboardFocus(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_updateAnyKeyDown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
_states[(int)((e.Key == Key.System) ? e.SystemKey : e.Key)] = true;
|
_states[(int)((e.Key == Key.System) ? e.SystemKey : e.Key)] = true;
|
||||||
|
|
|
@ -2,29 +2,14 @@
|
||||||
using System.Deployment.Application;
|
using System.Deployment.Application;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.IsolatedStorage;
|
using System.IO.IsolatedStorage;
|
||||||
using System.Windows;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class WpfStorageService : StorageService
|
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)
|
public override void Load(string path, Action<Stream> reader)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Security.Permissions;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
@ -10,15 +11,14 @@ namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class WpfVideoService : VideoService
|
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;
|
_window = window;
|
||||||
_image = image;
|
_image = image;
|
||||||
SetImageSize();
|
|
||||||
|
|
||||||
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapDpi, BitmapDpi, BitmapPixelFormat, null);
|
|
||||||
_pixels = new uint[BitmapWidth * BitmapHeight];
|
|
||||||
_image.Source = _bitmap;
|
_image.Source = _bitmap;
|
||||||
|
SetImageSize();
|
||||||
|
|
||||||
SystemEvents.DisplaySettingsChanged += (sender, e) => SetImageSize();
|
SystemEvents.DisplaySettingsChanged += (sender, e) => SetImageSize();
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,8 @@ private void SetImageSize()
|
||||||
|
|
||||||
private Window _window;
|
private Window _window;
|
||||||
private Image _image;
|
private Image _image;
|
||||||
private WriteableBitmap _bitmap;
|
private WriteableBitmap _bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapDpi, BitmapDpi, BitmapPixelFormat, null);
|
||||||
private uint[] _pixels;
|
private uint[] _pixels = new uint[BitmapWidth * BitmapHeight];
|
||||||
private bool _pixelsDirty;
|
private bool _pixelsDirty;
|
||||||
private bool _isFullScreen;
|
private bool _isFullScreen;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,9 @@
|
||||||
<Compile Include="..\Services\KeyboardService.cs">
|
<Compile Include="..\Services\KeyboardService.cs">
|
||||||
<Link>Services\KeyboardService.cs</Link>
|
<Link>Services\KeyboardService.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Services\MachineService.cs">
|
||||||
|
<Link>Services\MachineService.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Services\MachineServices.cs">
|
<Compile Include="..\Services\MachineServices.cs">
|
||||||
<Link>Services\MachineServices.cs</Link>
|
<Link>Services\MachineServices.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -149,6 +152,7 @@
|
||||||
<Compile Include="MainApp.cs" />
|
<Compile Include="MainApp.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="MainGame.cs" />
|
<Compile Include="MainGame.cs" />
|
||||||
|
<Compile Include="Services\XnaAudioService.cs" />
|
||||||
<Compile Include="Services\XnaGamePortService.cs" />
|
<Compile Include="Services\XnaGamePortService.cs" />
|
||||||
<Compile Include="Services\XnaKeyboardService.cs" />
|
<Compile Include="Services\XnaKeyboardService.cs" />
|
||||||
<Compile Include="Services\XnaStorageService.cs" />
|
<Compile Include="Services\XnaStorageService.cs" />
|
||||||
|
|
|
@ -149,6 +149,9 @@
|
||||||
<Compile Include="..\Services\KeyboardService.cs">
|
<Compile Include="..\Services\KeyboardService.cs">
|
||||||
<Link>Services\KeyboardService.cs</Link>
|
<Link>Services\KeyboardService.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Services\MachineService.cs">
|
||||||
|
<Link>Services\MachineService.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\Services\MachineServices.cs">
|
<Compile Include="..\Services\MachineServices.cs">
|
||||||
<Link>Services\MachineServices.cs</Link>
|
<Link>Services\MachineServices.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -170,6 +173,7 @@
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="MainApp.cs" />
|
<Compile Include="MainApp.cs" />
|
||||||
<Compile Include="MainGame.cs" />
|
<Compile Include="MainGame.cs" />
|
||||||
|
<Compile Include="Services\XnaAudioService.cs" />
|
||||||
<Compile Include="Services\XnaGamePortService.cs" />
|
<Compile Include="Services\XnaGamePortService.cs" />
|
||||||
<Compile Include="Services\XnaKeyboardService.cs" />
|
<Compile Include="Services\XnaKeyboardService.cs" />
|
||||||
<Compile Include="Services\XnaStorageService.cs" />
|
<Compile Include="Services\XnaStorageService.cs" />
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Jellyfish.Virtu
|
namespace Jellyfish.Virtu
|
||||||
{
|
{
|
||||||
static class MainApp
|
static class MainApp
|
||||||
|
|
|
@ -18,13 +18,16 @@ public MainGame() :
|
||||||
GraphicsDeviceManager.PreferredBackBufferWidth = 560;
|
GraphicsDeviceManager.PreferredBackBufferWidth = 560;
|
||||||
GraphicsDeviceManager.PreferredBackBufferHeight = 384;
|
GraphicsDeviceManager.PreferredBackBufferHeight = 384;
|
||||||
#endif
|
#endif
|
||||||
_storageService = new XnaStorageService(this);
|
_storageService = new XnaStorageService(_machine, this);
|
||||||
_keyboardService = new XnaKeyboardService();
|
_keyboardService = new XnaKeyboardService(_machine);
|
||||||
_gamePortService = new XnaGamePortService();
|
_gamePortService = new XnaGamePortService(_machine);
|
||||||
_audioService = new AudioService(); // not connected
|
#if XBOX
|
||||||
_videoService = new XnaVideoService(this);
|
_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(StorageService), _storageService);
|
||||||
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
|
||||||
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
|
||||||
|
@ -32,6 +35,20 @@ public MainGame() :
|
||||||
_machine.Services.AddService(typeof(VideoService), _videoService);
|
_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()
|
protected override void BeginRun()
|
||||||
{
|
{
|
||||||
_machine.Start();
|
_machine.Start();
|
||||||
|
@ -41,7 +58,6 @@ protected override void Update(GameTime gameTime)
|
||||||
{
|
{
|
||||||
_keyboardService.Update();
|
_keyboardService.Update();
|
||||||
_gamePortService.Update();
|
_gamePortService.Update();
|
||||||
_audioService.Update();
|
|
||||||
base.Update(gameTime);
|
base.Update(gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,12 +73,12 @@ protected override void EndRun()
|
||||||
_machine.Stop();
|
_machine.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Machine _machine = new Machine();
|
||||||
|
|
||||||
private StorageService _storageService;
|
private StorageService _storageService;
|
||||||
private KeyboardService _keyboardService;
|
private KeyboardService _keyboardService;
|
||||||
private GamePortService _gamePortService;
|
private GamePortService _gamePortService;
|
||||||
private AudioService _audioService;
|
private AudioService _audioService;
|
||||||
private VideoService _videoService;
|
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;
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
|
||||||
namespace Jellyfish.Virtu.Services
|
namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class XnaGamePortService : GamePortService
|
public sealed class XnaGamePortService : GamePortService
|
||||||
{
|
{
|
||||||
|
public XnaGamePortService(Machine machine) :
|
||||||
|
base(machine)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
_lastState = _state;
|
_lastState = _state;
|
||||||
|
|
|
@ -8,17 +8,11 @@ namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class XnaKeyboardService : KeyboardService
|
public sealed class XnaKeyboardService : KeyboardService
|
||||||
{
|
{
|
||||||
public XnaKeyboardService() :
|
public XnaKeyboardService(Machine machine) :
|
||||||
this(TimeSpan.FromMilliseconds(500).Ticks, TimeSpan.FromMilliseconds(32).Ticks)
|
base(machine)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public XnaKeyboardService(long repeatDelay, long repeatSpeed)
|
|
||||||
{
|
|
||||||
_repeatDelay = repeatDelay;
|
|
||||||
_repeatSpeed = repeatSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsKeyDown(int key)
|
public override bool IsKeyDown(int key)
|
||||||
{
|
{
|
||||||
return IsKeyDown((Keys)key);
|
return IsKeyDown((Keys)key);
|
||||||
|
@ -46,7 +40,7 @@ public override void Update()
|
||||||
|
|
||||||
_lastKey = key;
|
_lastKey = key;
|
||||||
_lastTime = DateTime.UtcNow.Ticks;
|
_lastTime = DateTime.UtcNow.Ticks;
|
||||||
_repeatTime = _repeatDelay;
|
_repeatTime = RepeatDelay;
|
||||||
#if XBOX
|
#if XBOX
|
||||||
int asciiKey = GetAsciiKey(key, ref gamePadState);
|
int asciiKey = GetAsciiKey(key, ref gamePadState);
|
||||||
#else
|
#else
|
||||||
|
@ -71,7 +65,7 @@ public override void Update()
|
||||||
if (time - _lastTime >= _repeatTime)
|
if (time - _lastTime >= _repeatTime)
|
||||||
{
|
{
|
||||||
_lastTime = time;
|
_lastTime = time;
|
||||||
_repeatTime = _repeatSpeed;
|
_repeatTime = RepeatSpeed;
|
||||||
#if XBOX
|
#if XBOX
|
||||||
int asciiKey = GetAsciiKey(_lastKey, ref gamePadState);
|
int asciiKey = GetAsciiKey(_lastKey, ref gamePadState);
|
||||||
#else
|
#else
|
||||||
|
@ -356,6 +350,8 @@ where field.IsLiteral
|
||||||
where (key != Keys.None) // filter Keys.None
|
where (key != Keys.None) // filter Keys.None
|
||||||
select key).ToArray();
|
select key).ToArray();
|
||||||
#endif
|
#endif
|
||||||
|
private static readonly long RepeatDelay = TimeSpan.FromMilliseconds(500).Ticks;
|
||||||
|
private static readonly long RepeatSpeed = TimeSpan.FromMilliseconds(32).Ticks;
|
||||||
|
|
||||||
private KeyboardState _state;
|
private KeyboardState _state;
|
||||||
private KeyboardState _lastState;
|
private KeyboardState _lastState;
|
||||||
|
@ -363,7 +359,5 @@ where field.IsLiteral
|
||||||
private Keys _lastKey;
|
private Keys _lastKey;
|
||||||
private long _lastTime;
|
private long _lastTime;
|
||||||
private long _repeatTime;
|
private long _repeatTime;
|
||||||
private long _repeatDelay;
|
|
||||||
private long _repeatSpeed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,10 @@ namespace Jellyfish.Virtu.Services
|
||||||
{
|
{
|
||||||
public sealed class XnaStorageService : StorageService
|
public sealed class XnaStorageService : StorageService
|
||||||
{
|
{
|
||||||
public XnaStorageService(GameBase game)
|
public XnaStorageService(Machine machine, GameBase game) :
|
||||||
|
base(machine)
|
||||||
{
|
{
|
||||||
_game = game;
|
_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)
|
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 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
|
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 = game;
|
||||||
|
|
||||||
_game.GraphicsDeviceManager.PreparingDeviceSettings += GraphicsDeviceManager_PreparingDeviceSettings;
|
_game.GraphicsDeviceManager.PreparingDeviceSettings += GraphicsDeviceManager_PreparingDeviceSettings;
|
||||||
_game.GraphicsDeviceService.DeviceCreated += GraphicsDeviceService_DeviceCreated;
|
_game.GraphicsDeviceService.DeviceCreated += GraphicsDeviceService_DeviceCreated;
|
||||||
_game.GraphicsDeviceService.DeviceReset += GraphicsDeviceService_DeviceReset;
|
_game.GraphicsDeviceService.DeviceReset += (sender, e) => SetTexturePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "y*560")]
|
[SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "y*560")]
|
||||||
|
@ -53,10 +54,13 @@ public override void Update()
|
||||||
_spriteBatch.End();
|
_spriteBatch.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
_spriteBatch.Dispose();
|
if (disposing)
|
||||||
_texture.Dispose();
|
{
|
||||||
|
_spriteBatch.Dispose();
|
||||||
|
_texture.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GraphicsDeviceManager_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
|
private void GraphicsDeviceManager_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
|
||||||
|
@ -83,11 +87,6 @@ private void GraphicsDeviceService_DeviceCreated(object sender, EventArgs e)
|
||||||
SetTexturePosition();
|
SetTexturePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GraphicsDeviceService_DeviceReset(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
SetTexturePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetTexturePosition()
|
private void SetTexturePosition()
|
||||||
{
|
{
|
||||||
_texturePosition.X = (_graphicsDevice.PresentationParameters.BackBufferWidth - TextureWidth * _textureScale) / 2f; // centered
|
_texturePosition.X = (_graphicsDevice.PresentationParameters.BackBufferWidth - TextureWidth * _textureScale) / 2f; // centered
|
||||||
|
|
Loading…
Reference in New Issue
Block a user