Home | Site Map
Sunlight

Sound Classes - DirectMusic

Introduction

This chapter will introduce the DirectX.NET basic sound classes, from the Sunlight.DirectX.Sound namespace: the DirectMusic and Sound classes.

Classes

DirectMusic

The DirectMusic class wraps an IDirectMusicPerformance8 object and an IDirectMusicLoader8 object, providing a .NET interface to the DirectMusic initialisation and loading objects.

__gc public class DirectMusic
{
protected:
    IDirectMusicPerformance8 __nogc *m_pPerformance;
    IDirectMusicLoader8      __nogc *m_pLoader;

    bool m_bCreated;

public:
    DirectMusic();
    ~DirectMusic();

    void Create();
    void StopAll();

    System::Windows::Forms::Form *ParentWindow;
    int Channels;

    __value enum AudioPathType
    {
    	Dynamic3D = DMUS_APATH_DYNAMIC_3D,
    	DynamicMono = DMUS_APATH_DYNAMIC_MONO,
    	StereoPlusReverb = DMUS_APATH_SHARED_STEREOPLUSREVERB,
    	DynamicStereo = DMUS_APATH_DYNAMIC_STEREO
    };
    AudioPathType   AudioPath;

    IDirectMusicPerformance8 __nogc *GetPerformance()
    {
        return m_pPerformance;
    }
    IDirectMusicLoader8 __nogc *GetLoader()
    {
        return m_pLoader;
    }
};

Public Members

DirectMusic offers several fields to set the properties of the IDirectMusicPerformance8 object. ParentWindow defines the associated form. Channels sets the number of audio channels (the maximum number of concurrent audio samples), 64 by default. Finally, AudioPath sets the audio path type, from the AudioPathType enumeration (StereoPlusReverb by default). Note that AudioPathType maps 'friendly' names to DirectMusic constants.

DirectMusic is a fairly simple wrapper for the DirectMusic initialisation interfaces, so it only offers a few basic methods. Create is called to create the underlying DirectMusic objects, once the ParentWindow field has been set. StopAll merely stops all sounds that are playing.

Implementation Notes

DirectMusic is, mercifully, a straightforward class, so we'll skip the implementation details.

Sound

The Sound class wraps an IDirectMusicSegment8 object. This represents a sound effect or music file.

// Contains a sound effect or music file for DirectMusic.
__gc public class Sound
{
protected:
    IDirectMusicSegment8    *m_pSegment;

    bool    m_bIsLoaded;
    String *m_pFilename;

    // Load this sound object into memory.
    virtual void Load();
    // Unload this object from memory.
    virtual void Unload();

public:
    Sound();
    ~Sound();

    // Play this object from the beginning.
    virtual void Play();
    // Stop the playback of this object.
    virtual void Stop();

    // Prepare this object for playback later.
    virtual void Prepare();

    // Filename of this sound object.
    __property String *get_Filename();
    __property void set_Filename(String *);

    DirectMusic *DirectMusicObject;
};

Public Members

Sound contains only two public fields: the Filename field, which contains the filename of the sound object, whether sound effect or music file, and the DirectMusicObject field, which specifies the DirectMusic object that will be used to create and play this sound.

The Prepare method is used to load the sound object into memory. This eliminates delays when playing the sound, but uses up more memory. The Play method is then used to play the sound, and Stop will stop playback.

Implementation Notes

The bulk of the work in a Sound object is involved in loading the sound file, which is done in the Load protected method. First, we use our old friend Marshal::StringToCoTaskMemUni (since DirectMusic operates solely in Unicode):

// Load this sound object into memory.
void Sound::Load()
{
    IDirectMusicSegment8 __nogc *pSegment;

    LPWSTR wszFilename = (LPWSTR)(void *)Marshal::StringToCoTaskMemUni(m_pFilename);
    HRESULT h = DirectMusicObject->GetLoader()->LoadObjectFromFile(CLSID_DirectMusicSegment,
        IID_IDirectMusicSegment8, wszFilename, (void **)&pSegment);
    CoTaskMemFree(wszFilename);

DirectMusic can return a variety of error codes, so we'll trap the most obvious ones:

    if (FAILED(h))
    {
        if ((h == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || 
            (h == DMUS_E_LOADER_FAILEDOPEN) || 
            (h == DMUS_E_UNSUPPORTED_STREAM))
            throw new IO::FileNotFoundException(String::Format(S"Unable to load music file {0}", 
				m_pFilename), m_pFilename);
        throw new Sunlight::DirectX::DirectXException(S"IDirectMusicLoader8::LoadObjectFromFile", 
            h);
    }

Finally, we attempt to detect whether the file is a standard MIDI file, which is simply done on the basis of the file extension. If you wanted to explicitly set this, you could derive a class from Sound (say 'StandardMIDISound'), override Load and call IDirectMusicSegment8::SetParam followed by Download.

    m_pSegment = pSegment;

    String *LowercaseFilename = m_pFilename->ToLower();
    if (LowercaseFilename->EndsWith(S".mid") || LowercaseFilename->EndsWith(S".rmi"))
    {
        // Standard MIDI file
	m_pSegment->SetParam(GUID_StandardMIDIFile, 
            0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0, NULL);       
    }

    m_pSegment->Download(DirectMusicObject->GetPerformance());
}

The rest of Sound is utterly trivial.

In the next chapter, we'll discuss BackgroundSound, which extends Sound to handle DirectShow-compatible music files (notably MP3-encoded), and also adds support for notification of the end of a sound effect.