Click here to Skip to main content
Click here to Skip to main content
 
Add your own
alternative version

Sound Experiments in Managed DirectX

, 16 Feb 2007
Using static and streaming sound buffers in Managed DirectX.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES
//  OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//  � 2006 Gary W. Schwede  Stream Computers, Inc. All rights reserved.
//  Contact: gary at streamcomputers dot com. Permission to incorporate
//  all or part of this code in your application is given on the condition
//  that this notice accompanies it in your code and documentation.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using Microsoft.DirectX.DirectSound;
using StreamComputers.Riff;
using StreamComputers.Utilities;

namespace StreamComputers.MdxDirectSound
{
	#region StreamingSoundBuffer Class
	/// <summary>
	/// A streaming sound buffer associated with a specific RIFF WAVE file.
	/// </summary>
	public class StreamingSoundBuffer : MdxSoundBuffer
	{
		#region private fields

		// A note on buffer size: I assume CD audio; i.e., 16-bit stereo frames at 44100 frames/sec.
		// 64k * 4 bytes per frame is about 1.5 sec. There's about 370 msec between notification events.
		private const int			m_StreamBufferSize = 262144; 
		private const int			m_numNotifyPositions = 4;	 
		private BufferPositionNotify[] m_PositionNotify = 
			new BufferPositionNotify[m_numNotifyPositions];  
		private AutoResetEvent		m_NotificationEvent;
		private const int			m_TransferBlockSize = m_StreamBufferSize / m_numNotifyPositions;
		private byte[]				m_TransferBuf = new byte[m_TransferBlockSize] ;
		private Notify				m_Notify;
		private Thread				m_DataTransferThread;

		private RiffWaveReader		m_RiffReader;
		private bool				m_WaveDataAvailable;
		private bool				m_AbortDataTransfer;
		private int					m_bytesSoFar;		// data bytes transferred to the SecondaryBuffer so far
		private int					m_SecBufWritePos;	// byte index to start next write()
		private int					m_numSpuriousNotifies;
		private int					m_numBlocksTransferred;

		#endregion

		public StreamingSoundBuffer(string fileName, Device dev)
		{
			// UI button should be disabled if no file
			if(fileName == null)
			{
				// If you use my code, I suggest you change this message...
				// or make very sure the UI won't get here!
				MessageBox.Show("Select a sound file first, moron.");
				return;
			}
			try
			{
				// Use a RiffWaveReader to access the WAVE file
				m_RiffReader = new RiffWaveReader(fileName);
			}
			catch(RiffParserException)
			{
				if(m_RiffReader != null)
				{
					m_RiffReader.Dispose();
				}
				throw;
			}	
			WaveFormat format = m_RiffReader.GetMDXWaveFormat();
			DebugConsole.WriteLine(MdxInfo.WaveFormatAsString(format));

			// create a SecondaryBuffer suitable for streaming use and with sticky focus
			BufferDescription bdesc = new BufferDescription(format);
			bdesc.BufferBytes = m_StreamBufferSize;
			bdesc.ControlPositionNotify = true;
			bdesc.CanGetCurrentPosition = true;
			bdesc.StickyFocus = true;

			m_SecondaryBuffer = new SecondaryBuffer(bdesc, dev);
			m_SecondaryBuffer.SetCurrentPosition(0) ;
			m_SecBufWritePos = 0;
			DebugConsole.WriteLine(MdxInfo.BufferCapsAsString(m_SecondaryBuffer.Caps) );

			SetUpNotifications();				
		}

		/// <summary>
		/// Create & initialize the Notify object and notification positions in the streaming   
		/// SecondaryBuffer.  These persist through multiple Play/Pause/Stop events.
		/// </summary>
		private void SetUpNotifications()
		{
			// Create a notification Event object, to fire at each notify position
			m_NotificationEvent = new AutoResetEvent(false);

			// Set up the notification positions.
			// All use the same event, in contrast to recipe in DX9.0 SDK Aug 2005 titled 
			// "DirectSound Buffers | Using Streaming Buffers" 
			for (int i = 0; i < m_numNotifyPositions; i++)
			{
				m_PositionNotify[i].Offset = (i + 1)*m_TransferBlockSize - 1;
				m_PositionNotify[i].EventNotifyHandle = m_NotificationEvent.Handle;
			}

			// create the buffer's Notify object
			m_Notify = new Notify(m_SecondaryBuffer);

			// set the buffer to fire events at the notification positions
			m_Notify.SetNotificationPositions(m_PositionNotify, m_numNotifyPositions);
		}

		#region Data Transfer Thread code

		/// <summary>
		/// Instantiate and start the server thread.  It will catch events from the Notify object, 
		/// and call TransferBlockToSecondaryBuffer() each time a Notification position is crossed.
		/// Thread terminates at end of playback, or upon Stop event.
		/// </summary>
		private void CreateDataTransferThread()
		{
			// No thread should exist yet.
			Debug.Assert(m_DataTransferThread == null,
				"CreateDataTransferThread() saw thread non-null." );
			
			// Create a thread to monitor the notify events.
			m_DataTransferThread = new Thread(new ThreadStart(DataTransferActivity));
			m_DataTransferThread.Name = "DataTransferThread";
			m_DataTransferThread.Priority = ThreadPriority.Highest;
			m_AbortDataTransfer = false;
			m_WaveDataAvailable = true;
			m_numSpuriousNotifies = 0;
			m_numBlocksTransferred = 0;
			m_DataTransferThread.Start();
	
			// thread will wait for notification events
		}

		/// <summary>
		/// The DataTransferThread's work function.  Returns, and thread ends, when
		/// wave data transfer has ended and buffer has been filled with silence, or 
		/// when Stop event does EndDataTransferThread().
		/// </summary>
		private void DataTransferActivity()
		{
			while(m_WaveDataAvailable)
			{
				//wait here for a notification event
				m_NotificationEvent.WaitOne(Timeout.Infinite, true);
				if(m_AbortDataTransfer)
				{
					return;
				}

				// The following logic defends against occasional spurious notification events
				// which seem to be due to a bug in DirectSound.
				int currentPlayPos, currentWritePos;
				m_SecondaryBuffer.GetCurrentPosition(out currentPlayPos, out currentWritePos);
				int nextWriteSector = m_SecBufWritePos / m_TransferBlockSize;
				int currentPlaySector = currentPlayPos / m_TransferBlockSize;
				if(nextWriteSector != currentPlaySector)	// don't overwrite the play segment!
				{
					m_WaveDataAvailable = TransferBlockToSecondaryBuffer();
				}
				else
				{
					DebugConsole.WriteLine("Spurious Notification detected.");
					m_numSpuriousNotifies++;
				}
			}
			
			// Wave data transfer has ended.  
			// Fill the SecondaryBuffer with silence as it finishes playing the last blocks.
			Array.Clear(m_TransferBuf, 0, m_TransferBuf.Length);
			for (int i = 0; i < m_numNotifyPositions; i++)
			{
				m_NotificationEvent.WaitOne(Timeout.Infinite, true);	//true to exit synch domain before wait ?
				m_SecondaryBuffer.Write(m_SecBufWritePos, m_TransferBuf, LockFlag.None);
				m_SecBufWritePos += m_TransferBlockSize;
				m_SecBufWritePos %= m_StreamBufferSize;
				DebugConsole.WriteLine("{0, 12}    {1,12}    {2,12}", 
					m_bytesSoFar, 0, m_SecBufWritePos);
			}

			// DON'T DO THIS!
			// MDX doc suggests to set a notify flag at the end of the real data
			//m_EndOfDataEvent = new AutoResetEvent(false);
			//m_PositionNotify[m_numNotifyPositions].Offset = m_SecBufWritePos;
			//m_PositionNotify[m_numNotifyPositions].EventNotifyHandle = m_EndOfDataEvent.Handle;
			// try this: MDX doc contradictory on whether it can be done. BOMBS with INVALID CALL exception
			//m_SecondaryBufferNotify.SetNotificationPositions(m_PositionNotify, m_numNotifyPositions +1);
			//m_EndOfDataEvent.WaitOne(Timeout.Infinite, true);

			m_SecondaryBuffer.Stop();
			m_SecondaryBuffer.SetCurrentPosition(0);
			DebugConsole.WriteLine("Stopped.");

			DebugReportAnomalies();
		}

		[Conditional("DEBUG")]
		private void DebugReportAnomalies()
		{
			// Testing code to ensure that no notification events were missed
			int numBlocksExpected = (int) Math.Ceiling(
				(double)m_RiffReader.DataLength / (double)m_TransferBlockSize );
			if(m_numBlocksTransferred != numBlocksExpected )
			{
				MessageBox.Show(String.Format("{0} blocks transferred\n{1} blocks expected.",
				                              m_numBlocksTransferred, numBlocksExpected ),
				                "Data Transfer Error.",
				                MessageBoxButtons.OK,  
				                MessageBoxIcon.Exclamation );
			}
	
			// Testing code to report spurious notify events
			if(m_numSpuriousNotifies > 0)
			{
				MessageBox.Show(String.Format("{0} Spurious notify events detected during play.\n",
				                              m_numSpuriousNotifies ),
				                "Possible DirectSound Bug Detected",
				                MessageBoxButtons.OK,  
				                MessageBoxIcon.Exclamation );
			}
		}

		/// <summary>
		/// Transfers a block of data from the transfer buffer to the secondary buffer.
		/// </summary>
		/// <returns>true if the whole block is wave data, false if end of data with zero-fill</returns>
		private bool TransferBlockToSecondaryBuffer()
		{
			/// If the file has run out of wave data, the block was zero-filled to the end. 
			int bytesThisTime = m_RiffReader.GetDataBytes(ref m_TransferBuf, m_bytesSoFar, m_TransferBlockSize);

			// actual number of data bytes transferred so far, not including zero-fill
			m_bytesSoFar += bytesThisTime;

			// write the block to the buffer, possibly including zero-fill
			m_SecondaryBuffer.Write(m_SecBufWritePos, m_TransferBuf, LockFlag.None);
			m_numBlocksTransferred++;

			// update write position 
			m_SecBufWritePos += m_TransferBlockSize;
			m_SecBufWritePos %= m_StreamBufferSize;
			DebugConsole.WriteLine("{0, 12}    {1,12}    {2,12}", 
				m_bytesSoFar, bytesThisTime, m_SecBufWritePos);

			if(bytesThisTime < m_TransferBlockSize)
			{
				return false;
			}
			return true;
		}

		// Safely end the DataTransferThread if it is alive.  Sets ref to null.
		// Called from Stop() and Dispose() ie Cleanup()
		private void EndDataTransferThread()
		{
			if (m_DataTransferThread == null)
			{
				return;
			}
			if (m_DataTransferThread.IsAlive)
			{
				m_WaveDataAvailable = false;
				m_AbortDataTransfer = true;
				m_NotificationEvent.Set();
				m_DataTransferThread.Join(200);
			}
			m_DataTransferThread = null;
		}
		#endregion

		#region IDisposable Members
		
		protected override void Cleanup()
		{
			EndDataTransferThread();
			if(m_NotificationEvent != null)
			{
				m_NotificationEvent.Close() ;
			}
			if(m_Notify != null)
			{
				m_Notify.Dispose();
			}
			if (m_RiffReader != null)
			{
				m_RiffReader.Close();
			}
			base.Cleanup();
		}
		#endregion

		#region IPlayable methods

		public override void Play()
		{
			base.Play();
			if(m_SecondaryBuffer.PlayPosition != 0)		// already playing or paused
			{
				Debug.Assert(m_DataTransferThread != null && m_DataTransferThread.IsAlive,
					"DataTransferThread not available while playing or paused.");
				if(m_Paused)
				{
					Pause();		// toggle to unpaused state
				}
				return;
			}

			Stop();
			Debug.Assert(m_DataTransferThread == null);
			CreateDataTransferThread();		
			m_SecondaryBuffer.SetCurrentPosition(0) ;
			m_SecBufWritePos = 0;

			// load entire buffer
			DebugConsole.WriteLine("bytes so far    bytes this time SecBuf Write Pos");
			m_bytesSoFar = 0;
			for(int i = 0; i < m_numNotifyPositions; i++)
			{
				// get a block of bytes, possibly including zero-fill
				TransferBlockToSecondaryBuffer();
			}

			m_SecondaryBuffer.Play(0,BufferPlayFlags.Looping);
			return;
		}

		public override int Pause()
		{
			base.Pause();
			int playPosition = m_SecondaryBuffer.PlayPosition;
			if (m_SecondaryBuffer.Status.Playing)
			{
				m_SecondaryBuffer.Stop();
				m_Paused = true;
			}
			else if(m_Paused)
			{
				m_SecondaryBuffer.Play(0,BufferPlayFlags.Looping);
				m_Paused = false;
			}
			return playPosition ;
		}

		public override void Stop()
		{
			base.Stop();
			m_SecondaryBuffer.Stop();
			m_SecondaryBuffer.SetCurrentPosition(0);
			EndDataTransferThread();
			return;
		}

		#endregion
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

DrGary83
Software Developer (Senior)
United States United States
My life and career have been a bit unusual (mostly in good ways). So, I'm grateful every day for the opportunities God's given me to do different things and see different aspects of life.
 
Education: B.S. Physics '73 (atmospheric physics, sounding rockets), M.S. Computer Science '76 (radio astronomy, fuzzy controllers, music pattern recognition and visualization) New Mexico Tech; Ph.D. Engineering '83 (parallel computer architecture, digital signal processing, economics) U.C. Berkeley.
 
I'm married to Susan, a wonderful woman whom I met in a Computer Architecture class at U.C. Berkeley.
 
Professional activities: Digital systems engineer, digital audio pioneer, founder or key in several tech startups, consulting engineer, expert witness. I'm currently developing a multithreading framework in C# .NET, that makes it almost easy to write correct programs for multicore processors. I'm also implementing a new transform for recognizing, editing, and processing signals, especially sound.
 
I'm an occasional essayist, public speaker, and podcaster, and free-market space advocate. I enjoy good wine, good music, good friends, and cats.
 
If you think your project could use a different point of view, I'm available for consulting work in the San Francisco Bay area, or (preferrably) via the net.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 16 Feb 2007
Article Copyright 2006 by DrGary83
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid