We already learned everything that we needed to know about loading and downloading a Segment from the previous two chapters. But there are a ton of options to explore in how we actually play, stop, and control a running instance of a Segment. For that, we look at the SegmentState object and the PlaySegmentEx() and StopEx() calls.
One very important feature of DirectMusic is the ability to play the same Segment multiple times concurrently. This is useful for sound effects. For example, you might want to use the same car engine sound on several cars that are driving at the same time. This is also very important with musical fragments when the score is built from multiple Segments.
If the only way to adjust a parameter of the playing Segment or stop its playback involved applying the operation to the original Segment object, things would be very bad since that would have to represent all instances of the playing Segment. We need something to represent each instance of the playing Segment. DirectMusic provides such a beast, called the SegmentState. You can request one of these when you play the Segment and then use it to specifically stop the Segment later, find out if it is still playing, and even access some of its control parameters. It is represented by the IDirectMusicSegmentState interface. We use this to stop a playing Segment in our programming example.
Let's take a close look at PlaySegmentEx(), the method you call to actually play a Segment. We skip PlaySegment(), which is an older version from DirectX 7.0, as it is pretty much the same as PlaySegmentEx() with fewer options.
HRESULT hr = pPerformance->PlaySegmentEx( IUnknown * pSource, // Segment to play. WCHAR * pwzSegmentName, // Unused feature. IUnknown * pTransition, // Optional transition Segment. DWORD dwFlags, // Control flags. __int64 i64StartTime, // Optional start time. IDirectMusicSegmentState** ppSegmentState, // Optional Segment state to track // playing Segment. IUnknown * pFrom, // Optional Segment or AudioPath that stops when this starts. IUnknown * pAudioPath // AudioPath to play this Segment on. );
Typically, the three most important parameters are pSource, dwFlags, and pAudioPath. Pass the Segment you want to play to pSource, specify how you want it played with dwFlags, and determine which AudioPath it plays on with pAudioPath. (That is optional, too. You can pass NULL for the default AudioPath.)
Let's look at all the parameters in detail.
IUnknown * pSource: This is always an IDirectMusicSegment or IDirectMusicSegment8. Since IUnknown is a base class of IDirectMusicSegment, it automatically works without any casting or calling QueryInterface(). You can just pass the IDirectMusicSegment interface, and it will work. So, why isn't this just IDirectMusicSegment? The API is designed to support both IDirectMusicSegment and IDirectMusicSong. However, IDirectMusicSong is not currently exposed. When Microsoft eventually enables it, you will be in for a treat.
WCHAR * pwzSegmentName: This is only an option when using IDirectMusicSong, which is currently not enabled, so just ignore for now.
IUnknown * pTransition: The IUnknown interface pointer of a Segment to use to transition to this Segment. The transition Segment will play first, followed by the new Segment. The DMUS_SEGF_AUTOTRANSITION flag must be set in dwFlags. Also, if the transition Segment is using style technology and it includes the proper ChordMap Track, the chord progression for the transition is automatically composed to best fit between the currently playing Segment and the one we are transitioning to. These intelligent transitions make it much smoother when moving from one piece of music to another, resulting in a score that magically stays cohesive even when the story changes dramatically in unexpected ways.
DWORD dwFlags: This carries the control flags for the new Segment. These control how the Segment should be scheduled in terms of timing and behavior. It's worth taking some time to explore these. To make things easier, our sample application, Jones, lets you visually set these and hear what happens. All of these flags are explained in detail later when we walk through the source code.
__int64 i64StartTime: This is the time to begin playing the Segment. If 0, the Segment will play at the first opportunity, as defined by dwFlags (there are options to align with the time signature in different ways, for example). By default, this is assumed to be MUSIC_TIME units, which track the music time of the performance. However, if clock-time units are required, the DMUS_SEGF_REFTIME flag can be set and REFERENCE_TIME units used. This allows scheduling the start of the Segment's playback against a hard clock time, unaffected by the current musical tempo. This is particularly useful for sound effects that need to be scheduled.
IDirectMusicSegmentState** ppSegmentState: If you would like to track the playback of the Segment, pass the address of an IDirectMusicSegmentState interface pointer here. It is then filled in with an instance of an IDirectMusicSegmentState, which can then be used to operate on the specific instance of the playing Segment. This parameter can be NULL if no SegmentState object is wanted.
IUnknown * pFrom: Sometimes, you might want the new Segment to override an existing Segment, automatically stopping that Segment's playback at the exact time that the new one starts. Although this is automatically done with primary Segments, it isn't with controlling or secondary Segments, since they can overlap. This also lets you replace a series of Segments that are all playing on the same AudioPath with the new one. Therefore, pFrom is the IUnknown interface pointer of the SegmentState or AudioPath to stop when the new Segment begins playing. If it is an AudioPath, all SegmentStates playing on that AudioPath are stopped. This value can be NULL.
IUnknown * pAudioPath: Every Segment must play on an AudioPath. You can either provide a specific AudioPath object with this parameter or pass NULL, in which case the default AudioPath is used.
Often, you need to stop the Segment at some point. Again, there are two methods — the older Stop() and the newer StopEx(). Since StopEx() has all the functionality of Stop(), we only look at StopEx().
You have four choices for what you stop:
An individual instance of the playing Segment (the SegmentState).
All instances of the playing Segment.
All Segments playing on a specific AudioPath.
All Segments playing on the Performance.
HRESULT hr = m_pPerformance->StopEx( IUnknown *pObjectToStop, // Segment, SegState, or AudioPath to stop. __int64 i64StopTime, // Optional stop time. DWORD dwFlags // Control flags. );
IUnknown * pObjectToStop: Pointer to the interface of the object you'd like to stop. Since all COM objects are based on IUnknown, the IDirectMusicSegmentState, IDirectMusic-Segment, or IDirectMusicAudioPath interfaces all work.
IDirectMusicSegmentState will stop the playing instance of the Segment.
IDirectMusicSegment will stop all instances of the Segment.
IDirectMusicAudioPath will stop all Segments playing on the AudioPath.
NULL will stop all Segments playing.
__int64 i64StopTime: This defines the time to stop. If 0, the stop will occur at the first opportunity, as defined by dwFlags (there are options to align with the time signature in different ways, for example). By default, this is assumed to be MUSIC_TIME units, which track the music time of the performance. However, if clock-time units are required, the DMUS_SEGF_REFTIME flag can be set and REFERENCE_TIME units used. This allows scheduling the stop against a hard clock time, unaffected by the current musical tempo.
DWORD dwFlags: Control flags indicating when the stop should occur. These are a subset of the play flags, primarily focused on setting the timing grid. So, only DMUS_SEGF_BEAT, DMUS_SEGF_DEFAULT, DMUS_SEGF_GRID, DMUS_SEGF_MEASURE, DMUS_SEGF_REFTIME, DMUS_SEGF_MARKER, and DMUS_SEGF_SEGMENTEND are supported (more on these flags in a little bit).