Added sound emulation for Silverlight.

--HG--
extra : convert_revision : svn%3Affd33b8c-2492-42e0-bdc5-587b920b7d6d/trunk%4042615
This commit is contained in:
Sean Fausett 2010-03-08 09:54:04 +00:00
parent 0077d460f1
commit 51d9e8e5f2
13 changed files with 238 additions and 38 deletions

View File

@ -55,28 +55,6 @@ namespace Jellyfish.Library
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 @@ namespace Jellyfish.Library
[DllImport("dsound.dll")]
public static extern int DirectSoundCreate(IntPtr pcGuidDevice, [MarshalAs(UnmanagedType.Interface)] out IDirectSound pDS, IntPtr pUnkOuter);
}
private const int WaveFormatPcm = 1;
}
}

View File

@ -72,12 +72,19 @@
<Compile Include="..\StreamExtensions.cs">
<Link>StreamExtensions.cs</Link>
</Compile>
<Compile Include="..\StringBuilderExtensions.cs">
<Link>StringBuilderExtensions.cs</Link>
</Compile>
<Compile Include="..\WaveFormat.cs">
<Link>WaveFormat.cs</Link>
</Compile>
<Compile Include="ApplicationBase.cs" />
<Compile Include="FrameRateCounter.xaml.cs">
<DependentUpon>FrameRateCounter.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringFormatConverter.cs" />
<Compile Include="WaveMediaStreamSource.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="FrameRateCounter.xaml">

View File

@ -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, string>() { { MediaSourceAttributesKeys.Duration, "0" }, { MediaSourceAttributesKeys.CanSeek, "false" } };
var streamAttributes = new Dictionary<MediaStreamAttributeKeys, string>() { { MediaStreamAttributeKeys.CodecPrivateData, _waveFormat.ToHexString() } };
_audioDescription = new MediaStreamDescription(MediaStreamType.Audio, streamAttributes);
var availableStreams = new List<MediaStreamDescription>() { _audioDescription };
ReportOpenMediaCompleted(sourceAttributes, availableStreams);
}
protected override void SeekAsync(long seekToTime)
{
ReportSeekCompleted(seekToTime);
}
protected override void SwitchMediaStreamAsync(MediaStreamDescription mediaStreamDescription)
{
throw new NotImplementedException();
}
public event EventHandler<WaveMediaStreamSourceUpdateEventArgs> Update;
private byte[] _buffer;
private int _bufferSize;
private MemoryStream _bufferStream;
private WaveFormat _waveFormat;
private long _timestamp;
private MediaStreamDescription _audioDescription;
private Dictionary<MediaSampleAttributeKeys, string> _emptySampleDict = new Dictionary<MediaSampleAttributeKeys, string>();
}
}

View File

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

49
Library/WaveFormat.cs Normal file
View File

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

View File

@ -101,6 +101,12 @@
<Compile Include="..\StreamExtensions.cs">
<Link>StreamExtensions.cs</Link>
</Compile>
<Compile Include="..\StringBuilderExtensions.cs">
<Link>StringBuilderExtensions.cs</Link>
</Compile>
<Compile Include="..\WaveFormat.cs">
<Link>WaveFormat.cs</Link>
</Compile>
<Compile Include="..\XmlSerializerHelpers.cs">
<Link>XmlSerializerHelpers.cs</Link>
</Compile>

View File

@ -80,13 +80,18 @@
<Compile Include="..\StreamExtensions.cs">
<Link>StreamExtensions.cs</Link>
</Compile>
<Compile Include="..\StringBuilderExtensions.cs">
<Link>StringBuilderExtensions.cs</Link>
</Compile>
<Compile Include="..\WaveFormat.cs">
<Link>WaveFormat.cs</Link>
</Compile>
<Compile Include="..\XmlSerializerHelpers.cs">
<Link>XmlSerializerHelpers.cs</Link>
</Compile>
<Compile Include="FrameRateCounter.cs" />
<Compile Include="GameBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringBuilderExtensions.cs" />
</ItemGroup>
<ItemGroup>
<NestedContentProject Include="Content\Content.contentproj">

View File

@ -78,13 +78,18 @@
<Compile Include="..\StreamExtensions.cs">
<Link>StreamExtensions.cs</Link>
</Compile>
<Compile Include="..\StringBuilderExtensions.cs">
<Link>StringBuilderExtensions.cs</Link>
</Compile>
<Compile Include="..\WaveFormat.cs">
<Link>WaveFormat.cs</Link>
</Compile>
<Compile Include="..\XmlSerializerHelpers.cs">
<Link>XmlSerializerHelpers.cs</Link>
</Compile>
<Compile Include="FrameRateCounter.cs" />
<Compile Include="GameBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringBuilderExtensions.cs" />
</ItemGroup>
<ItemGroup>
<NestedContentProject Include="Content\Content.contentproj">

View File

@ -123,13 +123,18 @@
<Compile Include="..\StreamExtensions.cs">
<Link>StreamExtensions.cs</Link>
</Compile>
<Compile Include="..\StringBuilderExtensions.cs">
<Link>StringBuilderExtensions.cs</Link>
</Compile>
<Compile Include="..\WaveFormat.cs">
<Link>WaveFormat.cs</Link>
</Compile>
<Compile Include="..\XmlSerializerHelpers.cs">
<Link>XmlSerializerHelpers.cs</Link>
</Compile>
<Compile Include="FrameRateCounter.cs" />
<Compile Include="GameBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringBuilderExtensions.cs" />
</ItemGroup>
<ItemGroup>
<NestedContentProject Include="Content\Content.contentproj">

View File

@ -177,17 +177,12 @@ namespace Jellyfish.Virtu
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;
}

View File

@ -68,12 +68,11 @@ namespace Jellyfish.Virtu.Services
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];

View File

@ -12,7 +12,7 @@
</StackPanel>
<Grid Cursor="None">
<Image x:Name="_image" MinWidth="560" MinHeight="384"/>
<MediaElement x:Name="_media"/>
<MediaElement x:Name="_media" AutoPlay="true"/>
</Grid>
</tk:DockPanel>
</UserControl>

View File

@ -1,5 +1,6 @@
using System;
using System.Windows.Controls;
using Jellyfish.Library;
namespace Jellyfish.Virtu.Services
{
@ -20,10 +21,22 @@ namespace Jellyfish.Virtu.Services
_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);
}
}