00001 #region File Description
00002
00003
00004
00005 #endregion
00006
00007 namespace NewGamePhysics.Utilities
00008 {
00009 using System;
00010 using System.Threading;
00011 using System.Runtime.InteropServices;
00012
00016 [StructLayout(LayoutKind.Sequential)]
00017 public class WaveFormat
00018 {
00022 public ushort formatTag;
00023
00027 public ushort channels;
00028
00032 public uint samplesPerSec;
00033
00037 public uint averageBytesPerSec;
00038
00042 public ushort blockAlignment;
00043
00047 public ushort bitsPerSample;
00048
00052 public short cbSize;
00053
00060 public WaveFormat(uint rate, ushort bits, ushort channels)
00061 {
00062 if (!(rate > 0))
00063 {
00064 throw new ArgumentException("'rate' must be greater than >0", "rate");
00065 }
00066
00067 if (!((bits == 8) || (bits == 16)))
00068 {
00069 throw new ArgumentException("'bits' must be 8 or 16", "bits");
00070 }
00071
00072 if (!((channels == 1) || (channels == 2)))
00073 {
00074 throw new ArgumentException("'channels' must be 1 or 2", "channels");
00075 }
00076
00077 this.formatTag = 1;
00078 this.channels = channels;
00079 this.samplesPerSec = rate;
00080 this.bitsPerSample = bits;
00081 this.cbSize = 0;
00082
00083 this.blockAlignment = (ushort)(channels * (bits / 8));
00084 this.averageBytesPerSec = samplesPerSec * blockAlignment;
00085 }
00086 }
00087
00091 public class NativeAudioInterface
00092 {
00096 private const int MMSYSERR_BASE = 0;
00097
00101 private enum MMSYSERR : int
00102 {
00103 NOERROR = 0,
00104 ERROR = (MMSYSERR_BASE + 1),
00105 BADDEVICEID = (MMSYSERR_BASE + 2),
00106 NOTENABLED = (MMSYSERR_BASE + 3),
00107 ALLOCATED = (MMSYSERR_BASE + 4),
00108 INVALHANDLE = (MMSYSERR_BASE + 5),
00109 NODRIVER = (MMSYSERR_BASE + 6),
00110 NOMEM = (MMSYSERR_BASE + 7),
00111 NOTSUPPORTED = (MMSYSERR_BASE + 8),
00112 BADERRNUM = (MMSYSERR_BASE + 9),
00113 INVALFLAG = (MMSYSERR_BASE + 10),
00114 INVALPARAM = (MMSYSERR_BASE + 11),
00115 HANDLEBUSY = (MMSYSERR_BASE + 12),
00116 INVALIDALIAS = (MMSYSERR_BASE + 13),
00117 BADDB = (MMSYSERR_BASE + 14),
00118 KEYNOTFOUND = (MMSYSERR_BASE + 15),
00119 READERROR = (MMSYSERR_BASE + 16),
00120 WRITEERROR = (MMSYSERR_BASE + 17),
00121 DELETEERROR = (MMSYSERR_BASE + 18),
00122 VALNOTFOUND = (MMSYSERR_BASE + 19),
00123 NODRIVERCB = (MMSYSERR_BASE + 20),
00124 LASTERROR = (MMSYSERR_BASE + 20)
00125 }
00126
00130 private const int WAVERR_BASE = 32;
00131
00135 private enum WAVERR : int
00136 {
00137 NONE = 0,
00138 BADFORMAT = WAVERR_BASE + 0,
00139 STILLPLAYING = WAVERR_BASE + 1,
00140 UNPREPARED = WAVERR_BASE + 2,
00141 SYNC = WAVERR_BASE + 3,
00142 LASTERROR = WAVERR_BASE + 3
00143 }
00144
00148 public const int MMSYSERR_NOERROR = 0;
00149
00153 public const int MM_WOM_OPEN = 0x3BB;
00154
00158 public const int MM_WOM_CLOSE = 0x3BC;
00159
00163 public const int MM_WOM_DONE = 0x3BD;
00164
00168 public const int CALLBACK_FUNCTION = 0x00030000;
00169
00178 public delegate void WaveDelegate(
00179 IntPtr hdrvr,
00180 int uMsg,
00181 int dwUser,
00182 ref WaveHeader wavhdr,
00183 int dwParam2);
00184
00188 [StructLayout(LayoutKind.Sequential)] public struct WaveHeader
00189 {
00193 public IntPtr lpData;
00194
00198 public int dwBufferLength;
00199
00203 public int dwBytesRecorded;
00204
00208 public IntPtr dwUser;
00209
00213 public int dwFlags;
00214
00218 public int dwLoops;
00219
00223 public IntPtr lpNext;
00224
00228 public int reserved;
00229 }
00230
00231 #region native_calls
00235 private const string mmdll = "winmm.dll";
00236
00237 [DllImport(mmdll)]
00238 public static extern int waveOutPrepareHeader(IntPtr hWaveOut, ref WaveHeader lpWaveOutHdr, int uSize);
00239
00240 [DllImport(mmdll)]
00241 public static extern int waveOutUnprepareHeader(IntPtr hWaveOut, ref WaveHeader lpWaveOutHdr, int uSize);
00242
00243 [DllImport(mmdll)]
00244 public static extern int waveOutWrite(IntPtr hWaveOut, ref WaveHeader lpWaveOutHdr, int uSize);
00245
00246 [DllImport(mmdll)]
00247 public static extern int waveOutOpen(out IntPtr hWaveOut, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
00248
00249 [DllImport(mmdll)]
00250 public static extern int waveOutReset(IntPtr hWaveOut);
00251
00252 [DllImport(mmdll)]
00253 public static extern int waveOutClose(IntPtr hWaveOut);
00254
00255 #endregion
00256 }
00257
00262 public delegate void BufferFillCallback(byte[] data);
00263
00267 internal class SampleBuffer : IDisposable
00268 {
00272 private const int BufferWaitTimeoutSeconds = 10;
00273
00277 private IntPtr waveData;
00278
00282 private AutoResetEvent bufferCompleted = new AutoResetEvent(false);
00283
00287 private NativeAudioInterface.WaveHeader header;
00288
00292 private GCHandle headerHandle;
00293
00297 private byte[] headerData;
00298
00302 private GCHandle headerDataHandle;
00303
00307 private bool isPlaying;
00308
00317 internal static void WaveOutProc(
00318 IntPtr hdrvr,
00319 int message,
00320 int dwUser,
00321 ref NativeAudioInterface.WaveHeader waveHeader,
00322 int dwParam2)
00323 {
00324 if (message == NativeAudioInterface.MM_WOM_DONE)
00325 {
00326 try
00327 {
00328 GCHandle h = (GCHandle)waveHeader.dwUser;
00329 SampleBuffer buffer = (SampleBuffer)h.Target;
00330 buffer.OnCompleted();
00331 }
00332 catch
00333 {
00334 }
00335 }
00336 }
00337
00343 public SampleBuffer(IntPtr waveOutHandle, int bufferSize)
00344 {
00345
00346 waveData = waveOutHandle;
00347
00348
00349 headerHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
00350 header.dwUser = (IntPtr)GCHandle.Alloc(this);
00351
00352
00353 headerData = new byte[bufferSize];
00354 headerDataHandle = GCHandle.Alloc(headerData, GCHandleType.Pinned);
00355 header.lpData = headerDataHandle.AddrOfPinnedObject();
00356 header.dwBufferLength = bufferSize;
00357
00358
00359 int error = NativeAudioInterface.waveOutPrepareHeader(
00360 waveData,
00361 ref header,
00362 Marshal.SizeOf(header));
00363 if (error != NativeAudioInterface.MMSYSERR_NOERROR)
00364 {
00365 throw new ApplicationException("Could not prepare sample buffer.");
00366 }
00367 }
00368
00372 ~SampleBuffer()
00373 {
00374 Dispose();
00375 }
00376
00380 public void Dispose()
00381 {
00382
00383 if (header.lpData != IntPtr.Zero)
00384 {
00385 NativeAudioInterface.waveOutUnprepareHeader(waveData, ref header, Marshal.SizeOf(header));
00386 headerHandle.Free();
00387 header.lpData = IntPtr.Zero;
00388 }
00389
00390
00391 if (headerDataHandle.IsAllocated)
00392 {
00393 headerDataHandle.Free();
00394 }
00395
00396
00397 bufferCompleted.Close();
00398
00399 GC.SuppressFinalize(this);
00400 }
00401
00405 public int Size
00406 {
00407 get { return header.dwBufferLength; }
00408 }
00409
00413 public IntPtr Data
00414 {
00415 get { return header.lpData; }
00416 }
00417
00422 public bool Play()
00423 {
00424 lock(this)
00425 {
00426 this.bufferCompleted.Reset();
00427 int result = NativeAudioInterface.waveOutWrite(
00428 waveData,
00429 ref header,
00430 Marshal.SizeOf(header));
00431 this.isPlaying = (result == NativeAudioInterface.MMSYSERR_NOERROR);
00432 }
00433
00434 return this.isPlaying;
00435 }
00436
00440 public void WaitForBuffer()
00441 {
00442 if (this.isPlaying)
00443 {
00444 this.bufferCompleted.WaitOne(new TimeSpan(0, 0, BufferWaitTimeoutSeconds));
00445 }
00446 }
00447
00451 public void OnCompleted()
00452 {
00453 lock (this)
00454 {
00455 this.bufferCompleted.Set();
00456 this.isPlaying = false;
00457 }
00458 }
00459 }
00460
00464 public class WaveOutPlayer : IDisposable
00465 {
00469 private const int NumBuffers = 2;
00470
00474 private const int BufferSize = 4096;
00475
00479 private IntPtr waveOutHandle;
00480
00484 private SampleBuffer[] buffers = new SampleBuffer[NumBuffers];
00485
00489 private int currentBuffer;
00490
00494 private Thread fillBufferThread;
00495
00499 private BufferFillCallback bufferFillCallback;
00500
00504 private bool finished;
00505
00509 private byte zeroSampleValue;
00510
00514 private NativeAudioInterface.WaveDelegate waveOutProc =
00515 new NativeAudioInterface.WaveDelegate(SampleBuffer.WaveOutProc);
00516
00522 public WaveOutPlayer(
00523 WaveFormat format,
00524 BufferFillCallback bufferFillCallback)
00525 {
00526
00527 this.zeroSampleValue = format.bitsPerSample == 8 ? (byte)128 : (byte)0;
00528
00529
00530 this.bufferFillCallback = bufferFillCallback;
00531
00532
00533 int result = NativeAudioInterface.waveOutOpen(
00534 out waveOutHandle,
00535 -1,
00536 format,
00537 waveOutProc,
00538 0,
00539 NativeAudioInterface.CALLBACK_FUNCTION);
00540 if (result != NativeAudioInterface.MMSYSERR_NOERROR)
00541 {
00542 throw new ApplicationException("Could not open wave out device. Error: " + result);
00543 }
00544
00545
00546 AllocateBuffers();
00547
00548
00549 fillBufferThread = new Thread(new ThreadStart(WaveOutPlayerThread));
00550 fillBufferThread.Start();
00551 }
00552
00556 ~WaveOutPlayer()
00557 {
00558 Dispose();
00559 }
00560
00564 public void Dispose()
00565 {
00566 if (fillBufferThread != null)
00567 {
00568 try
00569 {
00570 finished = true;
00571 if (waveOutHandle != IntPtr.Zero)
00572 {
00573 NativeAudioInterface.waveOutReset(waveOutHandle);
00574 }
00575
00576 fillBufferThread.Join();
00577 bufferFillCallback = null;
00578 FreeBuffers();
00579 if (waveOutHandle != IntPtr.Zero)
00580 {
00581 NativeAudioInterface.waveOutClose(waveOutHandle);
00582 }
00583 }
00584 finally
00585 {
00586 fillBufferThread = null;
00587 waveOutHandle = IntPtr.Zero;
00588 }
00589 }
00590
00591 GC.SuppressFinalize(this);
00592 }
00593
00597 private void WaveOutPlayerThread()
00598 {
00599 while (!finished)
00600 {
00601 byte[] sampleData = new byte[BufferSize];
00602
00603 if (bufferFillCallback != null)
00604 {
00605
00606 bufferFillCallback(sampleData);
00607 }
00608 else
00609 {
00610
00611 for (int i = 0; i < BufferSize; i++)
00612 {
00613 sampleData[i] = zeroSampleValue;
00614 }
00615 }
00616
00617
00618 this.buffers[currentBuffer].WaitForBuffer();
00619
00620
00621 System.Runtime.InteropServices.Marshal.Copy(
00622 sampleData, 0, buffers[currentBuffer].Data, buffers[currentBuffer].Size);
00623
00624
00625 this.buffers[currentBuffer].Play();
00626
00627
00628 currentBuffer++;
00629 currentBuffer = currentBuffer % NumBuffers;
00630 }
00631
00632
00633 WaitForAllBuffers();
00634 }
00635
00639 private void AllocateBuffers()
00640 {
00641 FreeBuffers();
00642 for (int i = 0; i < NumBuffers; i++)
00643 {
00644 this.buffers[i] = new SampleBuffer(this.waveOutHandle, BufferSize);
00645 }
00646 }
00647
00651 private void FreeBuffers()
00652 {
00653 for (int i = 0; i < NumBuffers; i++)
00654 {
00655 if (this.buffers[i] != null)
00656 {
00657 this.buffers[i].Dispose();
00658 this.buffers[i] = null;
00659 }
00660 }
00661 }
00662
00666 private void WaitForAllBuffers()
00667 {
00668 for (int i = 0; i < NumBuffers; i++)
00669 {
00670 if (this.buffers[i] != null)
00671 {
00672 this.buffers[i].WaitForBuffer();
00673 }
00674 }
00675 }
00676 }
00677 }
00678