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:
Sean Fausett 2009-07-26 23:22:00 +00:00
parent 7c2048f0c3
commit 8382195feb
68 changed files with 1010 additions and 339 deletions

189
Library/DirectSound.cs Normal file
View 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);
}
}

View 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);
}
}
}

View File

@ -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
}
}
}

View 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();
}
}
}
}
}

View File

@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Permissions;
using System.Security.Principal;
namespace Jellyfish.Library
@ -9,6 +10,7 @@ namespace Jellyfish.Library
[StructLayout(LayoutKind.Sequential)]
public sealed class SecurityAttributes
{
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public SecurityAttributes()
{
_length = Marshal.SizeOf(typeof(SecurityAttributes));
@ -116,31 +118,21 @@ public void AddAuditRule(GeneralAuditRule rule)
base.AddAuditRule(rule);
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public void GetSecurityAttributes(bool inheritable, Action<SecurityAttributes> action)
{
GetSecurityAttributes(this, inheritable, action);
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public static void GetSecurityAttributes(ObjectSecurity security, bool inheritable, Action<SecurityAttributes> action)
{
if (security != null)
{
GCHandle gcHandle = new GCHandle();
try
GCHandleHelpers.Pin(security.GetSecurityDescriptorBinaryForm(), securityDescriptor =>
{
gcHandle = GCHandle.Alloc(security.GetSecurityDescriptorBinaryForm(), GCHandleType.Pinned);
SecurityAttributes securityAttributes = new SecurityAttributes();
securityAttributes.SecurityDescriptor = gcHandle.AddrOfPinnedObject();
securityAttributes.InheritHandle = inheritable;
action(securityAttributes);
}
finally
{
if (gcHandle.IsAllocated)
{
gcHandle.Free();
}
}
action(new SecurityAttributes() { SecurityDescriptor = securityDescriptor, InheritHandle = inheritable });
});
}
else if (inheritable)
{

View File

@ -3,7 +3,7 @@
namespace Jellyfish.Library
{
public class Lazy<T> where T : class
public sealed class Lazy<T> where T : class
{
public Lazy(Func<T> initializer)
{

31
Library/MarshalHelpers.cs Normal file
View 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);
}
}
}

View File

@ -6,11 +6,6 @@ namespace Jellyfish.Library
{
public class ApplicationBase : Application
{
public ApplicationBase() :
this(null)
{
}
public ApplicationBase(string name)
{
Name = name;

View File

@ -5,7 +5,7 @@
namespace Jellyfish.Library
{
public partial class FrameRateCounter : UserControl
public sealed partial class FrameRateCounter : UserControl
{
public FrameRateCounter()
{

View File

@ -54,9 +54,6 @@
<Compile Include="..\AssemblyCommentAttribute.cs">
<Link>AssemblyCommentAttribute.cs</Link>
</Compile>
<Compile Include="..\FileHelpers.cs">
<Link>FileHelpers.cs</Link>
</Compile>
<Compile Include="..\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>

View File

@ -5,7 +5,7 @@
namespace Jellyfish.Library
{
public class StringFormatConverter : IValueConverter // SL is missing Binding.StringFormat
public sealed class StringFormatConverter : IValueConverter // SL is missing Binding.StringFormat
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{

View File

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Security.Permissions;
using System.Text;
using System.Windows;
using System.Windows.Threading;
@ -8,11 +9,7 @@ namespace Jellyfish.Library
{
public class ApplicationBase : Application
{
public ApplicationBase() :
this(null)
{
}
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public ApplicationBase(string name)
{
Name = name;

View File

@ -5,7 +5,7 @@
namespace Jellyfish.Library
{
public partial class FrameRateCounter : UserControl
public sealed partial class FrameRateCounter : UserControl
{
public FrameRateCounter()
{

View File

@ -62,8 +62,14 @@
<Compile Include="..\AssemblyCommentAttribute.cs">
<Link>AssemblyCommentAttribute.cs</Link>
</Compile>
<Compile Include="..\FileHelpers.cs">
<Link>FileHelpers.cs</Link>
<Compile Include="..\DirectSound.cs">
<Link>DirectSound.cs</Link>
</Compile>
<Compile Include="..\DirectSoundInterop.cs">
<Link>DirectSoundInterop.cs</Link>
</Compile>
<Compile Include="..\GCHandleHelpers.cs">
<Link>GCHandleHelpers.cs</Link>
</Compile>
<Compile Include="..\GeneralSecurity.cs">
<Link>GeneralSecurity.cs</Link>
@ -77,6 +83,9 @@
<Compile Include="..\Lazy.cs">
<Link>Lazy.cs</Link>
</Compile>
<Compile Include="..\MarshalHelpers.cs">
<Link>MarshalHelpers.cs</Link>
</Compile>
<Compile Include="..\MathHelpers.cs">
<Link>MathHelpers.cs</Link>
</Compile>
@ -100,6 +109,7 @@
<DependentUpon>FrameRateCounter.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WindowExtensions.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="FrameRateCounter.xaml">

View 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;
}
}
}

View File

@ -5,13 +5,11 @@
namespace Jellyfish.Library
{
public class FrameRateCounter : DrawableGameComponent
public sealed class FrameRateCounter : DrawableGameComponent
{
public FrameRateCounter(GameBase game) :
base(game)
{
_frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage
FontColor = Color.White;
FontName = "Default";
@ -68,6 +66,6 @@ public override void Update(GameTime gameTime)
private long _elapsedTime;
private int _frameCount;
private int _frameRate;
private StringBuilder _frameRateBuilder;
private StringBuilder _frameRateBuilder = new StringBuilder(); // cache builder; avoids garbage
}
}

View File

@ -7,11 +7,6 @@ namespace Jellyfish.Library
{
public class GameBase : Game
{
public GameBase() :
this(null)
{
}
public GameBase(string name)
{
Name = name;

View File

@ -62,9 +62,6 @@
<Compile Include="..\AssemblyCommentAttribute.cs">
<Link>AssemblyCommentAttribute.cs</Link>
</Compile>
<Compile Include="..\FileHelpers.cs">
<Link>FileHelpers.cs</Link>
</Compile>
<Compile Include="..\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>

View File

@ -60,9 +60,6 @@
<Compile Include="..\AssemblyCommentAttribute.cs">
<Link>AssemblyCommentAttribute.cs</Link>
</Compile>
<Compile Include="..\FileHelpers.cs">
<Link>FileHelpers.cs</Link>
</Compile>
<Compile Include="..\GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>

View File

@ -84,8 +84,14 @@
<Compile Include="..\AssemblyCommentAttribute.cs">
<Link>AssemblyCommentAttribute.cs</Link>
</Compile>
<Compile Include="..\FileHelpers.cs">
<Link>FileHelpers.cs</Link>
<Compile Include="..\DirectSound.cs">
<Link>DirectSound.cs</Link>
</Compile>
<Compile Include="..\DirectSoundInterop.cs">
<Link>DirectSoundInterop.cs</Link>
</Compile>
<Compile Include="..\GCHandleHelpers.cs">
<Link>GCHandleHelpers.cs</Link>
</Compile>
<Compile Include="..\GeneralSecurity.cs">
<Link>GeneralSecurity.cs</Link>
@ -99,6 +105,9 @@
<Compile Include="..\Lazy.cs">
<Link>Lazy.cs</Link>
</Compile>
<Compile Include="..\MarshalHelpers.cs">
<Link>MarshalHelpers.cs</Link>
</Compile>
<Compile Include="..\MathHelpers.cs">
<Link>MathHelpers.cs</Link>
</Compile>

View File

@ -2,7 +2,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading;
using Jellyfish.Virtu.Properties;
namespace Jellyfish.Virtu
{
@ -187,6 +186,7 @@ public int Execute()
Opcode = _memory.Read(RPC);
RPC = (RPC + 1) & 0xFFFF;
_executeOpcode[Opcode]();
Cycles += CC;
// System.Diagnostics.Debug.WriteLine(" " + ToString());
@ -3243,6 +3243,7 @@ private void UpdateSettings()
public int EA { get; private set; }
public int CC { get; private set; }
public int Opcode { get; private set; }
public long Cycles { get; private set; }
private Action _updateEvent;

View File

@ -7,7 +7,7 @@ public partial class Cpu
private const int CyclesPerUpdate = 17030;
private const int CyclesPerVSync = 17030;
private const int CyclesPerSecond = 1022730;
private static readonly long TicksPerVSync = TimeSpan.FromSeconds((double)CyclesPerVSync / (double)CyclesPerSecond).Ticks;
private static readonly long TicksPerVSync = TimeSpan.FromSeconds((double)CyclesPerVSync / CyclesPerSecond).Ticks;
private const int OpcodeCount = 256;

View File

@ -11,6 +11,7 @@
<Word>Annunciator</Word>
<Word>Dsk</Word>
<Word>Opcode</Word>
<Word>Unpause</Word>
<Word>Virtu</Word>
<Word>Xna</Word>
<Word>x</Word>

View File

@ -266,25 +266,25 @@ private void WriteDataNibbles(int sectorOffset)
private static readonly byte[] NibbleToByte = new byte[]
{
// padding for offset (not used)
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
// nibble translate table
0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E,
0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E,
0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
}
}

View File

@ -1,25 +1,19 @@
using System.Diagnostics.CodeAnalysis;
using Jellyfish.Virtu.Services;
using Jellyfish.Virtu.Settings;
namespace Jellyfish.Virtu
{
public sealed class DiskII : MachineComponent
{
public DiskII(Machine machine) :
public DiskII(Machine machine) :
base(machine)
{
}
public override void Initialize()
{
_storageService = Machine.Services.GetService<StorageService>();
#if WINDOWS
DiskIISettings settings = Machine.Settings.DiskII;
if (settings.Disk1.Name.Length == 0)
{
settings.Disk1.Name = _storageService.GetDiskFile();
}
if (settings.Disk1.Name.Length > 0)
{
_drives[0].InsertDisk(settings.Disk1.Name, settings.Disk1.IsWriteProtected);
@ -28,6 +22,7 @@ public override void Initialize()
{
_drives[1].InsertDisk(settings.Disk2.Name, settings.Disk2.IsWriteProtected);
}
#endif
}
public override void Reset()
@ -42,10 +37,6 @@ public override void Reset()
public override void Uninitialize()
{
Flush();
DiskIISettings settings = Machine.Settings.DiskII; // TODO remove; reset filename to prompt on next start
settings.Disk1.Name = string.Empty;
settings.Disk2.Name = string.Empty;
}
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
@ -227,13 +218,14 @@ private void SetPhase(int address)
}
}
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public Drive525[] Drives { get { return _drives; } }
private const int Phase0On = 1 << 0;
private const int Phase1On = 1 << 1;
private const int Phase2On = 1 << 2;
private const int Phase3On = 1 << 3;
private StorageService _storageService;
private Drive525[] _drives = new Drive525[] { new Drive525(), new Drive525() };
private int _latch;
private int _phaseStates;

View File

@ -1,9 +1,10 @@
using System;
using System.IO;
using Jellyfish.Library;
namespace Jellyfish.Virtu
{
public class Drive525
public sealed class Drive525
{
public Drive525()
{
@ -14,13 +15,20 @@ public Drive525()
}
public void InsertDisk(string fileName, bool isWriteProtected)
{
using (FileStream stream = File.OpenRead(fileName))
{
InsertDisk(fileName, stream, isWriteProtected);
}
}
public void InsertDisk(string name, Stream stream, bool isWriteProtected)
{
FlushTrack();
// TODO handle null param/empty string for eject, or add Eject()
byte[] fileData = FileHelpers.ReadAllBytes(fileName);
_disk = Disk525.CreateDisk(fileName, fileData, isWriteProtected);
_disk = Disk525.CreateDisk(name, stream.ReadAllBytes(), isWriteProtected);
_trackLoaded = false;
}

View File

@ -1,7 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Jellyfish.Library;
using Jellyfish.Virtu.Properties;
using Jellyfish.Virtu.Services;
using Jellyfish.Virtu.Settings;

View File

@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Jellyfish.Virtu.Properties;
using Jellyfish.Virtu.Services;
using Jellyfish.Virtu.Settings;

View File

@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System;
using System.Collections.ObjectModel;
using System.Threading;
using Jellyfish.Library;
using Jellyfish.Virtu.Services;
@ -6,11 +7,12 @@
namespace Jellyfish.Virtu
{
public class Machine
public enum MachineState { Stopped = 0, Starting, Running, Pausing, Paused, Stopping }
public sealed class Machine : IDisposable
{
public Machine()
{
Thread = new Thread(Run) { Name = "Machine" };
Events = new MachineEvents();
Services = new MachineServices();
Settings = new MachineSettings();
@ -23,8 +25,15 @@ public Machine()
Cassette = new Cassette(this);
Speaker = new Speaker(this);
Video = new Video(this);
Components = new Collection<MachineComponent> { Cpu, Memory, DiskII, Keyboard, GamePort, Cassette, Speaker, Video };
Thread = new Thread(Run) { Name = "Machine" };
}
public void Dispose()
{
_pausedEvent.Close();
_unpauseEvent.Close();
}
public void Reset()
@ -36,35 +45,64 @@ public void Start()
{
_storageService = Services.GetService<StorageService>();
_storageService.Load(MachineSettings.FileName, stream => Settings.Deserialize(stream));
State = MachineState.Starting;
Thread.Start();
}
public void Pause()
{
State = MachineState.Pausing;
_pausedEvent.WaitOne();
State = MachineState.Paused;
}
public void Unpause()
{
State = MachineState.Running;
_unpauseEvent.Set();
}
public void Stop()
{
_stopPending = true;
State = MachineState.Stopping;
_unpauseEvent.Set();
Thread.Join();
State = MachineState.Stopped;
_storageService.Save(MachineSettings.FileName, stream => Settings.Serialize(stream));
}
private void Run()
private void Run() // machine thread
{
Components.ForEach(component => component.Initialize());
Reset();
State = MachineState.Running;
do
{
Events.RaiseEvents(Cpu.Execute());
do
{
Events.RaiseEvents(Cpu.Execute());
}
while (State == MachineState.Running);
if (State == MachineState.Pausing)
{
_pausedEvent.Set();
_unpauseEvent.WaitOne();
}
}
while (!_stopPending);
while (State != MachineState.Stopping);
Components.ForEach(component => component.Uninitialize());
}
public Thread Thread { get; private set; }
public MachineEvents Events { get; private set; }
public MachineServices Services { get; private set; }
public MachineSettings Settings { get; private set; }
public Collection<MachineComponent> Components { get; private set; }
public MachineState State { get; private set; }
public Cpu Cpu { get; private set; }
public Memory Memory { get; private set; }
public DiskII DiskII { get; private set; }
@ -73,8 +111,13 @@ private void Run()
public Cassette Cassette { get; private set; }
public Speaker Speaker { get; private set; }
public Video Video { get; private set; }
public Collection<MachineComponent> Components { get; private set; }
public Thread Thread { get; private set; }
private AutoResetEvent _pausedEvent = new AutoResetEvent(false);
private AutoResetEvent _unpauseEvent = new AutoResetEvent(false);
private StorageService _storageService;
private bool _stopPending;
}
}

View File

@ -5,7 +5,7 @@
namespace Jellyfish.Virtu
{
public class MachineEvent
public sealed class MachineEvent
{
public MachineEvent(int delta, Action action)
{
@ -22,7 +22,7 @@ public override string ToString()
public Action Action { get; set; }
}
public class MachineEvents
public sealed class MachineEvents
{
public void AddEvent(int delta, Action action)
{
@ -75,7 +75,7 @@ public int FindEvent(Action action)
}
}
return delta;
return 0;
}
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
@ -86,17 +86,23 @@ public void RaiseEvents(int delta)
while (node.Value.Delta <= 0)
{
delta = node.Value.Delta;
node.Value.Action();
_used.Remove(node);
_free.AddFirst(node); // cache node; avoids garbage
RemoveEvent(node);
node = _used.First;
node.Value.Delta += delta;
}
}
private void RemoveEvent(LinkedListNode<MachineEvent> node)
{
if (node.Next != null)
{
node.Next.Value.Delta += node.Value.Delta;
}
_used.Remove(node);
_free.AddFirst(node); // cache node; avoids garbage
}
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
}

View File

@ -6,7 +6,7 @@
namespace Jellyfish.Virtu.Settings
{
public class MachineSettings
public sealed class MachineSettings
{
public MachineSettings()
{
@ -21,8 +21,8 @@ public MachineSettings()
UseGamePort = false,
Key = new KeySettings
{
Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', DownRight = 0 },
Joystick1 = new JoystickSettings { UpLeft = 0, Up = 'I', UpRight = 0, Left = 'J', Right = 'L', DownLeft = 0, Down = 'K', DownRight = 0 },
Joystick0 = new JoystickSettings { UpLeft = 0, Up = 'I', UpRight = 0, Left = 'J', Right = 'L', DownLeft = 0, Down = 'K', DownRight = 0 },
Joystick1 = new JoystickSettings { UpLeft = 0, Up = 'E', UpRight = 0, Left = 'S', Right = 'F', DownLeft = 0, Down = 'D', DownRight = 0 },
Button0 = 0, Button1 = 0, Button2 = 0
}
};
@ -305,25 +305,25 @@ public void Serialize(Stream stream)
public const string Namespace = "http://schemas.jellyfish.co.nz/virtu/settings";
}
public class CpuSettings
public sealed class CpuSettings
{
public bool Is65C02 { get; set; }
public bool IsThrottled { get; set; }
}
public class DiskSettings
public sealed class DiskSettings
{
public string Name { get; set; }
public bool IsWriteProtected { get; set; }
};
public class DiskIISettings
public sealed class DiskIISettings
{
public DiskSettings Disk1 { get; set; }
public DiskSettings Disk2 { get; set; }
};
public class JoystickSettings
public sealed class JoystickSettings
{
public int UpLeft { get; set; }
public int Up { get; set; }
@ -335,7 +335,7 @@ public class JoystickSettings
public int DownRight { get; set; }
};
public class KeySettings
public sealed class KeySettings
{
public JoystickSettings Joystick0 { get; set; }
public JoystickSettings Joystick1 { get; set; }
@ -344,19 +344,19 @@ public class KeySettings
public int Button2 { get; set; }
};
public class KeyboardSettings
public sealed class KeyboardSettings
{
public bool UseGamePort { get; set; }
public KeySettings Key { get; set; }
}
public class GamePortSettings
public sealed class GamePortSettings
{
public bool UseKeyboard { get; set; }
public KeySettings Key { get; set; }
}
public class ColorSettings
public sealed class ColorSettings
{
public uint Black { get; set; }
public uint DarkBlue { get; set; }
@ -377,7 +377,7 @@ public class ColorSettings
public uint Monochrome { get; set; }
}
public class VideoSettings
public sealed class VideoSettings
{
public bool IsFullScreen { get; set; }
public bool IsMonochrome { get; set; }

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.3074
// Runtime Version:2.0.50727.4016
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.

BIN
Virtu/Roms/AppleIIe.rom Normal file

Binary file not shown.

BIN
Virtu/Roms/DiskII.rom Normal file

Binary file not shown.

View File

@ -1,9 +1,86 @@
using System;
using System.Linq;
namespace Jellyfish.Virtu.Services
{
public class AudioService
public class AudioService : MachineService
{
public virtual void Update() { }
public AudioService(Machine machine) :
base(machine)
{
}
public void ToggleOutput() // machine thread
{
lock (_lock)
{
long cycles = Machine.Cpu.Cycles;
int toggleDelta = (int)(cycles - _toggleCycles);
_toggleCycles = cycles;
_deltaBuffer[_writeIndex] = toggleDelta;
_writeIndex = (_writeIndex + 1) % DeltaBufferSize;
}
}
protected void Update(int bufferSize, Action<byte[], int> updateBuffer) // audio thread
{
lock (_lock)
{
long cycles = Machine.Cpu.Cycles;
int updateDelta = (int)(cycles - _updateCycles);
_updateCycles = _toggleCycles = cycles; // reset audio frame
if (updateDelta > 0)
{
double bytesPerCycle = (double)bufferSize / (Machine.Settings.Cpu.IsThrottled ? CyclesPerSample : updateDelta);
while (_readIndex != _writeIndex)
{
int deltaSize = (int)(_deltaBuffer[_readIndex] * bytesPerCycle);
if (deltaSize > bufferSize)
{
_deltaBuffer[_readIndex] -= (int)((double)bufferSize / bytesPerCycle);
break;
}
updateBuffer(_isOutputHigh ? SampleHigh : SampleZero, deltaSize);
_isOutputHigh ^= true;
bufferSize -= deltaSize;
_readIndex = (_readIndex + 1) % DeltaBufferSize;
}
updateBuffer(_isOutputHigh ? SampleHigh : SampleZero, bufferSize);
}
else
{
updateBuffer(SampleZero, bufferSize);
}
}
}
public const int SampleRate = 44100; // hz
public const int SampleChannels = 1;
public const int SampleBits = 8;
public const int SampleSize = (int)(SampleRate * Latency / 1000f) * SampleChannels * SampleBits / 8;
private const int CyclesPerSecond = 1022730;
private const int CyclesPerSample = (int)(CyclesPerSecond * Latency / 1000f);
private const int Latency = 40; // ms
private static readonly byte[] SampleHigh = Enumerable.Repeat((byte)0xFF, SampleSize).ToArray();
private static readonly byte[] SampleZero = new byte[SampleSize];
private const int DeltaBufferSize = 8192;
private int[] _deltaBuffer = new int[DeltaBufferSize];
private uint _readIndex;
private uint _writeIndex;
private bool _isOutputHigh;
private long _toggleCycles;
private long _updateCycles;
private object _lock = new object();
}
}

View File

@ -48,9 +48,10 @@ public override string ToString()
private bool _isDown;
}
public class GamePortService
public class GamePortService : MachineService
{
public GamePortService()
public GamePortService(Machine machine) :
base(machine)
{
Paddle0 = Paddle1 = Paddle2 = Paddle3 = 255; // not connected
}

View File

@ -4,7 +4,7 @@
namespace Jellyfish.Virtu.Services
{
public class AsciiKeyEventArgs : EventArgs
public sealed class AsciiKeyEventArgs : EventArgs
{
private AsciiKeyEventArgs()
{
@ -22,8 +22,13 @@ public static AsciiKeyEventArgs Create(int asciiKey)
private static readonly AsciiKeyEventArgs _instance = new AsciiKeyEventArgs();
}
public abstract class KeyboardService
public abstract class KeyboardService : MachineService
{
protected KeyboardService(Machine machine) :
base(machine)
{
}
public abstract bool IsKeyDown(int key);
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
@ -42,7 +47,7 @@ public void WaitForKeyUp()
{
while (IsAnyKeyDown)
{
Thread.Sleep(100);
Thread.Sleep(10);
}
}
@ -50,7 +55,7 @@ public void WaitForResetKeyUp()
{
while (IsResetKeyDown)
{
Thread.Sleep(100);
Thread.Sleep(10);
}
}

View 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; }
}
}

View File

@ -5,9 +5,9 @@
namespace Jellyfish.Virtu.Services
{
public class MachineServices : IServiceProvider
public sealed class MachineServices : IServiceProvider
{
public void AddService(Type serviceType, object serviceProvider)
public void AddService(Type serviceType, MachineService serviceProvider)
{
if (_serviceProviders.ContainsKey(serviceType))
{
@ -37,6 +37,6 @@ public void RemoveService(Type serviceType)
_serviceProviders.Remove(serviceType);
}
private Dictionary<Type, object> _serviceProviders = new Dictionary<Type, object>();
private Dictionary<Type, MachineService> _serviceProviders = new Dictionary<Type, MachineService>();
}
}

View File

@ -1,13 +1,15 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace Jellyfish.Virtu.Services
{
public abstract class StorageService
public abstract class StorageService : MachineService
{
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public abstract string GetDiskFile();
protected StorageService(Machine machine) :
base(machine)
{
}
public abstract void Load(string path, Action<Stream> reader);
public abstract void Save(string path, Action<Stream> writer);
}

View File

@ -1,9 +1,12 @@
using System;
namespace Jellyfish.Virtu.Services
namespace Jellyfish.Virtu.Services
{
public abstract class VideoService
public abstract class VideoService : MachineService
{
protected VideoService(Machine machine) :
base(machine)
{
}
public abstract void SetPixel(int x, int y, uint color);
public abstract void Update();

View File

@ -62,6 +62,7 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net" />
<Reference Include="System.Windows.Controls.Toolkit, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Xml" />
<Reference Include="System.Windows.Browser" />
<Reference Include="System.Xml.Linq, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
@ -130,6 +131,9 @@
<Compile Include="..\Services\KeyboardService.cs">
<Link>Services\KeyboardService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineService.cs">
<Link>Services\MachineService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineServices.cs">
<Link>Services\MachineServices.cs</Link>
</Compile>
@ -155,6 +159,7 @@
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\SilverlightAudioService.cs" />
<Compile Include="Services\SilverlightKeyboardService.cs" />
<Compile Include="Services\SilverlightStorageService.cs" />
<Compile Include="Services\SilverlightVideoService.cs" />

View File

@ -2,7 +2,7 @@
namespace Jellyfish.Virtu
{
public partial class MainApp : ApplicationBase
public sealed partial class MainApp : ApplicationBase
{
public MainApp() :
base("Virtu")

View File

@ -1,11 +1,18 @@
<UserControl x:Class="Jellyfish.Virtu.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
xmlns:jv="clr-namespace:Jellyfish.Virtu;assembly=Jellyfish.Virtu">
<Grid Background="Black" Cursor="None">
<Image Name="_image" MinWidth="560" MinHeight="384"/>
<jl:FrameRateCounter/>
</Grid>
<tk:DockPanel Background="Black">
<StackPanel Orientation="Horizontal" tk:DockPanel.Dock="Top">
<Button x:Name="_disk1Button" Content="Disk 1" IsTabStop="false" Margin="4 4 0 4"/>
<Button x:Name="_disk2Button" Content="Disk 2" IsTabStop="false" Margin="4 4 0 4"/>
<jl:FrameRateCounter Margin="4 4 0 4" VerticalAlignment="Center"/>
</StackPanel>
<Grid Cursor="None">
<Image x:Name="_image" MinWidth="560" MinHeight="384"/>
<MediaElement x:Name="_media"/>
</Grid>
</tk:DockPanel>
</UserControl>

View File

@ -1,60 +1,78 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Jellyfish.Virtu.Services;
using System.Windows.Input;
namespace Jellyfish.Virtu
{
public partial class MainPage : UserControl
public sealed partial class MainPage : UserControl, IDisposable
{
public MainPage()
{
InitializeComponent();
_storageService = new SilverlightStorageService(this);
_keyboardService = new SilverlightKeyboardService(this);
_gamePortService = new GamePortService(); // not connected
_audioService = new AudioService(); // not connected
_videoService = new SilverlightVideoService(_image);
_storageService = new SilverlightStorageService(_machine);
_keyboardService = new SilverlightKeyboardService(_machine, this);
_gamePortService = new GamePortService(_machine); // not connected
_audioService = new SilverlightAudioService(_machine, this, _media);
_videoService = new SilverlightVideoService(_machine, _image);
_machine = new Machine();
_machine.Services.AddService(typeof(StorageService), _storageService);
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
_machine.Services.AddService(typeof(AudioService), _audioService);
_machine.Services.AddService(typeof(VideoService), _videoService);
Loaded += MainPage_Loaded;
Loaded += (sender, e) => _machine.Start();
CompositionTarget.Rendering += CompositionTarget_Rendering;
Application.Current.Exit += MainApp_Exit;
Application.Current.Exit += (sender, e) => _machine.Stop();
_disk1Button.Click += (sender, e) => DiskButton_Click(0);
_disk2Button.Click += (sender, e) => DiskButton_Click(1);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
public void Dispose()
{
_machine.Start();
_machine.Dispose();
_storageService.Dispose();
_keyboardService.Dispose();
_gamePortService.Dispose();
_audioService.Dispose();
_videoService.Dispose();
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
_keyboardService.Update();
_gamePortService.Update();
_audioService.Update();
_videoService.Update();
}
private void MainApp_Exit(object sender, EventArgs e)
private void DiskButton_Click(int drive)
{
_machine.Stop();
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
bool? result = dialog.ShowDialog();
if (result.HasValue && result.Value)
{
using (FileStream stream = dialog.File.OpenRead())
{
_machine.Pause();
_machine.DiskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false);
_machine.Unpause();
}
}
}
private Machine _machine = new Machine();
private StorageService _storageService;
private KeyboardService _keyboardService;
private GamePortService _gamePortService;
private AudioService _audioService;
private VideoService _videoService;
private Machine _machine;
}
}

View 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;
}
}

View File

@ -2,7 +2,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
@ -10,13 +9,14 @@ namespace Jellyfish.Virtu.Services
{
public sealed class SilverlightKeyboardService : KeyboardService
{
public SilverlightKeyboardService(UserControl page)
public SilverlightKeyboardService(Machine machine, UserControl page) :
base(machine)
{
_page = page;
_page.LostFocus += Page_LostFocus;
_page.KeyDown += Page_KeyDown;
_page.KeyUp += Page_KeyUp;
_page.LostFocus += Page_LostFocus;
}
public override bool IsKeyDown(int key)
@ -57,15 +57,6 @@ private bool IsKeyDown(Key key)
return _states[(int)key];
}
private void Page_LostFocus(object sender, RoutedEventArgs e) // reset keyboard state on lost focus; can't access keyboard state on got focus
{
IsAnyKeyDown = false;
foreach (Key key in KeyValues)
{
_states[(int)key] = false;
}
}
private void Page_KeyDown(object sender, KeyEventArgs e)
{
_states[(int)e.Key] = true;
@ -86,6 +77,15 @@ private void Page_KeyUp(object sender, KeyEventArgs e)
_updateAnyKeyDown = true;
}
private void Page_LostFocus(object sender, RoutedEventArgs e) // reset keyboard state on lost focus; can't access keyboard state on got focus
{
IsAnyKeyDown = false;
foreach (Key key in KeyValues)
{
_states[(int)key] = false;
}
}
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode")]
private int GetAsciiKey(Key key, int platformKeyCode)

View File

@ -1,45 +1,14 @@
using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Threading;
namespace Jellyfish.Virtu.Services
{
public sealed class SilverlightStorageService : StorageService
{
public SilverlightStorageService(UserControl page)
public SilverlightStorageService(Machine machine) :
base(machine)
{
_dispatcher = page.Dispatcher;
}
public override string GetDiskFile()
{
string fileName = string.Empty;
// TODO
//ManualResetEvent syncEvent = new ManualResetEvent(false);
//DispatcherOperation operation = _dispatcher.BeginInvoke(() =>
//{
// try
// {
// OpenFileDialog dialog = new OpenFileDialog(); // SL expects all dialogs to be user initiated, ie from within an event handler.
// dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
// bool? result = dialog.ShowDialog();
// if (result.HasValue && result.Value)
// {
// fileName = dialog.File.FullName;
// }
// }
// finally
// {
// syncEvent.Set();
// }
//});
//syncEvent.WaitOne();
return fileName;
}
public override void Load(string path, Action<Stream> reader)
@ -78,7 +47,5 @@ public override void Save(string path, Action<Stream> writer)
{
}
}
private Dispatcher _dispatcher;
}
}

View File

@ -3,21 +3,18 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Jellyfish.Virtu.Services
{
public sealed class SilverlightVideoService : VideoService
{
public SilverlightVideoService(Image image)
public SilverlightVideoService(Machine machine, Image image) :
base(machine)
{
_image = image;
SetImageSize();
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight);
_pixels = new int[BitmapWidth * BitmapHeight];
_image.Source = _bitmap;
SetImageSize();
Application.Current.Host.Content.Resized += (sender, e) => SetImageSize();
}
@ -73,8 +70,8 @@ private void SetImageSize()
private const int BitmapHeight = 384;
private Image _image;
private WriteableBitmap _bitmap;
private int[] _pixels;
private WriteableBitmap _bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight);
private int[] _pixels = new int[BitmapWidth * BitmapHeight];
private bool _pixelsDirty;
private bool _isFullScreen;
}

View File

@ -16,7 +16,7 @@ public override void Initialize()
public void ToggleOutput()
{
// TODO
_audioService.ToggleOutput();
}
private AudioService _audioService;

View File

@ -1,5 +1,4 @@
using System;
using Jellyfish.Virtu.Properties;
using Jellyfish.Virtu.Services;
using Jellyfish.Virtu.Settings;

View File

@ -42,10 +42,12 @@
</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Xna.Framework, Version=3.1.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d, processorArchitecture=x86" />
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
@ -128,6 +130,9 @@
<Compile Include="..\MachineEvents.cs">
<Link>Core\MachineEvents.cs</Link>
</Compile>
<Compile Include="..\Services\MachineService.cs">
<Link>Services\MachineService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineServices.cs">
<Link>Services\MachineServices.cs</Link>
</Compile>
@ -161,9 +166,13 @@
<Compile Include="..\Services\VideoService.cs">
<Link>Services\VideoService.cs</Link>
</Compile>
<Compile Include="..\Xna\Services\XnaGamePortService.cs">
<Link>Services\XnaGamePortService.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Services\WpfAudioService.cs" />
<Compile Include="Services\WpfKeyboardService.cs" />
<Compile Include="Services\WpfStorageService.cs" />
<Compile Include="Services\WpfVideoService.cs" />

View File

@ -1,9 +1,11 @@
using Jellyfish.Library;
using System.Security.Permissions;
using Jellyfish.Library;
namespace Jellyfish.Virtu
{
public partial class MainApp : ApplicationBase
public sealed partial class MainApp : ApplicationBase
{
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public MainApp() :
base("Virtu")
{

View File

@ -3,9 +3,14 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jl="clr-namespace:Jellyfish.Library;assembly=Jellyfish.Library"
Title="Virtu" ResizeMode="CanMinimize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
<Grid Background="Black" Cursor="None">
<Image Name="_image" MinWidth="560" MinHeight="384" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<jl:FrameRateCounter/>
</Grid>
<DockPanel Background="Black">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Button x:Name="_disk1Button" Content="Disk 1" Focusable="false" IsTabStop="false" Margin="4 4 0 4"/>
<Button x:Name="_disk2Button" Content="Disk 2" Focusable="false" IsTabStop="false" Margin="4 4 0 4"/>
<jl:FrameRateCounter Margin="4 4 0 4" VerticalAlignment="Center"/>
</StackPanel>
<Grid Cursor="None">
<Image x:Name="_image" MinWidth="560" MinHeight="384" RenderOptions.BitmapScalingMode="NearestNeighbor"/>
</Grid>
</DockPanel>
</Window>

View File

@ -1,58 +1,78 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Media;
using Jellyfish.Virtu.Services;
using Microsoft.Win32;
namespace Jellyfish.Virtu
{
public partial class MainWindow : Window
public sealed partial class MainWindow : Window, IDisposable
{
public MainWindow()
{
InitializeComponent();
_storageService = new WpfStorageService();
_keyboardService = new WpfKeyboardService(this);
_gamePortService = new GamePortService(); // not connected
_audioService = new AudioService(); // not connected
_videoService = new WpfVideoService(this, _image);
_storageService = new WpfStorageService(_machine);
_keyboardService = new WpfKeyboardService(_machine, this);
_gamePortService = new XnaGamePortService(_machine);
_audioService = new WpfAudioService(_machine, this);
_videoService = new WpfVideoService(_machine, this, _image);
_machine = new Machine();
_machine.Services.AddService(typeof(StorageService), _storageService);
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
_machine.Services.AddService(typeof(AudioService), _audioService);
_machine.Services.AddService(typeof(VideoService), _videoService);
Loaded += MainWindow_Loaded;
Loaded += (sender, e) => _machine.Start();
CompositionTarget.Rendering += CompositionTarget_Rendering;
Application.Current.Exit += MainApp_Exit;
Application.Current.Exit += (sender, e) => _machine.Stop();
_disk1Button.Click += (sender, e) => DiskButton_Click(0);
_disk2Button.Click += (sender, e) => DiskButton_Click(1);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
public void Dispose()
{
_machine.Start();
_machine.Dispose();
_storageService.Dispose();
_keyboardService.Dispose();
_gamePortService.Dispose();
_audioService.Dispose();
_videoService.Dispose();
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
_keyboardService.Update();
_gamePortService.Update();
_audioService.Update();
_videoService.Update();
}
private void MainApp_Exit(object sender, ExitEventArgs e)
private void DiskButton_Click(int drive)
{
_machine.Stop();
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
bool? result = dialog.ShowDialog();
if (result.HasValue && result.Value)
{
using (FileStream stream = File.OpenRead(dialog.FileName))
{
_machine.Pause();
_machine.DiskII.Drives[drive].InsertDisk(dialog.FileName, stream, false);
_machine.Unpause();
}
}
}
private Machine _machine = new Machine();
private StorageService _storageService;
private KeyboardService _keyboardService;
private GamePortService _gamePortService;
private AudioService _audioService;
private VideoService _videoService;
private Machine _machine;
}
}

View 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);
}
}

View File

@ -8,13 +8,14 @@ namespace Jellyfish.Virtu.Services
{
public sealed class WpfKeyboardService : KeyboardService
{
public WpfKeyboardService(Window window)
public WpfKeyboardService(Machine machine, Window window) :
base(machine)
{
_window = window;
_window.GotKeyboardFocus += Window_GotKeyboardFocus;
_window.KeyDown += Window_KeyDown;
_window.KeyUp += Window_KeyUp;
_window.GotKeyboardFocus += (sender, e) => _updateAnyKeyDown = true;
}
public override bool IsKeyDown(int key)
@ -55,11 +56,6 @@ private bool IsKeyDown(Key key)
return _states[(int)key];
}
private void Window_GotKeyboardFocus(object sender, EventArgs e)
{
_updateAnyKeyDown = true;
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
_states[(int)((e.Key == Key.System) ? e.SystemKey : e.Key)] = true;

View File

@ -2,29 +2,14 @@
using System.Deployment.Application;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using Microsoft.Win32;
namespace Jellyfish.Virtu.Services
{
public sealed class WpfStorageService : StorageService
{
public override string GetDiskFile()
public WpfStorageService(Machine machine) :
base(machine)
{
string fileName = string.Empty;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Disk Files (*.dsk;*.nib)|*.dsk;*.nib|All Files (*.*)|*.*";
bool? result = dialog.ShowDialog();
if (result.HasValue && result.Value)
{
fileName = dialog.FileName;
}
}));
return fileName;
}
public override void Load(string path, Action<Stream> reader)

View File

@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Permissions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@ -10,15 +11,14 @@ namespace Jellyfish.Virtu.Services
{
public sealed class WpfVideoService : VideoService
{
public WpfVideoService(Window window, Image image)
[SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
public WpfVideoService(Machine machine, Window window, Image image) :
base(machine)
{
_window = window;
_image = image;
SetImageSize();
_bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapDpi, BitmapDpi, BitmapPixelFormat, null);
_pixels = new uint[BitmapWidth * BitmapHeight];
_image.Source = _bitmap;
SetImageSize();
SystemEvents.DisplaySettingsChanged += (sender, e) => SetImageSize();
}
@ -74,8 +74,8 @@ private void SetImageSize()
private Window _window;
private Image _image;
private WriteableBitmap _bitmap;
private uint[] _pixels;
private WriteableBitmap _bitmap = new WriteableBitmap(BitmapWidth, BitmapHeight, BitmapDpi, BitmapDpi, BitmapPixelFormat, null);
private uint[] _pixels = new uint[BitmapWidth * BitmapHeight];
private bool _pixelsDirty;
private bool _isFullScreen;
}

View File

@ -128,6 +128,9 @@
<Compile Include="..\Services\KeyboardService.cs">
<Link>Services\KeyboardService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineService.cs">
<Link>Services\MachineService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineServices.cs">
<Link>Services\MachineServices.cs</Link>
</Compile>
@ -149,6 +152,7 @@
<Compile Include="MainApp.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="MainGame.cs" />
<Compile Include="Services\XnaAudioService.cs" />
<Compile Include="Services\XnaGamePortService.cs" />
<Compile Include="Services\XnaKeyboardService.cs" />
<Compile Include="Services\XnaStorageService.cs" />

View File

@ -149,6 +149,9 @@
<Compile Include="..\Services\KeyboardService.cs">
<Link>Services\KeyboardService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineService.cs">
<Link>Services\MachineService.cs</Link>
</Compile>
<Compile Include="..\Services\MachineServices.cs">
<Link>Services\MachineServices.cs</Link>
</Compile>
@ -170,6 +173,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="MainApp.cs" />
<Compile Include="MainGame.cs" />
<Compile Include="Services\XnaAudioService.cs" />
<Compile Include="Services\XnaGamePortService.cs" />
<Compile Include="Services\XnaKeyboardService.cs" />
<Compile Include="Services\XnaStorageService.cs" />

View File

@ -1,5 +1,3 @@
using System;
namespace Jellyfish.Virtu
{
static class MainApp

View File

@ -18,13 +18,16 @@ public MainGame() :
GraphicsDeviceManager.PreferredBackBufferWidth = 560;
GraphicsDeviceManager.PreferredBackBufferHeight = 384;
#endif
_storageService = new XnaStorageService(this);
_keyboardService = new XnaKeyboardService();
_gamePortService = new XnaGamePortService();
_audioService = new AudioService(); // not connected
_videoService = new XnaVideoService(this);
_storageService = new XnaStorageService(_machine, this);
_keyboardService = new XnaKeyboardService(_machine);
_gamePortService = new XnaGamePortService(_machine);
#if XBOX
_audioService = new AudioService(_machine); // not connected
#else
_audioService = new XnaAudioService(_machine, this);
#endif
_videoService = new XnaVideoService(_machine, this);
_machine = new Machine();
_machine.Services.AddService(typeof(StorageService), _storageService);
_machine.Services.AddService(typeof(KeyboardService), _keyboardService);
_machine.Services.AddService(typeof(GamePortService), _gamePortService);
@ -32,6 +35,20 @@ public MainGame() :
_machine.Services.AddService(typeof(VideoService), _videoService);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_machine.Dispose();
_storageService.Dispose();
_keyboardService.Dispose();
_gamePortService.Dispose();
_audioService.Dispose();
_videoService.Dispose();
}
base.Dispose(disposing);
}
protected override void BeginRun()
{
_machine.Start();
@ -41,7 +58,6 @@ protected override void Update(GameTime gameTime)
{
_keyboardService.Update();
_gamePortService.Update();
_audioService.Update();
base.Update(gameTime);
}
@ -57,12 +73,12 @@ protected override void EndRun()
_machine.Stop();
}
private Machine _machine = new Machine();
private StorageService _storageService;
private KeyboardService _keyboardService;
private GamePortService _gamePortService;
private AudioService _audioService;
private VideoService _videoService;
private Machine _machine;
}
}

View 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
}

View File

@ -1,11 +1,15 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace Jellyfish.Virtu.Services
{
public sealed class XnaGamePortService : GamePortService
{
public XnaGamePortService(Machine machine) :
base(machine)
{
}
public override void Update()
{
_lastState = _state;

View File

@ -8,17 +8,11 @@ namespace Jellyfish.Virtu.Services
{
public sealed class XnaKeyboardService : KeyboardService
{
public XnaKeyboardService() :
this(TimeSpan.FromMilliseconds(500).Ticks, TimeSpan.FromMilliseconds(32).Ticks)
public XnaKeyboardService(Machine machine) :
base(machine)
{
}
public XnaKeyboardService(long repeatDelay, long repeatSpeed)
{
_repeatDelay = repeatDelay;
_repeatSpeed = repeatSpeed;
}
public override bool IsKeyDown(int key)
{
return IsKeyDown((Keys)key);
@ -46,7 +40,7 @@ public override void Update()
_lastKey = key;
_lastTime = DateTime.UtcNow.Ticks;
_repeatTime = _repeatDelay;
_repeatTime = RepeatDelay;
#if XBOX
int asciiKey = GetAsciiKey(key, ref gamePadState);
#else
@ -71,7 +65,7 @@ public override void Update()
if (time - _lastTime >= _repeatTime)
{
_lastTime = time;
_repeatTime = _repeatSpeed;
_repeatTime = RepeatSpeed;
#if XBOX
int asciiKey = GetAsciiKey(_lastKey, ref gamePadState);
#else
@ -356,6 +350,8 @@ where field.IsLiteral
where (key != Keys.None) // filter Keys.None
select key).ToArray();
#endif
private static readonly long RepeatDelay = TimeSpan.FromMilliseconds(500).Ticks;
private static readonly long RepeatSpeed = TimeSpan.FromMilliseconds(32).Ticks;
private KeyboardState _state;
private KeyboardState _lastState;
@ -363,7 +359,5 @@ where field.IsLiteral
private Keys _lastKey;
private long _lastTime;
private long _repeatTime;
private long _repeatDelay;
private long _repeatSpeed;
}
}

View File

@ -8,15 +8,10 @@ namespace Jellyfish.Virtu.Services
{
public sealed class XnaStorageService : StorageService
{
public XnaStorageService(GameBase game)
public XnaStorageService(Machine machine, GameBase game) :
base(machine)
{
_game = game;
_storageDevice = new Lazy<StorageDevice>(() => Guide.EndShowStorageDeviceSelector(Guide.BeginShowStorageDeviceSelector(null, null)));
}
public override string GetDiskFile()
{
return string.Empty;
}
public override void Load(string path, Action<Stream> reader)
@ -48,6 +43,6 @@ public override void Save(string path, Action<Stream> writer)
}
private GameBase _game;
private Lazy<StorageDevice> _storageDevice;
private Lazy<StorageDevice> _storageDevice = new Lazy<StorageDevice>(() => Guide.EndShowStorageDeviceSelector(Guide.BeginShowStorageDeviceSelector(null, null)));
}
}

View File

@ -6,15 +6,16 @@
namespace Jellyfish.Virtu.Services
{
public sealed class XnaVideoService : VideoService, IDisposable
public sealed class XnaVideoService : VideoService
{
public XnaVideoService(GameBase game)
public XnaVideoService(Machine machine, GameBase game) :
base(machine)
{
_game = game;
_game.GraphicsDeviceManager.PreparingDeviceSettings += GraphicsDeviceManager_PreparingDeviceSettings;
_game.GraphicsDeviceService.DeviceCreated += GraphicsDeviceService_DeviceCreated;
_game.GraphicsDeviceService.DeviceReset += GraphicsDeviceService_DeviceReset;
_game.GraphicsDeviceService.DeviceReset += (sender, e) => SetTexturePosition();
}
[SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "y*560")]
@ -53,10 +54,13 @@ public override void Update()
_spriteBatch.End();
}
public void Dispose()
protected override void Dispose(bool disposing)
{
_spriteBatch.Dispose();
_texture.Dispose();
if (disposing)
{
_spriteBatch.Dispose();
_texture.Dispose();
}
}
private void GraphicsDeviceManager_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
@ -83,11 +87,6 @@ private void GraphicsDeviceService_DeviceCreated(object sender, EventArgs e)
SetTexturePosition();
}
private void GraphicsDeviceService_DeviceReset(object sender, EventArgs e)
{
SetTexturePosition();
}
private void SetTexturePosition()
{
_texturePosition.X = (_graphicsDevice.PresentationParameters.BackBufferWidth - TextureWidth * _textureScale) / 2f; // centered