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