diff --git a/Library/DirectSoundInterop.cs b/Library/DirectSoundInterop.cs
index 722c524..026e036 100644
--- a/Library/DirectSoundInterop.cs
+++ b/Library/DirectSoundInterop.cs
@@ -55,28 +55,6 @@ public BufferPositionNotify(int offset, EventWaitHandle notifyEvent)
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
{
@@ -125,7 +103,5 @@ private static class NativeMethods
[DllImport("dsound.dll")]
public static extern int DirectSoundCreate(IntPtr pcGuidDevice, [MarshalAs(UnmanagedType.Interface)] out IDirectSound pDS, IntPtr pUnkOuter);
}
-
- private const int WaveFormatPcm = 1;
}
}
diff --git a/Library/Silverlight/Jellyfish.Library.Silverlight.csproj b/Library/Silverlight/Jellyfish.Library.Silverlight.csproj
index 393aa64..a1b4b81 100644
--- a/Library/Silverlight/Jellyfish.Library.Silverlight.csproj
+++ b/Library/Silverlight/Jellyfish.Library.Silverlight.csproj
@@ -72,12 +72,19 @@
StreamExtensions.cs
+
+ StringBuilderExtensions.cs
+
+
+ WaveFormat.cs
+
FrameRateCounter.xaml
+
diff --git a/Library/Silverlight/WaveMediaStreamSource.cs b/Library/Silverlight/WaveMediaStreamSource.cs
new file mode 100644
index 0000000..d4e1bd4
--- /dev/null
+++ b/Library/Silverlight/WaveMediaStreamSource.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Windows.Media;
+
+namespace Jellyfish.Library
+{
+ public sealed class WaveMediaStreamSourceUpdateEventArgs : EventArgs
+ {
+ private WaveMediaStreamSourceUpdateEventArgs()
+ {
+ }
+
+ public static WaveMediaStreamSourceUpdateEventArgs Create(byte[] buffer, int bufferSize)
+ {
+ _instance.Buffer = buffer;
+ _instance.BufferSize = bufferSize;
+
+ return _instance; // use singleton; avoids garbage
+ }
+
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public byte[] Buffer { get; private set; }
+ public int BufferSize { get; private set; }
+
+ private static readonly WaveMediaStreamSourceUpdateEventArgs _instance = new WaveMediaStreamSourceUpdateEventArgs();
+ }
+
+ public sealed class WaveMediaStreamSource : MediaStreamSource, IDisposable
+ {
+ public WaveMediaStreamSource(int sampleRate, int sampleChannels, int sampleBits, int sampleSize, int sampleLatency)
+ {
+ _bufferSize = sampleSize;
+ _buffer = new byte[_bufferSize];
+ _bufferStream = new MemoryStream(_buffer);
+ _waveFormat = new WaveFormat(sampleRate, sampleChannels, sampleBits);
+ AudioBufferLength = sampleLatency; // ms; avoids audio delay
+ }
+
+ public void Dispose()
+ {
+ _bufferStream.Dispose();
+ }
+
+ protected override void CloseMedia()
+ {
+ _audioDescription = null;
+ }
+
+ protected override void GetDiagnosticAsync(MediaStreamSourceDiagnosticKind diagnosticKind)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void GetSampleAsync(MediaStreamType mediaStreamType)
+ {
+ var handler = Update;
+ if (handler != null)
+ {
+ handler(this, WaveMediaStreamSourceUpdateEventArgs.Create(_buffer, _bufferSize));
+ }
+
+ var sample = new MediaStreamSample(_audioDescription, _bufferStream, 0, _bufferSize, _timestamp, _emptySampleDict);
+ _timestamp += _bufferSize * 10000000L / _waveFormat.AverageBytesPerSec; // 100 ns
+
+ ReportGetSampleCompleted(sample);
+ }
+
+ protected override void OpenMediaAsync()
+ {
+ _timestamp = 0;
+
+ var sourceAttributes = new Dictionary() { { MediaSourceAttributesKeys.Duration, "0" }, { MediaSourceAttributesKeys.CanSeek, "false" } };
+ var streamAttributes = new Dictionary() { { MediaStreamAttributeKeys.CodecPrivateData, _waveFormat.ToHexString() } };
+ _audioDescription = new MediaStreamDescription(MediaStreamType.Audio, streamAttributes);
+ var availableStreams = new List() { _audioDescription };
+
+ ReportOpenMediaCompleted(sourceAttributes, availableStreams);
+ }
+
+ protected override void SeekAsync(long seekToTime)
+ {
+ ReportSeekCompleted(seekToTime);
+ }
+
+ protected override void SwitchMediaStreamAsync(MediaStreamDescription mediaStreamDescription)
+ {
+ throw new NotImplementedException();
+ }
+
+ public event EventHandler Update;
+
+ private byte[] _buffer;
+ private int _bufferSize;
+ private MemoryStream _bufferStream;
+ private WaveFormat _waveFormat;
+ private long _timestamp;
+ private MediaStreamDescription _audioDescription;
+ private Dictionary _emptySampleDict = new Dictionary();
+ }
+}
diff --git a/Library/StringBuilderExtensions.cs b/Library/StringBuilderExtensions.cs
new file mode 100644
index 0000000..2c5c9e8
--- /dev/null
+++ b/Library/StringBuilderExtensions.cs
@@ -0,0 +1,38 @@
+using System.Globalization;
+using System.Text;
+
+namespace Jellyfish.Library
+{
+ public static class StringBuilderExtensions
+ {
+ public static StringBuilder AppendHex(this StringBuilder builder, short value) // little endian
+ {
+ return builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}{1:X2}", value & 0xFF, value >> 8);
+ }
+
+ public static StringBuilder AppendHex(this StringBuilder builder, int value) // little endian
+ {
+ return builder.AppendFormat(CultureInfo.InvariantCulture, "{0:X2}{1:X2}{2:X2}{3:X2}", value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, value >> 24);
+ }
+
+ public static StringBuilder AppendWithoutGarbage(this StringBuilder builder, int value)
+ {
+ if (value < 0)
+ {
+ builder.Append('-');
+ }
+
+ int index = builder.Length;
+ do
+ {
+ builder.Insert(index, Digits, (value % 10) + 9, 1);
+ value /= 10;
+ }
+ while (value != 0);
+
+ return builder;
+ }
+
+ private static readonly char[] Digits = new char[] { '9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+ }
+}
diff --git a/Library/WaveFormat.cs b/Library/WaveFormat.cs
new file mode 100644
index 0000000..da82b2f
--- /dev/null
+++ b/Library/WaveFormat.cs
@@ -0,0 +1,49 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Jellyfish.Library
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class WaveFormat
+ {
+ public WaveFormat(int sampleRate, int sampleChannels, int sampleBits)
+ {
+ _formatTag = WaveFormatPcm;
+ _samplesPerSec = sampleRate;
+ _channels = (short)sampleChannels;
+ _bitsPerSample = (short)sampleBits;
+ _blockAlign = (short)(sampleChannels * sampleBits / 8);
+ _averageBytesPerSec = sampleRate * _blockAlign;
+ }
+
+ public string ToHexString() // little endian
+ {
+ StringBuilder builder = new StringBuilder();
+
+ builder.AppendHex(_formatTag);
+ builder.AppendHex(_channels);
+ builder.AppendHex(_samplesPerSec);
+ builder.AppendHex(_averageBytesPerSec);
+ builder.AppendHex(_blockAlign);
+ builder.AppendHex(_bitsPerSample);
+ builder.AppendHex(_size);
+
+ return builder.ToString();
+ }
+
+ public int SamplesPerSec { get { return _samplesPerSec; } } // no auto props
+ public int Channels { get { return _channels; } }
+ public int BitsPerSample { get { return _bitsPerSample; } }
+ public int AverageBytesPerSec { get { return _averageBytesPerSec; } }
+
+ private const int WaveFormatPcm = 1;
+
+ private short _formatTag;
+ private short _channels;
+ private int _samplesPerSec;
+ private int _averageBytesPerSec;
+ private short _blockAlign;
+ private short _bitsPerSample;
+ private short _size;
+ }
+}
diff --git a/Library/Wpf/Jellyfish.Library.Wpf.csproj b/Library/Wpf/Jellyfish.Library.Wpf.csproj
index 83ccd7f..50e3b1b 100644
--- a/Library/Wpf/Jellyfish.Library.Wpf.csproj
+++ b/Library/Wpf/Jellyfish.Library.Wpf.csproj
@@ -101,6 +101,12 @@
StreamExtensions.cs
+
+ StringBuilderExtensions.cs
+
+
+ WaveFormat.cs
+
XmlSerializerHelpers.cs
diff --git a/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj b/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj
index 28dba83..cc816f2 100644
--- a/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj
+++ b/Library/Xna/Jellyfish.Library.Xna.Xbox.csproj
@@ -80,13 +80,18 @@
StreamExtensions.cs
+
+ StringBuilderExtensions.cs
+
+
+ WaveFormat.cs
+
XmlSerializerHelpers.cs
-
diff --git a/Library/Xna/Jellyfish.Library.Xna.Zune.csproj b/Library/Xna/Jellyfish.Library.Xna.Zune.csproj
index 8dc5d3e..256fb16 100644
--- a/Library/Xna/Jellyfish.Library.Xna.Zune.csproj
+++ b/Library/Xna/Jellyfish.Library.Xna.Zune.csproj
@@ -78,13 +78,18 @@
StreamExtensions.cs
+
+ StringBuilderExtensions.cs
+
+
+ WaveFormat.cs
+
XmlSerializerHelpers.cs
-
diff --git a/Library/Xna/Jellyfish.Library.Xna.csproj b/Library/Xna/Jellyfish.Library.Xna.csproj
index f7acfb6..181bca4 100644
--- a/Library/Xna/Jellyfish.Library.Xna.csproj
+++ b/Library/Xna/Jellyfish.Library.Xna.csproj
@@ -123,13 +123,18 @@
StreamExtensions.cs
+
+ StringBuilderExtensions.cs
+
+
+ WaveFormat.cs
+
XmlSerializerHelpers.cs
-
diff --git a/Virtu/Cpu.cs b/Virtu/Cpu.cs
index 10657e8..15138fa 100644
--- a/Virtu/Cpu.cs
+++ b/Virtu/Cpu.cs
@@ -177,17 +177,12 @@ public override string ToString()
public int Execute()
{
-// EA = 0x0000;
-// System.Diagnostics.Debug.WriteLine(string.Format("{0:X4}-", RPC));
-
CC = 0;
Opcode = _memory.Read(RPC);
RPC = (RPC + 1) & 0xFFFF;
_executeOpcode[Opcode]();
Cycles += CC;
-// System.Diagnostics.Debug.WriteLine(" " + ToString());
-
return CC;
}
diff --git a/Virtu/Services/AudioService.cs b/Virtu/Services/AudioService.cs
index 345dcf6..fec46c8 100644
--- a/Virtu/Services/AudioService.cs
+++ b/Virtu/Services/AudioService.cs
@@ -68,12 +68,11 @@ protected void Update(int bufferSize, Action updateBuffer) // audio
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;
+ public const int SampleLatency = 40; // ms
+ public const int SampleSize = (int)(SampleRate * SampleLatency / 1000f) * SampleChannels * SampleBits / 8;
private const int CyclesPerSecond = 1022730;
- private const int CyclesPerSample = (int)(CyclesPerSecond * Latency / 1000f);
-
- private const int Latency = 40; // ms
+ private const int CyclesPerSample = (int)(CyclesPerSecond * SampleLatency / 1000f);
private static readonly byte[] SampleHigh = Enumerable.Repeat((byte)0xFF, SampleSize).ToArray();
private static readonly byte[] SampleZero = new byte[SampleSize];
diff --git a/Virtu/Silverlight/MainPage.xaml b/Virtu/Silverlight/MainPage.xaml
index 2aa6080..4b3bce4 100644
--- a/Virtu/Silverlight/MainPage.xaml
+++ b/Virtu/Silverlight/MainPage.xaml
@@ -12,7 +12,7 @@
-
+
diff --git a/Virtu/Silverlight/Services/SilverlightAudioService.cs b/Virtu/Silverlight/Services/SilverlightAudioService.cs
index aa01b90..ea524d6 100644
--- a/Virtu/Silverlight/Services/SilverlightAudioService.cs
+++ b/Virtu/Silverlight/Services/SilverlightAudioService.cs
@@ -1,5 +1,6 @@
using System;
using System.Windows.Controls;
+using Jellyfish.Library;
namespace Jellyfish.Virtu.Services
{
@@ -20,10 +21,22 @@ public SilverlightAudioService(Machine machine, UserControl page, MediaElement m
_page = page;
_media = media;
- // TODO
+ _page.Loaded += (sender, e) => _media.SetSource(_mediaSource);
+ _mediaSource.Update += OnMediaSourceUpdate;
+ }
+
+ private void OnMediaSourceUpdate(object sender, WaveMediaStreamSourceUpdateEventArgs e)
+ {
+ int offset = 0;
+ Update(e.BufferSize, (source, count) =>
+ {
+ Buffer.BlockCopy(source, 0, e.Buffer, offset, count);
+ offset += count;
+ });
}
private UserControl _page;
private MediaElement _media;
+ private WaveMediaStreamSource _mediaSource = new WaveMediaStreamSource(SampleRate, SampleChannels, SampleBits, SampleSize, SampleLatency);
}
}