
This chapter will introduce the remaining DirectX.NET sound classes, from the Sunlight.DirectX.Sound namespace: the BackgroundSound class.
This chapter introduces .NET threads.
The BackgroundSound class extends Sound to cope with DirectShow files, in addition to DirectMusic files. To this end, it encapsulates the DirectShow interfaces.
// Contains a background music file for DirectMusic or DirectShow, with an end-of-file event. __gc public class BackgroundSound : public Sound { protected: // DirectShow source filters bool m_bDirectShow; // true if sound should be playing bool m_bPlaying; IGraphBuilder __nogc *m_pGraphBuilder; IMediaControl __nogc *m_pMediaControl; IMediaSeeking __nogc *m_pMediaSeeking; IMediaEventEx __nogc *m_pMediaEventEx; // Thread to handle end-of-file notifications Threading::Thread *m_pRepeatThread; // Event handle for end-of-file notifications HANDLE m_heventRepeat; // Thread function for looking for end-of-file void OnEndThread(); // Called when the ParentWindow is closed. void OnFormClosed(Object *sender, EventArgs *e); // Called when the ParentWindow is activated. void OnFormActivated(Object *sender, EventArgs *e); // Called when the ParentWindow is deactivated. void OnFormDeactivated(Object *sender, EventArgs *e); // Load this sound object into memory. virtual void Load(); // Unload this object from memory. virtual void Unload(); public: BackgroundSound(); ~BackgroundSound(); // Play this object. virtual void Play(); // Stop the playback of this object. virtual void Pause(); // Stop the playback of this object and return to the beginning. virtual void Stop(); // Called when sound finishes playing. __event EventHandler *Finished; };
While BackgroundSound is somewhat more complex than Sound, it offers a similar public interface. The Finished event is fired when the sound object finishes playing. The Pause method allows the application to pause the playback, if possible (DirectMusic objects can't be paused).
BackgroundSound is a good deal more complex than Sound, but most of the code is fairly familiar. The Load method is extended to handle the new file types, by trapping the various exceptions thrown by Sound.Load.
// Load this sound object into memory. void BackgroundSound::Load() { HRESULT h; try { Sound::Load(); GUID guid = GUID_NOTIFICATION_SEGMENT; m_pSegment->AddNotificationType(guid); m_heventRepeat = CreateEvent(NULL, FALSE, FALSE, NULL); m_bDirectShow = false; } catch (Exception *) { // DirectMusic didn't like it, so we get a go
The m_bDirectShow flag is used to indicate whether a DirectMusic or a DirectShow object is being used. If Sound.Load succeeds, we use IDirectMusicSegment8::AddNotificationType to tell DirectMusic that we're interested in the segment notifications. We also create an event handle at this point; we'll associate this event handle with the segment when we play it. We use a Win32 event (using CreateEvent), rather than a .NET event object, for continuity with DirectShow.
If DirectMusic fails to load the segment, we allow DirectShow to attempt loading. This code is very similar to that used in the DirectX tutorial to load DirectShow objects:
// Create an IGraphBuilder object, through which // we will create a DirectShow graph. IGraphBuilder __nogc *pBuilder; h = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pBuilder); if (FAILED(h)) Marshal::ThrowExceptionForHR(h); m_pGraphBuilder = pBuilder; // Get the IMediaControl Interface IMediaControl __nogc *pMC; h = m_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&pMC); if (FAILED(h)) Marshal::ThrowExceptionForHR(h); m_pMediaControl = pMC; // Get the IMediaSeeking Interface IMediaSeeking __nogc *pMS; h = m_pGraphBuilder->QueryInterface(IID_IMediaSeeking, (void **)&pMS); if (FAILED(h)) Marshal::ThrowExceptionForHR(h); m_pMediaSeeking = pMS; // Get the IMediaEventEx Interface IMediaEventEx __nogc *pME; h = m_pGraphBuilder->QueryInterface(IID_IMediaEventEx, (void **)&pME); if (FAILED(h)) Marshal::ThrowExceptionForHR(h); m_pMediaEventEx = pME; HANDLE hEvent; h = m_pMediaEventEx->GetEventHandle((OAEVENT *)&hEvent); if (FAILED(h)) Marshal::ThrowExceptionForHR(h); m_heventRepeat = hEvent; // Add the file to the graph. LPWSTR wszFilename = (LPWSTR)(void *)Marshal::StringToCoTaskMemUni(m_pFilename); h = m_pGraphBuilder->RenderFile(wszFilename, NULL); CoTaskMemFree(wszFilename); if ((h == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (h == VFW_E_NOT_FOUND)) throw new IO::FileNotFoundException(String::Format(S"Unable to load sound file {0}", m_pFilename), m_pFilename); else if (FAILED(h)) Marshal::ThrowExceptionForHR(h); m_bDirectShow = true;
Finally, we hook the three important Form events - Closed, Activated and Deactivate. A BackgroundSound object should stop playing when its associated form loses focus, and then restart when focus is regained. Also, when the form closes, the object should be unloaded.
// Attach to the parent window's Closed event, to shut down the thread. __hook(&Form::Closed, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormClosed, this); __hook(&Form::Activated, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormActivated, this); __hook(&Form::Deactivate, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormDeactivated, this);
Now, when we play the object, we need to set the notification event handle for DirectMusic objects. DirectShow objects also need to be played in a different manner.
// Play this object from the beginning. void BackgroundSound::Play() { Prepare(); if (m_bDirectShow) { // Start the graph m_pMediaControl->Run(); } else { // Must reset notification handle every time, since user may have played // another BackgroundMusic DirectMusicObject->GetPerformance()->SetNotificationHandle(m_heventRepeat, 0); DirectMusicObject->GetPerformance()->PlaySegment(m_pSegment, 0, 0, NULL); } m_bPlaying = true; }
Finally, the notification events must be handled. This is done by way of a separate thread, created in the object's constructor:
BackgroundSound::BackgroundSound() :
m_pGraphBuilder(NULL),
m_pMediaControl(NULL),
m_pMediaSeeking(NULL),
m_pMediaEventEx(NULL),
m_heventRepeat(NULL),
m_bDirectShow(false),
m_bPlaying(false)
{
// Create a thread to handle the repeat events.
m_pRepeatThread = new Threading::Thread(new Threading::ThreadStart(this, OnEndThread));
m_pRepeatThread->Start();
}
A .NET thread is represented by a System.Threading.Thread object. The constructor for Thread takes a System.Threading.ThreadStart delegate that represents the thread entry point. Thread.Start is called to begin the thread, which then runs concurrently with the main thread.
OnEndThread waits for the event handle to be triggered, which indicates some kind of event from either DirectMusic or DirectSound.
void BackgroundSound::OnEndThread() { long evCode, param1, param2; HRESULT h; for (;;) { DWORD dwResult = WaitForSingleObject(m_heventRepeat, 200); if (dwResult == WAIT_OBJECT_0) { if (Finished != NULL) {
DirectShow objects report their events using IMediaEventEx::GetEvent. On any ending event, we should raise the Finished event and stop the sound:
if (m_bDirectShow) { for (;;) { h = m_pMediaEventEx->GetEvent(&evCode, ¶m1, ¶m2, 0); if (FAILED(h)) break; // Check notification type and do something in response. if ((evCode == EC_COMPLETE) || (evCode == EC_USERABORT) || (evCode == EC_ERRORABORT)) { Stop(); __raise Finished(this, new EventArgs()); } m_pMediaEventEx->FreeEventParams(evCode, param1, param2); } }
DirectMusic, on the other hand, uses IDirectMusicPerformance8::GetNotificationPMsg. This means that DirectMusic objects can, in practice, be in use in only one BackgroundSound object at a time.
else { DMUS_NOTIFICATION_PMSG *pPmsg; while (DirectMusicObject->GetPerformance()->GetNotificationPMsg(&pPmsg) == S_OK) { // Check notification type and do something in response. if (pPmsg->dwNotificationOption == DMUS_NOTIFICATION_SEGEND) { Stop(); __raise Finished(this, new EventArgs()); } DirectMusicObject->GetPerformance()->FreePMsg((DMUS_PMSG*)pPmsg); } } } }
Finally, if WaitForSingleObject returns anything other than a timeout, chances are the event has been closed, and so the thread should quit:
else if (dwResult != WAIT_TIMEOUT) return; }
That's it for sound. In the next chapter, we will discuss tying up the loose ends of the system with a Form-derived class, and look at error handling.