mirror of
https://github.com/digital-jellyfish/Virtu.git
synced 2024-06-12 16:29:38 +00:00
Added sound emulation for Silverlight.
--HG-- extra : convert_revision : svn%3Affd33b8c-2492-42e0-bdc5-587b920b7d6d/trunk%4042615
This commit is contained in:
parent
0077d460f1
commit
51d9e8e5f2
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
102
Library/Silverlight/WaveMediaStreamSource.cs
Normal file
102
Library/Silverlight/WaveMediaStreamSource.cs
Normal 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>();
|
||||
}
|
||||
}
|
38
Library/StringBuilderExtensions.cs
Normal file
38
Library/StringBuilderExtensions.cs
Normal 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
49
Library/WaveFormat.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,12 +68,11 @@ protected void Update(int bufferSize, Action<byte[], int> 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];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user