diff --git a/Build/Setup.wxs b/Build/Setup.wxs
index 8df2564..8fb64bf 100644
--- a/Build/Setup.wxs
+++ b/Build/Setup.wxs
@@ -31,6 +31,7 @@
+
@@ -54,6 +55,7 @@
+
diff --git a/Floe.Audio/Exceptions.cs b/Floe.Audio/Exceptions.cs
new file mode 100644
index 0000000..bac3e1b
--- /dev/null
+++ b/Floe.Audio/Exceptions.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Floe.Audio
+{
+ [Serializable]
+ public class FileFormatException : Exception
+ {
+ public FileFormatException()
+ {
+ }
+
+ public FileFormatException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/Floe.Audio/FifoStream.cs b/Floe.Audio/FifoStream.cs
new file mode 100644
index 0000000..865895b
--- /dev/null
+++ b/Floe.Audio/FifoStream.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+
+namespace Floe.Audio
+{
+ public class FifoStream : Stream
+ {
+ private const int BlockSize = 8192;
+
+ private LinkedList _blocks;
+ private int _readIdx, _writeIdx;
+ private ManualResetEventSlim _pulse;
+ private bool _isDisposed;
+
+ public FifoStream()
+ {
+ _blocks = new LinkedList();
+ _pulse = new ManualResetEventSlim(false);
+ }
+
+ public override bool CanRead { get { return true; } }
+ public override bool CanSeek { get { return false; } }
+ public override bool CanWrite { get { return true; } }
+ public override void Flush() { throw new NotImplementedException(); }
+ public override long Length { get { throw new NotImplementedException(); } }
+ public override long Position { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }
+ public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); }
+ public override void SetLength(long value) { throw new NotImplementedException(); }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ _pulse.Wait();
+ if (_isDisposed)
+ {
+ return 0;
+ }
+ int total = 0;
+ lock (_blocks)
+ {
+ while (count > 0)
+ {
+ int written;
+ written = Math.Min(count, (_blocks.First == _blocks.Last ? _writeIdx : BlockSize) - _readIdx);
+ Array.Copy(_blocks.First.Value, _readIdx, buffer, offset, written);
+ count -= written;
+ offset += written;
+ _readIdx += written;
+
+ if (_readIdx >= BlockSize)
+ {
+ _blocks.RemoveFirst();
+ _readIdx = 0;
+ }
+ total += written;
+
+ if (_blocks.First == null ||
+ (_blocks.First == _blocks.Last && _readIdx >= _writeIdx))
+ {
+ _pulse.Reset();
+ break;
+ }
+ }
+ }
+ return total;
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (count < 1)
+ {
+ return;
+ }
+
+ lock (_blocks)
+ {
+ while (count > 0)
+ {
+ if (_blocks.Last == null || _writeIdx >= BlockSize)
+ {
+ _blocks.AddLast(new byte[BlockSize]);
+ _writeIdx = 0;
+ }
+ int written = Math.Min(count, BlockSize - _writeIdx);
+ Array.Copy(buffer, offset, _blocks.Last.Value, _writeIdx, written);
+ count -= written;
+ offset += written;
+ _writeIdx += written;
+ _pulse.Set();
+ }
+ }
+ }
+
+ public override void Close()
+ {
+ base.Close();
+ _isDisposed = true;
+ _pulse.Set();
+ }
+ }
+}
diff --git a/Floe.Audio/FilePlayer.cs b/Floe.Audio/FilePlayer.cs
new file mode 100644
index 0000000..99709d7
--- /dev/null
+++ b/Floe.Audio/FilePlayer.cs
@@ -0,0 +1,84 @@
+using System;
+using System.IO;
+using System.Linq;
+using Floe.Interop;
+
+namespace Floe.Audio
+{
+ public class FilePlayer : IDisposable
+ {
+ private const int WavBufferSamples = 3000;
+ private static readonly byte[] WavFileSignature = { 0x52, 0x49, 0x46, 0x46 }; // RIFF
+ private static readonly byte[] Mp3FileSignature = { 0x49, 0x44, 0x33 }; // ID3
+
+ private WaveOut _waveOut;
+
+ public event EventHandler Done;
+
+ public FilePlayer(string fileName)
+ {
+ var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
+ var sig = new byte[4];
+ try
+ {
+ fileStream.Read(sig, 0, 4);
+ fileStream.Seek(0, SeekOrigin.Begin);
+ if (WavFileSignature.SequenceEqual(sig.Take(WavFileSignature.Length)))
+ {
+ var wavStream = new WavFileStream(fileStream);
+ _waveOut = new WaveOut(wavStream, wavStream.Format, WavBufferSamples * wavStream.Format.FrameSize);
+ }
+ else if (Mp3FileSignature.SequenceEqual(sig.Take(Mp3FileSignature.Length)))
+ {
+ var mp3Stream = new Mp3FileStream(fileStream);
+ _waveOut = new WaveOut(mp3Stream, mp3Stream.Format, mp3Stream.Format.BlockSize + 1);
+ }
+ else
+ {
+ throw new FileFormatException("Unrecognized file format.");
+ }
+ }
+ catch (EndOfStreamException)
+ {
+ throw new FileFormatException("Premature end of file.");
+ }
+ _waveOut.EndOfStream += (sender, e) =>
+ {
+ var handler = this.Done;
+ if(handler != null)
+ {
+ handler(this, EventArgs.Empty);
+ }
+ };
+ }
+
+ public void Start()
+ {
+ _waveOut.Start();
+ }
+
+ public void Close()
+ {
+ _waveOut.Close();
+ }
+
+ public void Dispose()
+ {
+ _waveOut.Dispose();
+ }
+
+ public static void PlayAsync(string fileName, Action