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.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)
{ {

View File

@ -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
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 class ApplicationBase : Application
{ {
public ApplicationBase() :
this(null)
{
}
public ApplicationBase(string name) public ApplicationBase(string name)
{ {
Name = name; Name = name;

View File

@ -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()
{ {

View File

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

View File

@ -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)
{ {

View File

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

View File

@ -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()
{ {

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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 SetPixel(int x, int y, uint color);
public abstract void Update(); public abstract void Update();

View File

@ -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" />

View File

@ -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")

View File

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

View File

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

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.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)

View File

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

View File

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

View File

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

View File

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

View File

@ -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" />

View File

@ -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")
{ {

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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" />

View File

@ -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" />

View File

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

View File

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

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

View File

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

View File

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

View File

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